2
0

tutorial04.txt 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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.db.models import F
  60. from django.http import HttpResponse, HttpResponseRedirect
  61. from django.shortcuts import get_object_or_404, render
  62. from django.urls import reverse
  63. from .models import Choice, Question
  64. # ...
  65. def vote(request, question_id):
  66. question = get_object_or_404(Question, pk=question_id)
  67. try:
  68. selected_choice = question.choice_set.get(pk=request.POST["choice"])
  69. except (KeyError, Choice.DoesNotExist):
  70. # Redisplay the question voting form.
  71. return render(
  72. request,
  73. "polls/detail.html",
  74. {
  75. "question": question,
  76. "error_message": "You didn't select a choice.",
  77. },
  78. )
  79. else:
  80. selected_choice.votes = F("votes") + 1
  81. selected_choice.save()
  82. # Always return an HttpResponseRedirect after successfully dealing
  83. # with POST data. This prevents data from being posted twice if a
  84. # user hits the Back button.
  85. return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
  86. This code includes a few things we haven't covered yet in this tutorial:
  87. * :attr:`request.POST <django.http.HttpRequest.POST>` is a dictionary-like
  88. object that lets you access submitted data by key name. In this case,
  89. ``request.POST['choice']`` returns the ID of the selected choice, as a
  90. string. :attr:`request.POST <django.http.HttpRequest.POST>` values are
  91. always strings.
  92. Note that Django also provides :attr:`request.GET
  93. <django.http.HttpRequest.GET>` for accessing GET data in the same way --
  94. but we're explicitly using :attr:`request.POST
  95. <django.http.HttpRequest.POST>` in our code, to ensure that data is only
  96. altered via a POST call.
  97. * ``request.POST['choice']`` will raise :exc:`KeyError` if
  98. ``choice`` wasn't provided in POST data. The above code checks for
  99. :exc:`KeyError` and redisplays the question form with an error
  100. message if ``choice`` isn't given.
  101. * ``F("votes") + 1`` :ref:`instructs the database
  102. <avoiding-race-conditions-using-f>` to increase the vote count by 1.
  103. * After incrementing the choice count, the code returns an
  104. :class:`~django.http.HttpResponseRedirect` rather than a normal
  105. :class:`~django.http.HttpResponse`.
  106. :class:`~django.http.HttpResponseRedirect` takes a single argument: the
  107. URL to which the user will be redirected (see the following point for how
  108. we construct the URL in this case).
  109. As the Python comment above points out, you should always return an
  110. :class:`~django.http.HttpResponseRedirect` after successfully dealing with
  111. POST data. This tip isn't specific to Django; it's good web development
  112. practice in general.
  113. * We are using the :func:`~django.urls.reverse` function in the
  114. :class:`~django.http.HttpResponseRedirect` constructor in this example.
  115. This function helps avoid having to hardcode a URL in the view function.
  116. It is given the name of the view that we want to pass control to and the
  117. variable portion of the URL pattern that points to that view. In this
  118. case, using the URLconf we set up in :doc:`Tutorial 3 </intro/tutorial03>`,
  119. this :func:`~django.urls.reverse` call will return a string like
  120. ::
  121. "/polls/3/results/"
  122. where the ``3`` is the value of ``question.id``. This redirected URL will
  123. then call the ``'results'`` view to display the final page.
  124. As mentioned in :doc:`Tutorial 3 </intro/tutorial03>`, ``request`` is an
  125. :class:`~django.http.HttpRequest` object. For more on
  126. :class:`~django.http.HttpRequest` objects, see the :doc:`request and
  127. response documentation </ref/request-response>`.
  128. After somebody votes in a question, the ``vote()`` view redirects to the results
  129. page for the question. Let's write that view:
  130. .. code-block:: python
  131. :caption: ``polls/views.py``
  132. from django.shortcuts import get_object_or_404, render
  133. def results(request, question_id):
  134. question = get_object_or_404(Question, pk=question_id)
  135. return render(request, "polls/results.html", {"question": question})
  136. This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3
  137. </intro/tutorial03>`. The only difference is the template name. We'll fix this
  138. redundancy later.
  139. Now, create a ``polls/results.html`` template:
  140. .. code-block:: html+django
  141. :caption: ``polls/templates/polls/results.html``
  142. <h1>{{ question.question_text }}</h1>
  143. <ul>
  144. {% for choice in question.choice_set.all %}
  145. <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
  146. {% endfor %}
  147. </ul>
  148. <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
  149. Now, go to ``/polls/1/`` in your browser and vote in the question. You should see a
  150. results page that gets updated each time you vote. If you submit the form
  151. without having chosen a choice, you should see the error message.
  152. Use generic views: Less code is better
  153. ======================================
  154. The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()``
  155. views are very short -- and, as mentioned above, redundant. The ``index()``
  156. view, which displays a list of polls, is similar.
  157. These views represent a common case of basic web development: getting data from
  158. the database according to a parameter passed in the URL, loading a template and
  159. returning the rendered template. Because this is so common, Django provides a
  160. shortcut, called the "generic views" system.
  161. Generic views abstract common patterns to the point where you don't even need to
  162. write Python code to write an app. For example, the
  163. :class:`~django.views.generic.list.ListView` and
  164. :class:`~django.views.generic.detail.DetailView` generic views
  165. abstract the concepts of "display a list of objects" and
  166. "display a detail page for a particular type of object" respectively.
  167. Let's convert our poll app to use the generic views system, so we can delete a
  168. bunch of our own code. We'll have to take a few steps to make the conversion.
  169. We will:
  170. #. Convert the URLconf.
  171. #. Delete some of the old, unneeded views.
  172. #. Introduce new views based on Django's generic views.
  173. Read on for details.
  174. .. admonition:: Why the code-shuffle?
  175. Generally, when writing a Django app, you'll evaluate whether generic views
  176. are a good fit for your problem, and you'll use them from the beginning,
  177. rather than refactoring your code halfway through. But this tutorial
  178. intentionally has focused on writing the views "the hard way" until now, to
  179. focus on core concepts.
  180. You should know basic math before you start using a calculator.
  181. Amend URLconf
  182. -------------
  183. First, open the ``polls/urls.py`` URLconf and change it like so:
  184. .. code-block:: python
  185. :caption: ``polls/urls.py``
  186. from django.urls import path
  187. from . import views
  188. app_name = "polls"
  189. urlpatterns = [
  190. path("", views.IndexView.as_view(), name="index"),
  191. path("<int:pk>/", views.DetailView.as_view(), name="detail"),
  192. path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
  193. path("<int:question_id>/vote/", views.vote, name="vote"),
  194. ]
  195. Note that the name of the matched pattern in the path strings of the second and
  196. third patterns has changed from ``<question_id>`` to ``<pk>``. This is
  197. necessary because we'll use the
  198. :class:`~django.views.generic.detail.DetailView` generic view to replace our
  199. ``detail()`` and ``results()`` views, and it expects the primary key value
  200. captured from the URL to be called ``"pk"``.
  201. Amend views
  202. -----------
  203. Next, we're going to remove our old ``index``, ``detail``, and ``results``
  204. views and use Django's generic views instead. To do so, open the
  205. ``polls/views.py`` file and change it like so:
  206. .. code-block:: python
  207. :caption: ``polls/views.py``
  208. from django.db.models import F
  209. from django.http import HttpResponseRedirect
  210. from django.shortcuts import get_object_or_404, render
  211. from django.urls import reverse
  212. from django.views import generic
  213. from .models import Choice, Question
  214. class IndexView(generic.ListView):
  215. template_name = "polls/index.html"
  216. context_object_name = "latest_question_list"
  217. def get_queryset(self):
  218. """Return the last five published questions."""
  219. return Question.objects.order_by("-pub_date")[:5]
  220. class DetailView(generic.DetailView):
  221. model = Question
  222. template_name = "polls/detail.html"
  223. class ResultsView(generic.DetailView):
  224. model = Question
  225. template_name = "polls/results.html"
  226. def vote(request, question_id):
  227. # same as above, no changes needed.
  228. ...
  229. Each generic view needs to know what model it will be acting upon. This is
  230. provided using either the ``model`` attribute (in this example, ``model =
  231. Question`` for ``DetailView`` and ``ResultsView``) or by defining the
  232. :meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset` method (as
  233. shown in ``IndexView``).
  234. By default, the :class:`~django.views.generic.detail.DetailView` generic
  235. view uses a template called ``<app name>/<model name>_detail.html``.
  236. In our case, it would use the template ``"polls/question_detail.html"``. The
  237. ``template_name`` attribute is used to tell Django to use a specific
  238. template name instead of the autogenerated default template name. We
  239. also specify the ``template_name`` for the ``results`` list view --
  240. this ensures that the results view and the detail view have a
  241. different appearance when rendered, even though they're both a
  242. :class:`~django.views.generic.detail.DetailView` behind the scenes.
  243. Similarly, the :class:`~django.views.generic.list.ListView` generic
  244. view uses a default template called ``<app name>/<model
  245. name>_list.html``; we use ``template_name`` to tell
  246. :class:`~django.views.generic.list.ListView` to use our existing
  247. ``"polls/index.html"`` template.
  248. In previous parts of the tutorial, the templates have been provided
  249. with a context that contains the ``question`` and ``latest_question_list``
  250. context variables. For ``DetailView`` the ``question`` variable is provided
  251. automatically -- since we're using a Django model (``Question``), Django
  252. is able to determine an appropriate name for the context variable.
  253. However, for ListView, the automatically generated context variable is
  254. ``question_list``. To override this we provide the ``context_object_name``
  255. attribute, specifying that we want to use ``latest_question_list`` instead.
  256. As an alternative approach, you could change your templates to match
  257. the new default context variables -- but it's a lot easier to tell Django to
  258. use the variable you want.
  259. Run the server, and use your new polling app based on generic views.
  260. For full details on generic views, see the :doc:`generic views documentation
  261. </topics/class-based-views/index>`.
  262. When you're comfortable with forms and generic views, read :doc:`part 5 of this
  263. tutorial</intro/tutorial05>` to learn about testing our polls app.