1. ====================================
    
  2. Form handling with class-based views
    
  3. ====================================
    
  4. 
    
  5. Form processing generally has 3 paths:
    
  6. 
    
  7. * Initial GET (blank or prepopulated form)
    
  8. * POST with invalid data (typically redisplay form with errors)
    
  9. * POST with valid data (process the data and typically redirect)
    
  10. 
    
  11. Implementing this yourself often results in a lot of repeated boilerplate code
    
  12. (see :ref:`Using a form in a view<using-a-form-in-a-view>`). To help avoid
    
  13. this, Django provides a collection of generic class-based views for form
    
  14. processing.
    
  15. 
    
  16. Basic forms
    
  17. ===========
    
  18. 
    
  19. Given a contact form:
    
  20. 
    
  21. .. code-block:: python
    
  22.     :caption: ``forms.py``
    
  23. 
    
  24.     from django import forms
    
  25. 
    
  26.     class ContactForm(forms.Form):
    
  27.         name = forms.CharField()
    
  28.         message = forms.CharField(widget=forms.Textarea)
    
  29. 
    
  30.         def send_email(self):
    
  31.             # send email using the self.cleaned_data dictionary
    
  32.             pass
    
  33. 
    
  34. The view can be constructed using a ``FormView``:
    
  35. 
    
  36. .. code-block:: python
    
  37.     :caption: ``views.py``
    
  38. 
    
  39.     from myapp.forms import ContactForm
    
  40.     from django.views.generic.edit import FormView
    
  41. 
    
  42.     class ContactFormView(FormView):
    
  43.         template_name = 'contact.html'
    
  44.         form_class = ContactForm
    
  45.         success_url = '/thanks/'
    
  46. 
    
  47.         def form_valid(self, form):
    
  48.             # This method is called when valid form data has been POSTed.
    
  49.             # It should return an HttpResponse.
    
  50.             form.send_email()
    
  51.             return super().form_valid(form)
    
  52. 
    
  53. Notes:
    
  54. 
    
  55. * FormView inherits
    
  56.   :class:`~django.views.generic.base.TemplateResponseMixin` so
    
  57.   :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
    
  58.   can be used here.
    
  59. * The default implementation for
    
  60.   :meth:`~django.views.generic.edit.FormMixin.form_valid` simply
    
  61.   redirects to the :attr:`~django.views.generic.edit.FormMixin.success_url`.
    
  62. 
    
  63. Model forms
    
  64. ===========
    
  65. 
    
  66. Generic views really shine when working with models.  These generic
    
  67. views will automatically create a :class:`~django.forms.ModelForm`, so long as
    
  68. they can work out which model class to use:
    
  69. 
    
  70. * If the :attr:`~django.views.generic.edit.ModelFormMixin.model` attribute is
    
  71.   given, that model class will be used.
    
  72. * If :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
    
  73.   returns an object, the class of that object will be used.
    
  74. * If a :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` is
    
  75.   given, the model for that queryset will be used.
    
  76. 
    
  77. Model form views provide a
    
  78. :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` implementation
    
  79. that saves the model automatically.  You can override this if you have any
    
  80. special requirements; see below for examples.
    
  81. 
    
  82. You don't even need to provide a ``success_url`` for
    
  83. :class:`~django.views.generic.edit.CreateView` or
    
  84. :class:`~django.views.generic.edit.UpdateView` - they will use
    
  85. :meth:`~django.db.models.Model.get_absolute_url()` on the model object if available.
    
  86. 
    
  87. If you want to use a custom :class:`~django.forms.ModelForm` (for instance to
    
  88. add extra validation), set
    
  89. :attr:`~django.views.generic.edit.FormMixin.form_class` on your view.
    
  90. 
    
  91. .. note::
    
  92.     When specifying a custom form class, you must still specify the model,
    
  93.     even though the :attr:`~django.views.generic.edit.FormMixin.form_class` may
    
  94.     be a :class:`~django.forms.ModelForm`.
    
  95. 
    
  96. First we need to add :meth:`~django.db.models.Model.get_absolute_url()` to our
    
  97. ``Author`` class:
    
  98. 
    
  99. .. code-block:: python
    
  100.     :caption: ``models.py``
    
  101. 
    
  102.     from django.db import models
    
  103.     from django.urls import reverse
    
  104. 
    
  105.     class Author(models.Model):
    
  106.         name = models.CharField(max_length=200)
    
  107. 
    
  108.         def get_absolute_url(self):
    
  109.             return reverse('author-detail', kwargs={'pk': self.pk})
    
  110. 
    
  111. Then we can use :class:`CreateView` and friends to do the actual
    
  112. work. Notice how we're just configuring the generic class-based views
    
  113. here; we don't have to write any logic ourselves:
    
  114. 
    
  115. .. code-block:: python
    
  116.     :caption: ``views.py``
    
  117. 
    
  118.     from django.urls import reverse_lazy
    
  119.     from django.views.generic.edit import CreateView, DeleteView, UpdateView
    
  120.     from myapp.models import Author
    
  121. 
    
  122.     class AuthorCreateView(CreateView):
    
  123.         model = Author
    
  124.         fields = ['name']
    
  125. 
    
  126.     class AuthorUpdateView(UpdateView):
    
  127.         model = Author
    
  128.         fields = ['name']
    
  129. 
    
  130.     class AuthorDeleteView(DeleteView):
    
  131.         model = Author
    
  132.         success_url = reverse_lazy('author-list')
    
  133. 
    
  134. .. note::
    
  135.     We have to use :func:`~django.urls.reverse_lazy` instead of
    
  136.     ``reverse()``, as the urls are not loaded when the file is imported.
    
  137. 
    
  138. The ``fields`` attribute works the same way as the ``fields`` attribute on the
    
  139. inner ``Meta`` class on :class:`~django.forms.ModelForm`. Unless you define the
    
  140. form class in another way, the attribute is required and the view will raise
    
  141. an :exc:`~django.core.exceptions.ImproperlyConfigured` exception if it's not.
    
  142. 
    
  143. If you specify both the :attr:`~django.views.generic.edit.ModelFormMixin.fields`
    
  144. and :attr:`~django.views.generic.edit.FormMixin.form_class` attributes, an
    
  145. :exc:`~django.core.exceptions.ImproperlyConfigured` exception will be raised.
    
  146. 
    
  147. Finally, we hook these new views into the URLconf:
    
  148. 
    
  149. .. code-block:: python
    
  150.     :caption: ``urls.py``
    
  151. 
    
  152.     from django.urls import path
    
  153.     from myapp.views import AuthorCreateView, AuthorDeleteView, AuthorUpdateView
    
  154. 
    
  155.     urlpatterns = [
    
  156.         # ...
    
  157.         path('author/add/', AuthorCreateView.as_view(), name='author-add'),
    
  158.         path('author/<int:pk>/', AuthorUpdateView.as_view(), name='author-update'),
    
  159.         path('author/<int:pk>/delete/', AuthorDeleteView.as_view(), name='author-delete'),
    
  160.     ]
    
  161. 
    
  162. .. note::
    
  163. 
    
  164.     These views inherit
    
  165.     :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
    
  166.     which uses
    
  167.     :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
    
  168.     to construct the
    
  169.     :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
    
  170.     based on the model.
    
  171. 
    
  172.     In this example:
    
  173. 
    
  174.     * :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html``
    
  175.     * :class:`DeleteView` uses ``myapp/author_confirm_delete.html``
    
  176. 
    
  177.     If you wish to have separate templates for :class:`CreateView` and
    
  178.     :class:`UpdateView`, you can set either
    
  179.     :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` or
    
  180.     :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
    
  181.     on your view class.
    
  182. 
    
  183. Models and ``request.user``
    
  184. ===========================
    
  185. 
    
  186. To track the user that created an object using a :class:`CreateView`,
    
  187. you can use a custom :class:`~django.forms.ModelForm` to do this. First, add
    
  188. the foreign key relation to the model:
    
  189. 
    
  190. .. code-block:: python
    
  191.     :caption: ``models.py``
    
  192. 
    
  193.     from django.contrib.auth.models import User
    
  194.     from django.db import models
    
  195. 
    
  196.     class Author(models.Model):
    
  197.         name = models.CharField(max_length=200)
    
  198.         created_by = models.ForeignKey(User, on_delete=models.CASCADE)
    
  199. 
    
  200.         # ...
    
  201. 
    
  202. In the view, ensure that you don't include ``created_by`` in the list of fields
    
  203. to edit, and override
    
  204. :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user:
    
  205. 
    
  206. .. code-block:: python
    
  207.     :caption: ``views.py``
    
  208. 
    
  209.     from django.contrib.auth.mixins import LoginRequiredMixin
    
  210.     from django.views.generic.edit import CreateView
    
  211.     from myapp.models import Author
    
  212. 
    
  213.     class AuthorCreateView(LoginRequiredMixin, CreateView):
    
  214.         model = Author
    
  215.         fields = ['name']
    
  216. 
    
  217.         def form_valid(self, form):
    
  218.             form.instance.created_by = self.request.user
    
  219.             return super().form_valid(form)
    
  220. 
    
  221. :class:`~django.contrib.auth.mixins.LoginRequiredMixin` prevents users who
    
  222. aren't logged in from accessing the form. If you omit that, you'll need to
    
  223. handle unauthorized users in :meth:`~.ModelFormMixin.form_valid()`.
    
  224. 
    
  225. .. _content-negotiation-example:
    
  226. 
    
  227. Content negotiation example
    
  228. ===========================
    
  229. 
    
  230. Here is an example showing how you might go about implementing a form that
    
  231. works with an API-based workflow as well as 'normal' form POSTs::
    
  232. 
    
  233.     from django.http import JsonResponse
    
  234.     from django.views.generic.edit import CreateView
    
  235.     from myapp.models import Author
    
  236. 
    
  237.     class JsonableResponseMixin:
    
  238.         """
    
  239.         Mixin to add JSON support to a form.
    
  240.         Must be used with an object-based FormView (e.g. CreateView)
    
  241.         """
    
  242.         def form_invalid(self, form):
    
  243.             response = super().form_invalid(form)
    
  244.             if self.request.accepts('text/html'):
    
  245.                 return response
    
  246.             else:
    
  247.                 return JsonResponse(form.errors, status=400)
    
  248. 
    
  249.         def form_valid(self, form):
    
  250.             # We make sure to call the parent's form_valid() method because
    
  251.             # it might do some processing (in the case of CreateView, it will
    
  252.             # call form.save() for example).
    
  253.             response = super().form_valid(form)
    
  254.             if self.request.accepts('text/html'):
    
  255.                 return response
    
  256.             else:
    
  257.                 data = {
    
  258.                     'pk': self.object.pk,
    
  259.                 }
    
  260.                 return JsonResponse(data)
    
  261. 
    
  262.     class AuthorCreateView(JsonableResponseMixin, CreateView):
    
  263.         model = Author
    
  264.         fields = ['name']