123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- 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::
- # 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``::
- # 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:
- .. code-block:: python
- # models.py
- from django.core.urlresolvers 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::
- # views.py
- from django.views.generic.edit import CreateView, UpdateView, DeleteView
- from django.core.urlresolvers 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.core.urlresolvers.reverse_lazy` here, not
- just ``reverse`` as the urls are not loaded when the file is imported.
- .. versionchanged:: 1.6
- In Django 1.6, the ``fields`` attribute was added, which works the same way as
- the ``fields`` attribute on the inner ``Meta`` class on
- :class:`~django.forms.ModelForm`.
- Omitting the fields attribute will work as previously, but is deprecated and
- this attribute will be required from 1.8 (unless you define the form class in
- another way).
- Finally, we hook these new views into the URLconf::
- # urls.py
- from django.conf.urls import patterns, url
- from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
- urlpatterns = patterns('',
- # ...
- url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
- url(r'author/(?P<pk>\d+)/$', AuthorUpdate.as_view(), name='author_update'),
- url(r'author/(?P<pk>\d+)/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::
- # models.py
- from django.contrib.auth import User
- from django.db import models
- class Author(models.Model):
- name = models.CharField(max_length=200)
- created_by = models.ForeignKey(User)
- # ...
- 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::
- # 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::
- import json
- from django.http import HttpResponse
- 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 render_to_json_response(self, context, **response_kwargs):
- data = json.dumps(context)
- response_kwargs['content_type'] = 'application/json'
- return HttpResponse(data, **response_kwargs)
- def form_invalid(self, form):
- response = super(AjaxableResponseMixin, self).form_invalid(form)
- if self.request.is_ajax():
- return self.render_to_json_response(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 self.render_to_json_response(data)
- else:
- return response
- class AuthorCreate(AjaxableResponseMixin, CreateView):
- model = Author
- fields = ['name']
|