Рубрики
Без рубрики

Обработка нескольких форм на одной странице в Django

Узнайте, как реализовать две разные формы на одной странице способом “Django”.

Автор оригинала: 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:

Вот как выглядит иерархия обработки форм на основе классов 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. Например, вышеприведенные формы будут отображаться в шаблоне следующим образом:

{% csrf_token %} {{ forms.subscription }}
{% csrf_token %} {{ forms.contact }}

Вывод

Похоже, мы выполнили наши критерии проектирования, сделав обработку форм более модульной, которая может быть повторно использована в разных проектах. Мы также можем расширить это, чтобы добавить больше форм в нашем представлении и рефакторинг нашей логики только для одной формы, если это необходимо. Кроме того, код очень похож на CBVS существующей формы без большой кривой обучения.

Вы можете найти код для мультиформ, а также примеры использования здесь.