#python #django #django-forms #django-generic-views
Вопрос:
У меня есть форма в модальном режиме начальной загрузки, и я хочу сохранить этот модальный режим открытым после отправки. Я использую CreateView и пытаюсь передать дополнительную переменную в шаблон в интерфейсе, где я мог бы проверить, установлен ли флаг или нет, но флаг всегда ложен даже после отправки. Вот что у меня есть:
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 %} lt;h1gt;Flag is setlt;/h1gt; {% endif %}
Есть ли способ передать данные, не связанные с формой, в CreateView в шаблон без необходимости изменять url.py (т. е. добавление переменных данных в URL-путь)?
Редактировать:
Я попытался распечатать self.successful_submit в методах form_valid() и post (), и он действительно обновляется до True, но в шаблоне он все еще передается как False.
Комментарии:
1. Извините, что я допустил эту ошибку, когда печатал это здесь. Так было
successful_submit
с самого начала. Я обновил код здесь.2. Вы устанавливаете
form.successful_submit
вместоself.successful_submit
post
метода. Сказав это, вы могли бы просто проверить наличиеrequest.POST
в своем шаблоне.3. @Bobort Спасибо, что разъяснили это. Я изменил это значение, но все равно получил ту же проблему. Значение действительно меняется, когда я проверяю его в методе form_valid() и post (), но оно не работает в интерфейсе и остается ложным.
4. Я бы забыл об установке флага и просто провел тестирование условий
request.POST
.{% if request.POST %}lt;h1gt;Post Successfullt;/h1gt;{% endif %}
Говоря это, выsuccess_url
, вероятно, не указываете наCreateView
то, что . По умолчанию, я думаю, это указывает наget_absolute_url
экземпляр. Таким образом, вы все равно не увидите этот набор переменных после успешной отправки.5. Я обновил сообщение своим urls.py. Не могли бы вы, пожалуйста, проверить, так ли это?
Ответ №1:
Это основная проблема: «У меня есть форма в модальном режиме начальной загрузки, и я хочу сохранить этот модальный режим открытым после отправки».
Простой ответ: Используйте Ajax.
Сейчас у нас есть HTML по проводам как набирающая популярность парадигма, но я недостаточно знаком с ней, чтобы обсуждать это. Поэтому я буду использовать Ajax, чтобы показать решение. В этом конкретном решении используется общий шаблон ajax, а результатом публикации является отрисованный шаблон Django, который вы можете использовать для простой замены HTML на уже отрисованной странице.
Кроме того, мало кому нравится JavaScript, но это недостаточно веская причина, чтобы избегать его использования. Это в основном обязательно в любом веб-приложении, которое вы запускаете. Даже HTML по проводам использует минимальное количество JavaScript для достижения своих целей.
Сначала напишите свое представление Ajax. Для этого я использую классы django-rest-framework и привожу пример заполнения записи калибровки. Вы можете использовать любую форму, какую захотите; это просто мой рецепт для обработки модалов, которые вы хотите сохранить открытыми. В этом представлении я возвращаю ответ JSON, если СООБЩЕНИЕ было успешным. В противном случае я возвращаю отрисованный шаблон 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
lt;!-- {% block modal_id %}{% endblock %}{% block modal_title %}{% endblock %} --gt; {% block modal_body %} {% endblock modal_body %}
general/modal.html
lt;div class="modal fade" id="{% block modal_id %}{{ modal_id }}{% endblock modal_id %}" tabindex="-1" role="dialog"gt; lt;div class="modal-dialog"gt; lt;div class="modal-content"gt; lt;div class="modal-header"gt; lt;button type="button" class="close" data-dismiss="modal"gt; lt;span aria-hidden="true"gt;amp;times;lt;/spangt; lt;span class="sr-only"gt;Closelt;/spangt; lt;/buttongt; lt;h4 class="modal-title"gt; {% block modal_title %}{{ modal_title }}{% endblock modal_title %} lt;/h4gt; lt;/divgt; lt;div class="modal-body"gt; {% block modal_body %} {% endblock modal_body %} lt;/divgt; lt;/divgt; lt;/divgt; lt;/divgt;
Несмотря на то, что мы используем Хрустящие формы, вы можете обойтись без них. У меня также есть общая библиотека тегов шаблонов, которая отображает любые ошибки в форме. Вы можете написать свой собственный.
documents/form/cal.html
{% extends request.is_ajax|yesno:'generalajax_modal.html,generalmodal.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 %} lt;div id="new-cal-form-container"gt; lt;form action="{% url 'calibration-create' %}" method="post" id="new-cal-modal-form" autocomplete="off" novalidategt; {% if request.is_ajax %} {% crispy calibration_form %} {% form_errors calibration_form %} {% endif %} lt;button type="submit" name="Submit" value="Save" class="btn btn-success button" id="submit"gt;savelt;/buttongt; lt;/formgt; lt;/divgt; {% 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") amp;amp; !$button.prop("disabled")) { //only do it if the button is "enabled" var $placeholder = $("lt;formgt;lt;h1gt;loading...lt;/h1gt;lt;/formgt;"); var $modalForm = $modalFormContainer.find("form"); var ajaxURL = $modalForm.prop("action"); $modalForm.replaceWith($placeholder); var pk = $button.data().pk; if (pk) { if (ajaxURL.indexOf("?") gt; 0) { ajaxURL = "amp;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 на момент написания этой статьи. Я должен добавить, что основное содержимое приложения имеет класс «главная панель», используемый в этом не очень универсальном плагине jQuery.
Я беспокоюсь, что я пошел и заставил вас сохранить вашу позицию, пытаясь продолжать использовать стандартный почтовый запрос. Я думаю, вы могли бы просто перерисовать шаблон с вашим сообщением, так как это будет стандартной практикой в вашем проекте. Вы все еще можете уйти, не используя строку запроса таким образом.
Комментарии:
1. Это отличный подход для людей, которые используют DRF. Мой вариант использования прост, поэтому DRF для меня немного излишен (по крайней мере, на данном этапе проекта). Кроме того, это вынудило бы меня переписать все, что я делал до сих пор, например, библиотеку django-tables2, которую я использую, и которая, как я обнаружил, плохо работает с классами, у которых одинаковый MRO. И неофициальный шаблон начальной загрузки 5 crispy-форм, который не будет работать так же хорошо на той же странице, если я сериализую объекты. Я принял ваш ответ, так как он может помочь кому-то другому, у которого такой же вариант использования.