Обработка форм в представлениях-классах

Обработка форм осуществляется обычно в три этапа:

  • Инициализирующий GET запрос(пустая или частично проинициализированная форма)
  • Запрос POST с неверными данными(обычно отображается форма с указанием ошибок)
  • Запрос POST с корректными данными(обработка данных и , как правило, перенаправление)

При самостоятельной реализации подобной обработки форм, вы сталкиваетесь с большим количеством повторяющегося «шаблонного» кода. (См. использование форм в представлениях). Чтобы избавиться от этого, Django предоставляет нам коллекцию общих CBV для работы с формами.

Базовые формы

Given a contact form:

forms.py
from django import forms

class ContactForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

    def send_email(self):
        # send email using the self.cleaned_data dictionary
        pass

Представление можно создать используя класс FormView:

views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView

class ContactView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)

Примечания:

  • FormView наследует TemplateResponseMixin, таким образом вы можете использовать атрибут template_name.
  • Реализация по умолчанию для метода form_valid() просто осуществляет редирект на URL, хранящийся в атрибуте success_url.

Формы моделей

Общие CBV раскрываются во всей красе при работе с моделями. Они автоматически создают класс ModelForm при работе с моделями:

  • Если указано значение атрибута model, то будет использоваться этот класс модели.
  • Если метод get_object() возвращает объект, то будет использоваться класс этого объекта.
  • Если указан атрибут queryset, то будет использована модель этого запроса(queryset).

Представления форм, связанные с моделями, предоставляют реализацию метода form_valid(), которая автоматически сохраняет модель. Вы можете переопределить этот метод согласно вашим требованиям; смотрите примеры ниже.

Вы можете не устанавливать значение success_url для классов CreateView или UpdateView - они воспользуются методом get_absolute_url() объекта модели (если такой объект доступен).

If you want to use a custom ModelForm (for instance to add extra validation), set form_class on your view.

Примечание

При создании пользовательского класса формы, вы по прежнему должны указать модель. Даже в том случае если в form_class используется ModelForm.

Во-первых, мы должны добавить метод get_absolute_url() в наш класс Author :

models.py
from django.db import models
from django.urls import reverse

class Author(models.Model):
    name = models.CharField(max_length=200)

    def get_absolute_url(self):
        return reverse('author-detail', kwargs={'pk': self.pk})

Затем мы можем использовать класс CreateView и «сотоварищей» чтобы выполнить необходимую работу. Обратите внимание, что мы лишь создаем конфигурацию для CBV; нам не нужно писать никакого кода, реализующего логику:

views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author

class AuthorCreate(CreateView):
    model = Author
    fields = ['name']

class AuthorUpdate(UpdateView):
    model = Author
    fields = ['name']

class AuthorDelete(DeleteView):
    model = Author
    success_url = reverse_lazy('author-list')

Примечание

We have to use reverse_lazy() instead of reverse(), as the urls are not loaded when the file is imported.

Атрибут fields работает аналогично атрибуту fields внутреннего класса Meta ModelForm. Если вы не указали класс формы другим способом, этот атрибут обязателен и вы получите исключение ImproperlyConfigured, если он не указан.

Если вы указали и fields и form_class, будет вызвано исключение ImproperlyConfigured.

И наконец, мы подключаем новые представления в URLconf:

urls.py
from django.urls import path
from myapp.views import AuthorCreate, AuthorDelete, AuthorUpdate

urlpatterns = [
    # ...
    path('author/add/', AuthorCreate.as_view(), name='author-add'),
    path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
    path('author/<int:pk>/delete/', AuthorDelete.as_view(), name='author-delete'),
]

Примечание

Эти представления наследуют SingleObjectTemplateResponseMixin,который использует атрибут template_name_suffix для создания template_name с помощью имени модели.

В этом примере:

  • Классы CreateView и UpdateView используют myapp/author_form.html
  • Классы DeleteView используют myapp/author_confirm_delete.html

Если вам нужно разделить шаблоны для классов CreateView и UpdateView, вы можете либо назначить атрибут template_name, либо использовать template_name_suffix в ваших классах представлений.

Модели и request.user

Чтобы отслеживать пользователя, создавшего некий объект с помощью CreateView, вы можете использовать пользовательский класс ModelForm. Первое, добавьте внешний ключ (foreign key) к модели:

models.py
from django.contrib.auth.models import User
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=200)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)

    # ...

В представлении удостоверьтесь, что вы не включаете created_by в список редактируемых полей и переопределите метод form_valid() для добавления пользователя:

views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author

class AuthorCreate(LoginRequiredMixin, CreateView):
    model = Author
    fields = ['name']

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)

LoginRequiredMixin prevents users who aren’t logged in from accessing the form. If you omit that, you’ll need to handle unauthorized users in form_valid().

Пример с использованием AJAX

Here is an example showing how you might go about implementing a form that works for AJAX requests as well as „normal“ form POSTs:

from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author

class AjaxableResponseMixin:
    """
    Mixin to add AJAX support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """
    def form_invalid(self, form):
        response = super().form_invalid(form)
        if self.request.is_ajax():
            return JsonResponse(form.errors, status=400)
        else:
            return response

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super().form_valid(form)
        if self.request.is_ajax():
            data = {
                'pk': self.object.pk,
            }
            return JsonResponse(data)
        else:
            return response

class AuthorCreate(AjaxableResponseMixin, CreateView):
    model = Author
    fields = ['name']