Django CreateView 不更新上下文数据

Django CreateView not updating context data

我在 bootstrap 模态中有一个表单,我想在提交后保持该模态打开。我正在使用 CreateView 并尝试将一个附加变量传递给前端的模板,在那里我可以检查是否设置了标志,但即使在提交后标志也始终为 False。这是我拥有的:

url.py

from django.urls import path
from .views import MescData

urlpatterns = [
    path('mesc', MescData.as_view(), name='mesc')
]

views.py

from django.urls import reverse
from django.views.generic.edit import CreateView

from .forms import MescForm
from .models import Mesc


class MescData(CreateView):
    model = Mesc
    form_class = MescForm
    template_name = 'Materials/mesc_data.html'
    successful_submit = False  # Flag to keep the add entry modal open

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['successful_submit'] = self.successful_submit
        return context

    def get_success_url(self):
        return reverse('mesc')

    def post(self, request, *args, **kwargs):
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        self.successful_submit = True

        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, **kwargs):
        # self.successful_submit = True
        return super(MescData, self).form_valid(form, **kwargs)

在模板中,我是这样检查的:

{% if successful_submit %}
    <h1>Flag is set</h1>
{% endif %}

有没有一种方法可以将 CreateView 中与表单无关的数据传递给模板,而无需更改 url.py(即将变量数据添加到 url 路径)?

编辑:

我尝试在 form_valid() 和 post() 方法中打印 self.successful_submit,它确实被更新为 True,但在模板中它仍在传递为假。

这是核心问题:“我在 bootstrap 模式中有一个表单,我想在提交后保持该模式打开。”

简单的答案:使用Ajax。

我们现在确实有 HTML 在线范例越来越受欢迎,但我对它还不够熟悉,无法讨论它。所以我将使用 Ajax 来展示解决方案。此特定解决方案使用通用 ajax 模板,post 的结果是呈现的 Django 模板,您可以使用它来替换已呈现页面中的 HTML。

此外,很少有人喜欢 JavaScript,但这并不是避免使用它的充分理由。在您 运行 的任何 Web 应用程序中,它基本上都是强制性的。即使 HTML 通过网络使用最少量的 JavaScript 来实现其目标。

首先,写下您的 Ajax 观点。为此,我正在使用 django-rest-framework classes,我提供了一个填写校准记录的示例。你可以使用任何你想要的形式;这只是我处理你想要保持打开的模态的方法。在这个视图中,如果 POST 成功,我会 return 一个 JSON 响应。否则,我 return 渲染的 Django 模板。

from rest_framework.generics import (CreateAPIView, RetrieveAPIView,
                                     UpdateAPIView, get_object_or_404)
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response

class CalibrationCreateAjaxView(CreateAPIView, UpdateAPIView, RetrieveAPIView):
    renderer_classes = (TemplateHTMLRenderer,)
    template_name = "documents/form/cal.html"

    def post(self, request, *args, **kwargs):
        context = self.get_context(request)
        calibration_form = context['calibration_form']
        if calibration_form.is_valid():
            calibration_form.save()
            request.accepted_renderer = JSONRenderer()
            return Response(status=201)
        return Response(context, status=400)

    def get(self, request, *args, **kwargs):
        return Response(self.get_context(request))

    @staticmethod
    def get_context(request):
        pk = request.GET.get("pk")
        calibration_entry = get_object_or_404(CalibrationEntry, pk=pk) if pk else None
        return {
            'calibration_form': CalibrationFormAjax(request.POST or None, instance=calibration_entry)
        }

我也有我的视图模板。它利用了 request.is_ajax,后者已被弃用。您需要添加一些中间件才能继续使用它。这是我的中间件。也将它添加到您的设置文件中。

class IsAjaxMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    """
    request.is_ajax is being removed in Django 4
    Since we depend on this in our templates, we are adding this attribute to request
    Please review:
    https://docs.djangoproject.com/en/4.0/releases/3.1/#id2
    """
    def __call__(self, request):
        request.is_ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest'
        return self.get_response(request)

general/ajax_modal.html

<!-- {% block modal_id %}{% endblock %}{% block modal_title %}{% endblock %} -->
{% block modal_body %}
{% endblock modal_body %}

general/modal.html

<div class="modal fade" id="{% block modal_id %}{{ modal_id }}{% endblock modal_id %}" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">
                   <span aria-hidden="true">&times;</span>
                   <span class="sr-only">Close</span>
                </button>
                <h4 class="modal-title">
                    {% block modal_title %}{{ modal_title }}{% endblock modal_title %}
                </h4>
            </div>
            <div class="modal-body">
                {% block modal_body %}
                {% endblock modal_body %}
            </div>
        </div>
    </div>
</div>

即使我们使用的是 Crispy Forms,您也可以不用它。我还有一个通用的 templatetag 库,可以在表单上呈现任何错误。你可以自己写。

documents/form/cal.html

{% extends request.is_ajax|yesno:'general\ajax_modal.html,general\modal.html' %}
{% load crispy_forms_tags general %}
{% block modal_id %}new-cal-modal{% endblock modal_id %}
{% block modal_title %}Enter Calibration Information{% endblock modal_title %}
{% block modal_body %}
<div id="new-cal-form-container">
    <form action="{% url 'calibration-create' %}" method="post" id="new-cal-modal-form" autocomplete="off" novalidate>
        {% if request.is_ajax %}
            {% crispy calibration_form %}
            {% form_errors calibration_form %}
        {% endif %}
        <button type="submit" name="Submit" value="Save" class="btn btn-success button" id="submit">save</button>
    </form>
</div>
{% endblock modal_body %}

现在 Ajax 视图已全部设置完毕,我返回主页面,当用户单击按钮时,该页面将呈现模态对话框。我有一个名为“extraContent”的块,其中包含模态表单的模板。

{% block extraContent %}
    {% include 'documents/form/cal.html' %}
{% endblock extraContent %}

现在,JavaScript 需要 jQuery,我已将其添加到模板中。我想我在此基础上制作了自己的 jQuery 插件...

$.fn.modalFormContainer = function(optionsObject) {
    //call it on the modal div (the div that contains a modal-dialog, which contains modal-header, modal-body, etc
    //  we expect there to be a form within that div, and that form should have an action url
    //  when buttons that trigger the modal are clicked, the form is fetched from that action url and replaced.
    // optionsObject has formAfterLoadFunction and ajaxDoneFunction
    var options = $.extend({}, $.fn.modalFormContainer.defaults, optionsObject);
    var $modalFormContainer = $(this);
    // get the buttons that trigger this modal to open
    //  add a click event so that the form is fetched whenever the buttons are clicked
    //  if data-pk is an attribute on the button, apply that to the querystring of the
    //      ajaxURL when fetching the form
    var modalID = $modalFormContainer.prop("id");
    var modalFormButtonSelector = "[data-target=#" + modalID + "][data-toggle=modal]";

    function handleModalButtonClick(event) {
        //does the button have an associated pk? if so add the pk to the querystring of the ajax url
        //   this is wrapped in a form so that it gets replaced by the ajax response.
        var $button = $(this);
        if (!$button.hasClass("disabled") && !$button.prop("disabled")) { //only do it if the button is "enabled"
            var $placeholder = $("<form><h1>loading...</h1></form>");
            var $modalForm = $modalFormContainer.find("form");
            var ajaxURL = $modalForm.prop("action");
            $modalForm.replaceWith($placeholder);
            var pk = $button.data().pk;
            if (pk) {
                if (ajaxURL.indexOf("?") > 0) {
                    ajaxURL += "&pk=" + pk;
                } else {
                    ajaxURL += "?pk=" + pk;
                }
            }
            //fetch the form and replace $modalFormContainer's contents with it
            $.ajax({
                type: "GET",
                url: ajaxURL
            }).done(function(response) {
                // re-create the form from the response
                $modalFormContainer.find(".modal-body").html(response);
                $modalForm = $modalFormContainer.find("form"); //we would still need to find the form
                options.formAfterLoadFunction($modalForm);
            });
        } else {
            return false; //don't trigger the modal.
        }

    }
    //using delegation here so that dynamically added buttons will still have the behavior.
    // maybe use something more specific than '.main-panel' to help with performance?
    $(".main-panel").on("click", modalFormButtonSelector, handleModalButtonClick);

    $modalFormContainer.on("submit", "form", function(event) {
        // Stop the browser from submitting the form
        event.preventDefault();
        var $modalForm = $(event.target);
        var ajaxURL = $modalForm.prop("action");
        $modalForm.find("[type=submit]").addClass("disabled").prop("disabled", true);
        var formData = $modalForm.serialize();
        var internal_options = {
            url: ajaxURL,
            type: "POST",
            data: formData
        };
        // file upload forms have and enctype attribute
        //    we should not process files to be converted into strings
        if ($modalForm.attr("enctype") === "multipart/form-data") {
            internal_options.processData = false;
            internal_options.contentType = false;
            internal_options.cache = false;
            formData = new FormData($modalForm.get(0));
            internal_options.data = formData;
        }
        $.ajax(internal_options).done(function(response) {
            // blank out the form
            $modalForm.find("input:visible, select:visible, textarea:visible").val("");
            // remove errors on the form
            $modalForm.find(".has-error").removeClass("has-error");
            $modalForm.find("[id^=error]").remove();
            $modalForm.find(".alert.alert-block.alert-danger").remove();
            // hide the modal
            $(".modal-header .close").click();
            options.ajaxDoneFunction(response);
        }).fail(function(data) {
            // re-create the form from the response
            $modalFormContainer.find(".modal-body").html(data.responseText);
            options.formAfterLoadFunction($modalForm);
        });
    });

    return this;
};

$.fn.modalFormContainer.defaults = {
    formAfterLoadFunction: function($form) { return; },
    ajaxDoneFunction: function(response) { return; }
};

$("#new-cal-modal").modalFormContainer({
    formAfterLoadFunction: function($modalForm) {
        $(".datetimeinput").datepicker('destroy');
        $(".datetimeinput").datepicker();
    },
    ajaxDoneFunction: function(event) {
        location.reload();
    }
});

所以在回顾这个之后,我意识到这个食谱比我欺骗自己相信的要复杂得多。对此我深表歉意。我希望您可以查看代码并了解发生了什么。有一些边缘情况,比如处理日期和文件上传,这个秘诀现在可以处理,但你可能实际上不需要它们。我应该提一下,它来自的应用程序正在使用 Bootstrap 3,因此在撰写本文时,它的样式尚未更新为当前的 Bootstrap 5。我应该补充一点,应用程序的主要内容有一个 class 的“主面板”,正如在这个不那么通用的 jQuery 插件中所使用的那样。

我担心我已经离开并让您不知所措,以保持您尝试继续使用标准 POST 请求的立场。我想您可以使用 POST 重新渲染模板,因为这将成为您项目中的标准做法。你仍然可以在不使用查询字符串的情况下逃脱。