123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- ====================================
- Form handling with class-based views
- ====================================
- Form processing generally has 3 paths:
- * Initial GET (blank or prepopulated form)
- * POST with invalid data (typically redisplay form with errors)
- * POST with valid data (process the data and typically redirect)
- Implementing this yourself often results in a lot of repeated boilerplate code
- (see :ref:`Using a form in a view<using-a-form-in-a-view>`). To help avoid
- this, Django provides a collection of generic class-based views for form
- processing.
- Basic forms
- ===========
- Given a simple contact form:
- .. snippet::
- :filename: 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
- The view can be constructed using a ``FormView``:
- .. snippet::
- :filename: 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(ContactView, self).form_valid(form)
- Notes:
- * FormView inherits
- :class:`~django.views.generic.base.TemplateResponseMixin` so
- :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
- can be used here.
- * The default implementation for
- :meth:`~django.views.generic.edit.FormMixin.form_valid` simply
- redirects to the :attr:`~django.views.generic.edit.FormMixin.success_url`.
- Model forms
- ===========
- Generic views really shine when working with models. These generic
- views will automatically create a :class:`~django.forms.ModelForm`, so long as
- they can work out which model class to use:
- * If the :attr:`~django.views.generic.edit.ModelFormMixin.model` attribute is
- given, that model class will be used.
- * If :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
- returns an object, the class of that object will be used.
- * If a :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` is
- given, the model for that queryset will be used.
- Model form views provide a
- :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` implementation
- that saves the model automatically. You can override this if you have any
- special requirements; see below for examples.
- You don't even need to provide a ``success_url`` for
- :class:`~django.views.generic.edit.CreateView` or
- :class:`~django.views.generic.edit.UpdateView` - they will use
- :meth:`~django.db.models.Model.get_absolute_url()` on the model object if available.
- If you want to use a custom :class:`~django.forms.ModelForm` (for instance to
- add extra validation) simply set
- :attr:`~django.views.generic.edit.FormMixin.form_class` on your view.
- .. note::
- When specifying a custom form class, you must still specify the model,
- even though the :attr:`~django.views.generic.edit.FormMixin.form_class` may
- be a :class:`~django.forms.ModelForm`.
- First we need to add :meth:`~django.db.models.Model.get_absolute_url()` to our
- ``Author`` class:
- .. snippet::
- :filename: models.py
- from django.urls import reverse
- from django.db import models
- class Author(models.Model):
- name = models.CharField(max_length=200)
- def get_absolute_url(self):
- return reverse('author-detail', kwargs={'pk': self.pk})
- Then we can use :class:`CreateView` and friends to do the actual
- work. Notice how we're just configuring the generic class-based views
- here; we don't have to write any logic ourselves:
- .. snippet::
- :filename: views.py
- from django.views.generic.edit import CreateView, UpdateView, DeleteView
- from django.urls import reverse_lazy
- 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')
- .. note::
- We have to use :func:`~django.urls.reverse_lazy` here, not just
- ``reverse()`` as the urls are not loaded when the file is imported.
- The ``fields`` attribute works the same way as the ``fields`` attribute on the
- inner ``Meta`` class on :class:`~django.forms.ModelForm`. Unless you define the
- form class in another way, the attribute is required and the view will raise
- an :exc:`~django.core.exceptions.ImproperlyConfigured` exception if it's not.
- If you specify both the :attr:`~django.views.generic.edit.ModelFormMixin.fields`
- and :attr:`~django.views.generic.edit.FormMixin.form_class` attributes, an
- :exc:`~django.core.exceptions.ImproperlyConfigured` exception will be raised.
- Finally, we hook these new views into the URLconf:
- .. snippet::
- :filename: urls.py
- from django.conf.urls import url
- from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
- urlpatterns = [
- # ...
- url(r'author/add/$', AuthorCreate.as_view(), name='author-add'),
- url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author-update'),
- url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author-delete'),
- ]
- .. note::
- These views inherit
- :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
- which uses
- :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
- to construct the
- :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
- based on the model.
- In this example:
- * :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html``
- * :class:`DeleteView` uses ``myapp/author_confirm_delete.html``
- If you wish to have separate templates for :class:`CreateView` and
- :class:`UpdateView`, you can set either
- :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` or
- :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
- on your view class.
- Models and ``request.user``
- ===========================
- To track the user that created an object using a :class:`CreateView`,
- you can use a custom :class:`~django.forms.ModelForm` to do this. First, add
- the foreign key relation to the model:
- .. snippet::
- :filename: 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)
- # ...
- In the view, ensure that you don't include ``created_by`` in the list of fields
- to edit, and override
- :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user:
- .. snippet::
- :filename: views.py
- from django.views.generic.edit import CreateView
- from myapp.models import Author
- class AuthorCreate(CreateView):
- model = Author
- fields = ['name']
- def form_valid(self, form):
- form.instance.created_by = self.request.user
- return super(AuthorCreate, self).form_valid(form)
- Note that you'll need to :ref:`decorate this
- view<decorating-class-based-views>` using
- :func:`~django.contrib.auth.decorators.login_required`, or
- alternatively handle unauthorized users in the
- :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()`.
- AJAX example
- ============
- Here is a simple 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(object):
- """
- 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(AjaxableResponseMixin, self).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(AjaxableResponseMixin, self).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']
|