tutorial04.txt 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. =====================================
  2. Writing your first Django app, part 4
  3. =====================================
  4. This tutorial begins where :doc:`Tutorial 3 </intro/tutorial03>` left off. We're
  5. continuing the web-poll application and will focus on form processing and
  6. cutting down our code.
  7. .. admonition:: Where to get help:
  8. If you're having trouble going through this tutorial, please head over to
  9. the :doc:`Getting Help</faq/help>` section of the FAQ.
  10. Write a minimal form
  11. ====================
  12. Let's update our poll detail template ("polls/detail.html") from the last
  13. tutorial, so that the template contains an HTML ``<form>`` element:
  14. .. code-block:: html+django
  15. :caption: ``polls/templates/polls/detail.html``
  16. <form action="{% url 'polls:vote' question.id %}" method="post">
  17. {% csrf_token %}
  18. <fieldset>
  19. <legend><h1>{{ question.question_text }}</h1></legend>
  20. {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
  21. {% for choice in question.choice_set.all %}
  22. <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
  23. <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
  24. {% endfor %}
  25. </fieldset>
  26. <input type="submit" value="Vote">
  27. </form>
  28. A quick rundown:
  29. * The above template displays a radio button for each question choice. The
  30. ``value`` of each radio button is the associated question choice's ID. The
  31. ``name`` of each radio button is ``"choice"``. That means, when somebody
  32. selects one of the radio buttons and submits the form, it'll send the
  33. POST data ``choice=#`` where # is the ID of the selected choice. This is the
  34. basic concept of HTML forms.
  35. * We set the form's ``action`` to ``{% url 'polls:vote' question.id %}``, and we
  36. set ``method="post"``. Using ``method="post"`` (as opposed to
  37. ``method="get"``) is very important, because the act of submitting this
  38. form will alter data server-side. Whenever you create a form that alters
  39. data server-side, use ``method="post"``. This tip isn't specific to
  40. Django; it's good web development practice in general.
  41. * ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone
  42. through its loop
  43. * Since we're creating a POST form (which can have the effect of modifying
  44. data), we need to worry about Cross Site Request Forgeries.
  45. Thankfully, you don't have to worry too hard, because Django comes with a
  46. helpful system for protecting against it. In short, all POST forms that are
  47. targeted at internal URLs should use the :ttag:`{% csrf_token %}<csrf_token>`
  48. template tag.
  49. Now, let's create a Django view that handles the submitted data and does
  50. something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
  51. created a URLconf for the polls application that includes this line:
  52. .. code-block:: python
  53. :caption: ``polls/urls.py``
  54. path("<int:question_id>/vote/", views.vote, name="vote"),
  55. We also created a dummy implementation of the ``vote()`` function. Let's
  56. create a real version. Add the following to ``polls/views.py``:
  57. .. code-block:: python
  58. :caption: ``polls/views.py``
  59. from django.http import HttpResponse, HttpResponseRedirect
  60. from django.shortcuts import get_object_or_404, render
  61. from django.urls import reverse
  62. from .models import Choice, Question
  63. # ...
  64. def vote(request, question_id):
  65. question = get_object_or_404(Question, pk=question_id)
  66. try:
  67. selected_choice = question.choice_set.get(pk=request.POST["choice"])
  68. except (KeyError, Choice.DoesNotExist):
  69. # Redisplay the question voting form.
  70. return render(
  71. request,
  72. "polls/detail.html",
  73. {
  74. "question": question,
  75. "error_message": "You didn't select a choice.",
  76. },
  77. )
  78. else:
  79. selected_choice.votes += 1
  80. selected_choice.save()
  81. # Always return an HttpResponseRedirect after successfully dealing
  82. # with POST data. This prevents data from being posted twice if a
  83. # user hits the Back button.
  84. return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
  85. This code includes a few things we haven't covered yet in this tutorial:
  86. * :attr:`request.POST <django.http.HttpRequest.POST>` is a dictionary-like
  87. object that lets you access submitted data by key name. In this case,
  88. ``request.POST['choice']`` returns the ID of the selected choice, as a
  89. string. :attr:`request.POST <django.http.HttpRequest.POST>` values are
  90. always strings.
  91. Note that Django also provides :attr:`request.GET
  92. <django.http.HttpRequest.GET>` for accessing GET data in the same way --
  93. but we're explicitly using :attr:`request.POST
  94. <django.http.HttpRequest.POST>` in our code, to ensure that data is only
  95. altered via a POST call.
  96. * ``request.POST['choice']`` will raise :exc:`KeyError` if
  97. ``choice`` wasn't provided in POST data. The above code checks for
  98. :exc:`KeyError` and redisplays the question form with an error
  99. message if ``choice`` isn't given.
  100. * After incrementing the choice count, the code returns an
  101. :class:`~django.http.HttpResponseRedirect` rather than a normal
  102. :class:`~django.http.HttpResponse`.
  103. :class:`~django.http.HttpResponseRedirect` takes a single argument: the
  104. URL to which the user will be redirected (see the following point for how
  105. we construct the URL in this case).
  106. As the Python comment above points out, you should always return an
  107. :class:`~django.http.HttpResponseRedirect` after successfully dealing with
  108. POST data. This tip isn't specific to Django; it's good web development
  109. practice in general.
  110. * We are using the :func:`~django.urls.reverse` function in the
  111. :class:`~django.http.HttpResponseRedirect` constructor in this example.
  112. This function helps avoid having to hardcode a URL in the view function.
  113. It is given the name of the view that we want to pass control to and the
  114. variable portion of the URL pattern that points to that view. In this
  115. case, using the URLconf we set up in :doc:`Tutorial 3 </intro/tutorial03>`,
  116. this :func:`~django.urls.reverse` call will return a string like
  117. ::
  118. "/polls/3/results/"
  119. where the ``3`` is the value of ``question.id``. This redirected URL will
  120. then call the ``'results'`` view to display the final page.
  121. As mentioned in :doc:`Tutorial 3 </intro/tutorial03>`, ``request`` is an
  122. :class:`~django.http.HttpRequest` object. For more on
  123. :class:`~django.http.HttpRequest` objects, see the :doc:`request and
  124. response documentation </ref/request-response>`.
  125. After somebody votes in a question, the ``vote()`` view redirects to the results
  126. page for the question. Let's write that view:
  127. .. code-block:: python
  128. :caption: ``polls/views.py``
  129. from django.shortcuts import get_object_or_404, render
  130. def results(request, question_id):
  131. question = get_object_or_404(Question, pk=question_id)
  132. return render(request, "polls/results.html", {"question": question})
  133. This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
  134. </intro/tutorial03>`. The only difference is the template name. We'll fix this
  135. redundancy later.
  136. Now, create a ``polls/results.html`` template:
  137. .. code-block:: html+django
  138. :caption: ``polls/templates/polls/results.html``
  139. <h1>{{ question.question_text }}</h1>
  140. <ul>
  141. {% for choice in question.choice_set.all %}
  142. <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
  143. {% endfor %}
  144. </ul>
  145. <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
  146. Now, go to ``/polls/1/`` in your browser and vote in the question. You should see a
  147. results page that gets updated each time you vote. If you submit the form
  148. without having chosen a choice, you should see the error message.
  149. .. note::
  150. The code for our ``vote()`` view does have a small problem. It first gets
  151. the ``selected_choice`` object from the database, then computes the new
  152. value of ``votes``, and then saves it back to the database. If two users of
  153. your website try to vote at *exactly the same time*, this might go wrong:
  154. The same value, let's say 42, will be retrieved for ``votes``. Then, for
  155. both users the new value of 43 is computed and saved, but 44 would be the
  156. expected value.
  157. This is called a *race condition*. If you are interested, you can read
  158. :ref:`avoiding-race-conditions-using-f` to learn how you can solve this
  159. issue.
  160. Use generic views: Less code is better
  161. ======================================
  162. The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()``
  163. views are very short -- and, as mentioned above, redundant. The ``index()``
  164. view, which displays a list of polls, is similar.
  165. These views represent a common case of basic web development: getting data from
  166. the database according to a parameter passed in the URL, loading a template and
  167. returning the rendered template. Because this is so common, Django provides a
  168. shortcut, called the "generic views" system.
  169. Generic views abstract common patterns to the point where you don't even need to
  170. write Python code to write an app. For example, the
  171. :class:`~django.views.generic.list.ListView` and
  172. :class:`~django.views.generic.detail.DetailView` generic views
  173. abstract the concepts of "display a list of objects" and
  174. "display a detail page for a particular type of object" respectively.
  175. Let's convert our poll app to use the generic views system, so we can delete a
  176. bunch of our own code. We'll have to take a few steps to make the conversion.
  177. We will:
  178. #. Convert the URLconf.
  179. #. Delete some of the old, unneeded views.
  180. #. Introduce new views based on Django's generic views.
  181. Read on for details.
  182. .. admonition:: Why the code-shuffle?
  183. Generally, when writing a Django app, you'll evaluate whether generic views
  184. are a good fit for your problem, and you'll use them from the beginning,
  185. rather than refactoring your code halfway through. But this tutorial
  186. intentionally has focused on writing the views "the hard way" until now, to
  187. focus on core concepts.
  188. You should know basic math before you start using a calculator.
  189. Amend URLconf
  190. -------------
  191. First, open the ``polls/urls.py`` URLconf and change it like so:
  192. .. code-block:: python
  193. :caption: ``polls/urls.py``
  194. from django.urls import path
  195. from . import views
  196. app_name = "polls"
  197. urlpatterns = [
  198. path("", views.IndexView.as_view(), name="index"),
  199. path("<int:pk>/", views.DetailView.as_view(), name="detail"),
  200. path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
  201. path("<int:question_id>/vote/", views.vote, name="vote"),
  202. ]
  203. Note that the name of the matched pattern in the path strings of the second and
  204. third patterns has changed from ``<question_id>`` to ``<pk>``. This is
  205. necessary because we'll use the
  206. :class:`~django.views.generic.detail.DetailView` generic view to replace our
  207. ``detail()`` and ``results()`` views, and it expects the primary key value
  208. captured from the URL to be called ``"pk"``.
  209. Amend views
  210. -----------
  211. Next, we're going to remove our old ``index``, ``detail``, and ``results``
  212. views and use Django's generic views instead. To do so, open the
  213. ``polls/views.py`` file and change it like so:
  214. .. code-block:: python
  215. :caption: ``polls/views.py``
  216. from django.http import HttpResponseRedirect
  217. from django.shortcuts import get_object_or_404, render
  218. from django.urls import reverse
  219. from django.views import generic
  220. from .models import Choice, Question
  221. class IndexView(generic.ListView):
  222. template_name = "polls/index.html"
  223. context_object_name = "latest_question_list"
  224. def get_queryset(self):
  225. """Return the last five published questions."""
  226. return Question.objects.order_by("-pub_date")[:5]
  227. class DetailView(generic.DetailView):
  228. model = Question
  229. template_name = "polls/detail.html"
  230. class ResultsView(generic.DetailView):
  231. model = Question
  232. template_name = "polls/results.html"
  233. def vote(request, question_id):
  234. ... # same as above, no changes needed.
  235. Each generic view needs to know what model it will be acting upon. This is
  236. provided using either the ``model`` attribute (in this example, ``model =
  237. Question`` for ``DetailView`` and ``ResultsView``) or by defining the
  238. :meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset` method (as
  239. shown in ``IndexView``).
  240. By default, the :class:`~django.views.generic.detail.DetailView` generic
  241. view uses a template called ``<app name>/<model name>_detail.html``.
  242. In our case, it would use the template ``"polls/question_detail.html"``. The
  243. ``template_name`` attribute is used to tell Django to use a specific
  244. template name instead of the autogenerated default template name. We
  245. also specify the ``template_name`` for the ``results`` list view --
  246. this ensures that the results view and the detail view have a
  247. different appearance when rendered, even though they're both a
  248. :class:`~django.views.generic.detail.DetailView` behind the scenes.
  249. Similarly, the :class:`~django.views.generic.list.ListView` generic
  250. view uses a default template called ``<app name>/<model
  251. name>_list.html``; we use ``template_name`` to tell
  252. :class:`~django.views.generic.list.ListView` to use our existing
  253. ``"polls/index.html"`` template.
  254. In previous parts of the tutorial, the templates have been provided
  255. with a context that contains the ``question`` and ``latest_question_list``
  256. context variables. For ``DetailView`` the ``question`` variable is provided
  257. automatically -- since we're using a Django model (``Question``), Django
  258. is able to determine an appropriate name for the context variable.
  259. However, for ListView, the automatically generated context variable is
  260. ``question_list``. To override this we provide the ``context_object_name``
  261. attribute, specifying that we want to use ``latest_question_list`` instead.
  262. As an alternative approach, you could change your templates to match
  263. the new default context variables -- but it's a lot easier to tell Django to
  264. use the variable you want.
  265. Run the server, and use your new polling app based on generic views.
  266. For full details on generic views, see the :doc:`generic views documentation
  267. </topics/class-based-views/index>`.
  268. When you're comfortable with forms and generic views, read :doc:`part 5 of this
  269. tutorial</intro/tutorial05>` to learn about testing our polls app.