Автор оригинала: Lakshmi Narasimhan.
Недавно мне пришлось реализовать обработку двух разных форм на одной странице в Django. Благодаря тому, что этот вариант использования удивительно распространен, я не смог найти много примеров того, как это сделать “Джанго”.
Предпосылка
Допустим, на странице есть две формы. Одна форма для подписки пользователя, которая принимает только электронную почту, а другая-контактная форма, которая принимает заголовок и описание. Мы должны обрабатывать обе формы в одном и том же представлении.
class ContactForm(forms.Form): title = forms.CharField(max_length=150) message = forms.CharField(max_length=200, widget=forms.TextInput) class SubscriptionForm(forms.Form): email = forms.EmailField()
Первый разрез
Давайте обработаем их с помощью представлений, основанных на функциях Django.
def multiple_forms(request): if request.method == 'POST': contact_form = ContactForm(request.POST) subscription_form = SubscriptionForm(request.POST) if contact_form.is_valid() or subscription_form.is_valid(): # Do the needful return HttpResponseRedirect(reverse('form-redirect') ) else: contact_form = ContactForm() subscription_form = SubscriptionForm() return render(request, 'pages/multiple_forms.html', { 'contact_form': contact_form, 'subscription_form': subscription_form, })
Основная проблема с этим подходом заключается в том, что нет надежного способа инициировать только проверку и отправку формы, которая была отправлена. Кроме того, это неуклюжий рефакторинг логики после отправки в этот код.
Как насчет использования представлений на основе классов? Чтобы понять, как мы можем использовать представление на основе классов для решения этой проблемы, давайте посмотрим, как одна форма обрабатывается с помощью CBVs.
Django FormView
является основным классом для обработки форм таким образом. Как минимум, ему нужно:
- Атрибут
form_class
, указывающий на класс, форму которого мы хотим обработать. - Атрибут
success_url
указывает, на какой URL-адрес следует перенаправить после успешной обработки формы. - A
form_valid
метод для выполнения фактической логики обработки.
Вот как выглядит иерархия обработки форм на основе классов Django:
Проектирование нашего многоформного погрузочно-разгрузочного механизма
Давайте попробуем имитировать то же самое для нескольких форм. Наши критерии проектирования таковы:
- Простота использования или лучший опыт разработчика с точки зрения рефакторинга и тестирования.
- Интуитивно понятный, то есть если он работает и ведет себя как существующая обработка форм. CBVs, это было бы легко понять и потреблять
- Модульный, где я могу повторно использовать это в разных проектах и приложениях.
Вместо одного класса у нас будет dict
классов форм. Мы следуем тому же правилу и для успешных URL-адресов. Вполне вероятно, что каждая форма на странице имеет свой собственный URL-адрес, который будет перенаправлен после успешной отправки.
Типичным шаблоном использования будет:
class MultipleFormsDemoView(MultiFormsView): template_name = "pages/cbv_multiple_forms.html" form_classes = {'contact': ContactForm, 'subscription': SubscriptionForm, } success_urls = { 'contact': reverse_lazy('contact-form-redirect'), 'subscription': reverse_lazy('submission-form-redirect'), } def contact_form_valid(self, form): 'contact form processing goes in here' def subscription_form_valid(self, form): 'subscription form processing goes in here'
Мы также разрабатываем аналогичную иерархию классов для обработки нескольких форм.
Есть два заметных изменения при обработке нескольких форм. Чтобы сработала корректная функция обработки формы , мы должны проложить маршрут во время публикации , используя скрытый параметр в каждой форме, называемый action
. Мы встраиваем этот скрытый вход в обе формы. Это также может быть сделано в более элегантной манере, как в:
class MultipleForm(forms.Form): action = forms.CharField(max_length=60, widget=forms.HiddenInput()) class ContactForm(MultipleForm): title = forms.CharField(max_length=150) message = forms.CharField(max_length=200, widget=forms.TextInput) class SubscriptionForm(MultipleForm): email = forms.EmailField()
Значение атрибута action
обычно является именем ключа в form_classes
. Обратите внимание, как префикс для каждой функции form~valid~сопоставляется с именем ключа в form_classes dict. Это взято из
атрибута action .
Второе изменение состоит в том, чтобы убедиться, что этот атрибут action
предварительно заполнен правильным именем формы из form_classes dict. Мы
слегка изменяем get_initial функцию, чтобы сделать это, предоставляя разработчикам возможность переопределить это на основе каждой формы.
Серьезно, у меня было требование разработать несколько форм, каждая из которых имела свой собственный набор начальных аргументов.
def get_initial(self, form_name): initial_method = 'get_%s_initial' % form_name if hasattr(self, initial_method): return getattr(self, initial_method)() else: return {'action': form_name}
Фактическая функция проверки формы будет вызвана из post()
.
def _process_individual_form(self, form_name, form_classes): forms = self.get_forms(form_classes) form = forms.get(form_name) if not form: return HttpResponseForbidden() elif form.is_valid(): return self.forms_valid(forms, form_name) else: return self.forms_invalid(forms)
Существует много возможностей для улучшения, например, прибегать к некоторому поведению по умолчанию, когда функция form~valid~ формы не существует, или создавать исключения, но этого должно быть достаточно для большинства случаев.
Мы можем ссылаться на эти формы в шаблоне по имени ключа dict. Например, вышеприведенные формы будут отображаться в шаблоне следующим образом:
Вывод
Похоже, мы выполнили наши критерии проектирования, сделав обработку форм более модульной, которая может быть повторно использована в разных проектах. Мы также можем расширить это, чтобы добавить больше форм в нашем представлении и рефакторинг нашей логики только для одной формы, если это необходимо. Кроме того, код очень похож на CBVS существующей формы без большой кривой обучения.
Вы можете найти код для мультиформ, а также примеры использования здесь.