| ===================================== |
| 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. Rename a few templates. |
| |
| 3. Delete some of the old, unneeded views. |
| |
| 4. 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 polls.models import Poll |
| |
| info_dict = { |
| 'queryset': Poll.objects.all(), |
| } |
| |
| urlpatterns = patterns('', |
| (r'^$', 'django.views.generic.list_detail.object_list', info_dict), |
| (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), |
| url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), |
| (r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), |
| ) |
| |
| We're using two generic views here: |
| :func:`~django.views.generic.list_detail.object_list` and |
| :func:`~django.views.generic.list_detail.object_detail`. 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 data it will be acting upon. This |
| data is provided in a dictionary. The ``queryset`` key in this dictionary |
| points to the list of objects to be manipulated by the generic view. |
| |
| * The :func:`~django.views.generic.list_detail.object_detail` generic view |
| expects the ID value captured from the URL to be called ``"object_id"``, |
| so we've changed ``poll_id`` to ``object_id`` 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 :func:`~django.views.generic.list_detail.object_detail` generic |
| view uses a template called ``<app name>/<model name>_detail.html``. In our |
| case, it'll use the template ``"polls/poll_detail.html"``. Thus, rename your |
| ``polls/detail.html`` template to ``polls/poll_detail.html``, and change the |
| :func:`~django.shortcuts.render_to_response` line in ``vote()``. |
| |
| Similarly, the :func:`~django.views.generic.list_detail.object_list` generic |
| view uses a template called ``<app name>/<model name>_list.html``. Thus, rename |
| ``polls/index.html`` to ``polls/poll_list.html``. |
| |
| Because we have more than one entry in the URLconf that uses |
| :func:`~django.views.generic.list_detail.object_detail` for the polls app, we |
| manually specify a template name for the results view: |
| ``template_name='polls/results.html'``. Otherwise, both views would use the same |
| template. Note that we use ``dict()`` to return an altered dictionary in place. |
| |
| .. note:: :meth:`django.db.models.QuerySet.all` is lazy |
| |
| It might look a little frightening to see ``Poll.objects.all()`` being used |
| in a detail view which only needs one ``Poll`` object, but don't worry; |
| ``Poll.objects.all()`` is actually a special object called a |
| :class:`~django.db.models.QuerySet`, which is "lazy" and doesn't hit your |
| database until it absolutely has to. By the time the database query happens, |
| the :func:`~django.views.generic.list_detail.object_detail` generic view |
| will have narrowed its scope down to a single object, so the eventual query |
| will only select one row from the database. |
| |
| If you'd like to know more about how that works, The Django database API |
| documentation :ref:`explains the lazy nature of QuerySet objects |
| <querysets-are-lazy>`. |
| |
| In previous parts of the tutorial, the templates have been provided with a |
| context that contains the ``poll`` and ``latest_poll_list`` context variables. |
| However, the generic views provide the variables ``object`` and ``object_list`` |
| as context. Therefore, you need to change your templates to match the new |
| context variables. Go through your templates, and modify any reference to |
| ``latest_poll_list`` to ``object_list``, and change any reference to ``poll`` |
| to ``object``. |
| |
| 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 ``vote()`` view is still required. However, it must be modified to match the |
| new context variables. In the :func:`~django.shortcuts.render_to_response` call, |
| rename the ``poll`` context variable to ``object``. |
| |
| 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>` |