| ===================================== |
| Writing your first Django app, part 4 |
| ===================================== |
| |
| This tutorial begins where :doc:`Tutorial 3 </intro/tutorial03>` left off. We're |
| continuing the Web-poll application and will focus on simple form processing and |
| cutting down our code. |
| |
| Write a simple form |
| =================== |
| |
| Let's update our poll detail template ("polls/detail.html") from the last |
| tutorial, so that the template contains an HTML ``<form>`` element: |
| |
| .. code-block:: html+django |
| |
| <h1>{{ poll.question }}</h1> |
| |
| {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} |
| |
| <form action="/polls/{{ poll.id }}/vote/" method="post"> |
| {% csrf_token %} |
| {% for choice in poll.choice_set.all %} |
| <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> |
| <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br /> |
| {% endfor %} |
| <input type="submit" value="Vote" /> |
| </form> |
| |
| A quick rundown: |
| |
| * The above template displays a radio button for each poll choice. The |
| ``value`` of each radio button is the associated poll choice's ID. The |
| ``name`` of each radio button is ``"choice"``. That means, when somebody |
| selects one of the radio buttons and submits the form, it'll send the |
| POST data ``choice=3``. This is HTML Forms 101. |
| |
| * We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we |
| set ``method="post"``. Using ``method="post"`` (as opposed to |
| ``method="get"``) is very important, because the act of submitting this |
| form will alter data server-side. Whenever you create a form that alters |
| data server-side, use ``method="post"``. This tip isn't specific to |
| Django; it's just good Web development practice. |
| |
| * ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone |
| through its loop |
| |
| * Since we're creating a POST form (which can have the effect of modifying |
| data), we need to worry about Cross Site Request Forgeries. |
| Thankfully, you don't have to worry too hard, because Django comes with |
| a very easy-to-use system for protecting against it. In short, all POST |
| forms that are targeted at internal URLs should use the ``{% csrf_token %}`` |
| template tag. |
| |
| The ``{% csrf_token %}`` tag requires information from the request object, which |
| is not normally accessible from within the template context. To fix this, a |
| small adjustment needs to be made to the ``detail`` view, so that it looks like |
| the following:: |
| |
| from django.template import RequestContext |
| # ... |
| def detail(request, poll_id): |
| p = get_object_or_404(Poll, pk=poll_id) |
| return render_to_response('polls/detail.html', {'poll': p}, |
| context_instance=RequestContext(request)) |
| |
| The details of how this works are explained in the documentation for |
| :ref:`RequestContext <subclassing-context-requestcontext>`. |
| |
| Now, let's create a Django view that handles the submitted data and does |
| something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we |
| created a URLconf for the polls application that includes this line:: |
| |
| (r'^(?P<poll_id>\d+)/vote/$', 'vote'), |
| |
| We also created a dummy implementation of the ``vote()`` function. Let's |
| create a real version. Add the following to ``polls/views.py``:: |
| |
| from django.shortcuts import get_object_or_404, render_to_response |
| from django.http import HttpResponseRedirect, HttpResponse |
| from django.core.urlresolvers import reverse |
| from django.template import RequestContext |
| from polls.models import Choice, Poll |
| # ... |
| def vote(request, poll_id): |
| p = get_object_or_404(Poll, pk=poll_id) |
| try: |
| selected_choice = p.choice_set.get(pk=request.POST['choice']) |
| except (KeyError, Choice.DoesNotExist): |
| # Redisplay the poll voting form. |
| return render_to_response('polls/detail.html', { |
| 'poll': p, |
| 'error_message': "You didn't select a choice.", |
| }, context_instance=RequestContext(request)) |
| else: |
| selected_choice.votes += 1 |
| selected_choice.save() |
| # Always return an HttpResponseRedirect after successfully dealing |
| # with POST data. This prevents data from being posted twice if a |
| # user hits the Back button. |
| return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,))) |
| |
| This code includes a few things we haven't covered yet in this tutorial: |
| |
| * :attr:`request.POST <django.http.HttpRequest.POST>` is a dictionary-like |
| object that lets you access submitted data by key name. In this case, |
| ``request.POST['choice']`` returns the ID of the selected choice, as a |
| string. :attr:`request.POST <django.http.HttpRequest.POST>` values are |
| always strings. |
| |
| Note that Django also provides :attr:`request.GET |
| <django.http.HttpRequest.GET>` for accessing GET data in the same way -- |
| but we're explicitly using :attr:`request.POST |
| <django.http.HttpRequest.POST>` in our code, to ensure that data is only |
| altered via a POST call. |
| |
| * ``request.POST['choice']`` will raise :exc:`KeyError` if ``choice`` wasn't |
| provided in POST data. The above code checks for :exc:`KeyError` and |
| redisplays the poll form with an error message if ``choice`` isn't given. |
| |
| * After incrementing the choice count, the code returns an |
| :class:`~django.http.HttpResponseRedirect` rather than a normal |
| :class:`~django.http.HttpResponse`. |
| :class:`~django.http.HttpResponseRedirect` takes a single argument: the |
| URL to which the user will be redirected (see the following point for how |
| we construct the URL in this case). |
| |
| As the Python comment above points out, you should always return an |
| :class:`~django.http.HttpResponseRedirect` after successfully dealing with |
| POST data. This tip isn't specific to Django; it's just good Web |
| development practice. |
| |
| * We are using the :func:`~django.core.urlresolvers.reverse` function in the |
| :class:`~django.http.HttpResponseRedirect` constructor in this example. |
| This function helps avoid having to hardcode a URL in the view function. |
| It is given the name of the view that we want to pass control to and the |
| variable portion of the URL pattern that points to that view. In this |
| case, using the URLconf we set up in Tutorial 3, this |
| :func:`~django.core.urlresolvers.reverse` call will return a string like |
| :: |
| |
| '/polls/3/results/' |
| |
| ... where the ``3`` is the value of ``p.id``. This redirected URL will |
| then call the ``'results'`` view to display the final page. Note that you |
| need to use the full name of the view here (including the prefix). |
| |
| As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest` |
| object. For more on :class:`~django.http.HttpRequest` objects, see the |
| :doc:`request and response documentation </ref/request-response>`. |
| |
| After somebody votes in a poll, the ``vote()`` view redirects to the results |
| page for the poll. Let's write that view:: |
| |
| def results(request, poll_id): |
| p = get_object_or_404(Poll, pk=poll_id) |
| return render_to_response('polls/results.html', {'poll': p}) |
| |
| This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3 |
| </intro/tutorial03>`. The only difference is the template name. We'll fix this |
| redundancy later. |
| |
| Now, create a ``results.html`` template: |
| |
| .. code-block:: html+django |
| |
| <h1>{{ poll.question }}</h1> |
| |
| <ul> |
| {% for choice in poll.choice_set.all %} |
| <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> |
| {% endfor %} |
| </ul> |
| |
| <a href="/polls/{{ poll.id }}/">Vote again?</a> |
| |
| Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a |
| results page that gets updated each time you vote. If you submit the form |
| without having chosen a choice, you should see the error message. |
| |
| Use generic views: Less code is better |
| ====================================== |
| |
| The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()`` |
| views are stupidly simple -- and, as mentioned above, redundant. The ``index()`` |
| view (also from Tutorial 3), which displays a list of polls, is similar. |
| |
| These views represent a common case of basic Web development: getting data from |
| the database according to a parameter passed in the URL, loading a template and |
| returning the rendered template. Because this is so common, Django provides a |
| shortcut, called the "generic views" system. |
| |
| Generic views abstract common patterns to the point where you don't even need |
| to write Python code to write an app. |
| |
| Let's convert our poll app to use the generic views system, so we can delete a |
| bunch of our own code. We'll just have to take a few steps to make the |
| conversion. We will: |
| |
| 1. Convert the URLconf. |
| |
| 2. Delete some of the old, unneeded views. |
| |
| 3. Fix up URL handling for the new views. |
| |
| Read on for details. |
| |
| .. admonition:: Why the code-shuffle? |
| |
| Generally, when writing a Django app, you'll evaluate whether generic views |
| are a good fit for your problem, and you'll use them from the beginning, |
| rather than refactoring your code halfway through. But this tutorial |
| intentionally has focused on writing the views "the hard way" until now, to |
| focus on core concepts. |
| |
| You should know basic math before you start using a calculator. |
| |
| First, open the ``polls/urls.py`` URLconf. It looks like this, according to the |
| tutorial so far:: |
| |
| from django.conf.urls.defaults import * |
| |
| urlpatterns = patterns('polls.views', |
| (r'^$', 'index'), |
| (r'^(?P<poll_id>\d+)/$', 'detail'), |
| (r'^(?P<poll_id>\d+)/results/$', 'results'), |
| (r'^(?P<poll_id>\d+)/vote/$', 'vote'), |
| ) |
| |
| Change it like so:: |
| |
| from django.conf.urls.defaults import * |
| from django.views.generic import DetailView, ListView |
| from polls.models import Poll |
| |
| urlpatterns = patterns('', |
| (r'^$', |
| ListView.as_view( |
| queryset=Poll.objects.order_by('-pub_date')[:5], |
| context_object_name='latest_poll_list', |
| template_name='polls/index.html')), |
| (r'^(?P<pk>\d+)/$', |
| DetailView.as_view( |
| model=Poll, |
| template_name='polls/detail.html')), |
| url(r'^(?P<pk>\d+)/results/$', |
| DetailView.as_view( |
| model=Poll, |
| template_name='polls/results.html'), |
| name='poll_results'), |
| (r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), |
| ) |
| |
| We're using two generic views here: |
| :class:`~django.views.generic.list.ListView` and |
| :class:`~django.views.generic.detail.DetailView`. Respectively, those |
| two views abstract the concepts of "display a list of objects" and |
| "display a detail page for a particular type of object." |
| |
| * Each generic view needs to know what model it will be acting |
| upon. This is provided using the ``model`` parameter. |
| |
| * The :class:`~django.views.generic.list.DetailView` generic view |
| expects the primary key value captured from the URL to be called |
| ``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic |
| views. |
| |
| * We've added a name, ``poll_results``, to the results view so |
| that we have a way to refer to its URL later on (see the |
| documentation about :ref:`naming URL patterns |
| <naming-url-patterns>` for information). We're also using the |
| :func:`~django.conf.urls.default.url` function from |
| :mod:`django.conf.urls.defaults` here. It's a good habit to use |
| :func:`~django.conf.urls.defaults.url` when you are providing a |
| pattern name like this. |
| |
| By default, the :class:`~django.views.generic.list.DetailView` generic |
| view uses a template called ``<app name>/<model name>_detail.html``. |
| In our case, it'll use the template ``"polls/poll_detail.html"``. The |
| ``template_name`` argument is used to tell Django to use a specific |
| template name instead of the autogenerated default template name. We |
| also specify the ``template_name`` for the ``results`` list view -- |
| this ensures that the results view and the detail view have a |
| different appearance when rendered, even though they're both a |
| :class:`~django.views.generic.list.DetailView` behind the scenes. |
| |
| Similarly, the :class:`~django.views.generic.list.ListView` generic |
| view uses a default template called ``<app name>/<model |
| name>_list.html``; we use ``template_name`` to tell |
| :class:`~django.views.generic.list.ListView` to use our existing |
| ``"polls/index.html"`` template. |
| |
| In previous parts of the tutorial, the templates have been provided |
| with a context that contains the ``poll`` and ``latest_poll_list`` |
| context variables. For DetailView the ``poll`` variable is provided |
| automatically -- since we're using a Django model (``Poll``), Django |
| is able to determine an appropriate name for the context variable. |
| However, for ListView, the automatically generated context variable is |
| ``poll_list``. To override this we provide the ``context_object_name`` |
| option, specifying that we want to use ``latest_poll_list`` instead. |
| As an alternative approach, you could change your templates to match |
| the new default context variables -- but it's a lot easier to just |
| tell Django to use the variable you want. |
| |
| You can now delete the ``index()``, ``detail()`` and ``results()`` |
| views from ``polls/views.py``. We don't need them anymore -- they have |
| been replaced by generic views. |
| |
| The last thing to do is fix the URL handling to account for the use of |
| generic views. In the vote view above, we used the |
| :func:`~django.core.urlresolvers.reverse` function to avoid |
| hard-coding our URLs. Now that we've switched to a generic view, we'll |
| need to change the :func:`~django.core.urlresolvers.reverse` call to |
| point back to our new generic view. We can't simply use the view |
| function anymore -- generic views can be (and are) used multiple times |
| -- but we can use the name we've given:: |
| |
| return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) |
| |
| Run the server, and use your new polling app based on generic views. |
| |
| For full details on generic views, see the :doc:`generic views documentation |
| </topics/http/generic-views>`. |
| |
| Coming soon |
| =========== |
| |
| The tutorial ends here for the time being. Future installments of the tutorial |
| will cover: |
| |
| * Advanced form processing |
| * Using the RSS framework |
| * Using the cache framework |
| * Using the comments framework |
| * Advanced admin features: Permissions |
| * Advanced admin features: Custom JavaScript |
| |
| In the meantime, you might want to check out some pointers on :doc:`where to go |
| from here </intro/whatsnext>` |