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">×</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 重新渲染模板,因为这将成为您项目中的标准做法。你仍然可以在不使用查询字符串的情况下逃脱。
我在 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">×</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 重新渲染模板,因为这将成为您项目中的标准做法。你仍然可以在不使用查询字符串的情况下逃脱。