123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- =====================================
- Writing your first Django app, part 3
- =====================================
- This tutorial begins where :doc:`Tutorial 2 </intro/tutorial02>` left off. We're
- continuing the web-poll application and will focus on creating the public
- interface -- "views."
- .. admonition:: Where to get help:
- If you're having trouble going through this tutorial, please head over to
- the :doc:`Getting Help</faq/help>` section of the FAQ.
- Overview
- ========
- A view is a "type" of web page in your Django application that generally serves
- a specific function and has a specific template. For example, in a blog
- application, you might have the following views:
- * Blog homepage -- displays the latest few entries.
- * Entry "detail" page -- permalink page for a single entry.
- * Year-based archive page -- displays all months with entries in the
- given year.
- * Month-based archive page -- displays all days with entries in the
- given month.
- * Day-based archive page -- displays all entries in the given day.
- * Comment action -- handles posting comments to a given entry.
- In our poll application, we'll have the following four views:
- * Question "index" page -- displays the latest few questions.
- * Question "detail" page -- displays a question text, with no results but
- with a form to vote.
- * Question "results" page -- displays results for a particular question.
- * Vote action -- handles voting for a particular choice in a particular
- question.
- In Django, web pages and other content are delivered by views. Each view is
- represented by a Python function (or method, in the case of class-based views).
- Django will choose a view by examining the URL that's requested (to be precise,
- the part of the URL after the domain name).
- Now in your time on the web you may have come across such beauties as
- ``ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B``.
- You will be pleased to know that Django allows us much more elegant
- *URL patterns* than that.
- A URL pattern is the general form of a URL - for example:
- ``/newsarchive/<year>/<month>/``.
- To get from a URL to a view, Django uses what are known as 'URLconfs'. A
- URLconf maps URL patterns to views.
- This tutorial provides basic instruction in the use of URLconfs, and you can
- refer to :doc:`/topics/http/urls` for more information.
- Writing more views
- ==================
- Now let's add a few more views to ``polls/views.py``. These views are
- slightly different, because they take an argument:
- .. code-block:: python
- :caption: ``polls/views.py``
- def detail(request, question_id):
- return HttpResponse("You're looking at question %s." % question_id)
- def results(request, question_id):
- response = "You're looking at the results of question %s."
- return HttpResponse(response % question_id)
- def vote(request, question_id):
- return HttpResponse("You're voting on question %s." % question_id)
- Wire these new views into the ``polls.urls`` module by adding the following
- :func:`~django.urls.path` calls:
- .. code-block:: python
- :caption: ``polls/urls.py``
- from django.urls import path
- from . import views
- urlpatterns = [
- # ex: /polls/
- path("", views.index, name="index"),
- # ex: /polls/5/
- path("<int:question_id>/", views.detail, name="detail"),
- # ex: /polls/5/results/
- path("<int:question_id>/results/", views.results, name="results"),
- # ex: /polls/5/vote/
- path("<int:question_id>/vote/", views.vote, name="vote"),
- ]
- Take a look in your browser, at "/polls/34/". It'll run the ``detail()``
- function and display whatever ID you provide in the URL. Try
- "/polls/34/results/" and "/polls/34/vote/" too -- these will display the
- placeholder results and voting pages.
- When somebody requests a page from your website -- say, "/polls/34/", Django
- will load the ``mysite.urls`` Python module because it's pointed to by the
- :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
- and traverses the patterns in order. After finding the match at ``'polls/'``,
- it strips off the matching text (``"polls/"``) and sends the remaining text --
- ``"34/"`` -- to the 'polls.urls' URLconf for further processing. There it
- matches ``'<int:question_id>/'``, resulting in a call to the ``detail()`` view
- like so:
- .. code-block:: pycon
- detail(request=<HttpRequest object>, question_id=34)
- The ``question_id=34`` part comes from ``<int:question_id>``. Using angle
- brackets "captures" part of the URL and sends it as a keyword argument to the
- view function. The ``question_id`` part of the string defines the name that
- will be used to identify the matched pattern, and the ``int`` part is a
- converter that determines what patterns should match this part of the URL path.
- The colon (``:``) separates the converter and pattern name.
- Write views that actually do something
- ======================================
- Each view is responsible for doing one of two things: returning an
- :class:`~django.http.HttpResponse` object containing the content for the
- requested page, or raising an exception such as :exc:`~django.http.Http404`. The
- rest is up to you.
- Your view can read records from a database, or not. It can use a template
- system such as Django's -- or a third-party Python template system -- or not.
- It can generate a PDF file, output XML, create a ZIP file on the fly, anything
- you want, using whatever Python libraries you want.
- All Django wants is that :class:`~django.http.HttpResponse`. Or an exception.
- Because it's convenient, let's use Django's own database API, which we covered
- in :doc:`Tutorial 2 </intro/tutorial02>`. Here's one stab at a new ``index()``
- view, which displays the latest 5 poll questions in the system, separated by
- commas, according to publication date:
- .. code-block:: python
- :caption: ``polls/views.py``
- from django.http import HttpResponse
- from .models import Question
- def index(request):
- latest_question_list = Question.objects.order_by("-pub_date")[:5]
- output = ", ".join([q.question_text for q in latest_question_list])
- return HttpResponse(output)
- # Leave the rest of the views (detail, results, vote) unchanged
- There's a problem here, though: the page's design is hard-coded in the view. If
- you want to change the way the page looks, you'll have to edit this Python code.
- So let's use Django's template system to separate the design from Python by
- creating a template that the view can use.
- First, create a directory called ``templates`` in your ``polls`` directory.
- Django will look for templates in there.
- Your project's :setting:`TEMPLATES` setting describes how Django will load and
- render templates. The default settings file configures a ``DjangoTemplates``
- backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
- ``True``. By convention ``DjangoTemplates`` looks for a "templates"
- subdirectory in each of the :setting:`INSTALLED_APPS`.
- Within the ``templates`` directory you have just created, create another
- directory called ``polls``, and within that create a file called
- ``index.html``. In other words, your template should be at
- ``polls/templates/polls/index.html``. Because of how the ``app_directories``
- template loader works as described above, you can refer to this template within
- Django as ``polls/index.html``.
- .. admonition:: Template namespacing
- Now we *might* be able to get away with putting our templates directly in
- ``polls/templates`` (rather than creating another ``polls`` subdirectory),
- but it would actually be a bad idea. Django will choose the first template
- it finds whose name matches, and if you had a template with the same name
- in a *different* application, Django would be unable to distinguish between
- them. We need to be able to point Django at the right one, and the best
- way to ensure this is by *namespacing* them. That is, by putting those
- templates inside *another* directory named for the application itself.
- Put the following code in that template:
- .. code-block:: html+django
- :caption: ``polls/templates/polls/index.html``
- {% if latest_question_list %}
- <ul>
- {% for question in latest_question_list %}
- <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
- {% endfor %}
- </ul>
- {% else %}
- <p>No polls are available.</p>
- {% endif %}
- .. note::
- To make the tutorial shorter, all template examples use incomplete HTML. In
- your own projects you should use `complete HTML documents`__.
- __ https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#anatomy_of_an_html_document
- Now let's update our ``index`` view in ``polls/views.py`` to use the template:
- .. code-block:: python
- :caption: ``polls/views.py``
- from django.http import HttpResponse
- from django.template import loader
- from .models import Question
- def index(request):
- latest_question_list = Question.objects.order_by("-pub_date")[:5]
- template = loader.get_template("polls/index.html")
- context = {
- "latest_question_list": latest_question_list,
- }
- return HttpResponse(template.render(context, request))
- That code loads the template called ``polls/index.html`` and passes it a
- context. The context is a dictionary mapping template variable names to Python
- objects.
- Load the page by pointing your browser at "/polls/", and you should see a
- bulleted-list containing the "What's up" question from :doc:`Tutorial 2
- </intro/tutorial02>`. The link points to the question's detail page.
- A shortcut: :func:`~django.shortcuts.render`
- --------------------------------------------
- It's a very common idiom to load a template, fill a context and return an
- :class:`~django.http.HttpResponse` object with the result of the rendered
- template. Django provides a shortcut. Here's the full ``index()`` view,
- rewritten:
- .. code-block:: python
- :caption: ``polls/views.py``
- from django.shortcuts import render
- from .models import Question
- def index(request):
- latest_question_list = Question.objects.order_by("-pub_date")[:5]
- context = {"latest_question_list": latest_question_list}
- return render(request, "polls/index.html", context)
- Note that once we've done this in all these views, we no longer need to import
- :mod:`~django.template.loader` and :class:`~django.http.HttpResponse` (you'll
- want to keep ``HttpResponse`` if you still have the stub methods for ``detail``,
- ``results``, and ``vote``).
- The :func:`~django.shortcuts.render` function takes the request object as its
- first argument, a template name as its second argument and a dictionary as its
- optional third argument. It returns an :class:`~django.http.HttpResponse`
- object of the given template rendered with the given context.
- Raising a 404 error
- ===================
- Now, let's tackle the question detail view -- the page that displays the question text
- for a given poll. Here's the view:
- .. code-block:: python
- :caption: ``polls/views.py``
- from django.http import Http404
- from django.shortcuts import render
- from .models import Question
- # ...
- def detail(request, question_id):
- try:
- question = Question.objects.get(pk=question_id)
- except Question.DoesNotExist:
- raise Http404("Question does not exist")
- return render(request, "polls/detail.html", {"question": question})
- The new concept here: The view raises the :exc:`~django.http.Http404` exception
- if a question with the requested ID doesn't exist.
- We'll discuss what you could put in that ``polls/detail.html`` template a bit
- later, but if you'd like to quickly get the above example working, a file
- containing just:
- .. code-block:: html+django
- :caption: ``polls/templates/polls/detail.html``
- {{ question }}
- will get you started for now.
- A shortcut: :func:`~django.shortcuts.get_object_or_404`
- -------------------------------------------------------
- It's a very common idiom to use :meth:`~django.db.models.query.QuerySet.get`
- and raise :exc:`~django.http.Http404` if the object doesn't exist. Django
- provides a shortcut. Here's the ``detail()`` view, rewritten:
- .. code-block:: python
- :caption: ``polls/views.py``
- from django.shortcuts import get_object_or_404, render
- from .models import Question
- # ...
- def detail(request, question_id):
- question = get_object_or_404(Question, pk=question_id)
- return render(request, "polls/detail.html", {"question": question})
- The :func:`~django.shortcuts.get_object_or_404` function takes a Django model
- as its first argument and an arbitrary number of keyword arguments, which it
- passes to the :meth:`~django.db.models.query.QuerySet.get` function of the
- model's manager. It raises :exc:`~django.http.Http404` if the object doesn't
- exist.
- .. admonition:: Philosophy
- Why do we use a helper function :func:`~django.shortcuts.get_object_or_404`
- instead of automatically catching the
- :exc:`~django.core.exceptions.ObjectDoesNotExist` exceptions at a higher
- level, or having the model API raise :exc:`~django.http.Http404` instead of
- :exc:`~django.core.exceptions.ObjectDoesNotExist`?
- Because that would couple the model layer to the view layer. One of the
- foremost design goals of Django is to maintain loose coupling. Some
- controlled coupling is introduced in the :mod:`django.shortcuts` module.
- There's also a :func:`~django.shortcuts.get_list_or_404` function, which works
- just as :func:`~django.shortcuts.get_object_or_404` -- except using
- :meth:`~django.db.models.query.QuerySet.filter` instead of
- :meth:`~django.db.models.query.QuerySet.get`. It raises
- :exc:`~django.http.Http404` if the list is empty.
- Use the template system
- =======================
- Back to the ``detail()`` view for our poll application. Given the context
- variable ``question``, here's what the ``polls/detail.html`` template might look
- like:
- .. code-block:: html+django
- :caption: ``polls/templates/polls/detail.html``
- <h1>{{ question.question_text }}</h1>
- <ul>
- {% for choice in question.choice_set.all %}
- <li>{{ choice.choice_text }}</li>
- {% endfor %}
- </ul>
- The template system uses dot-lookup syntax to access variable attributes. In
- the example of ``{{ question.question_text }}``, first Django does a dictionary lookup
- on the object ``question``. Failing that, it tries an attribute lookup -- which
- works, in this case. If attribute lookup had failed, it would've tried a
- list-index lookup.
- Method-calling happens in the :ttag:`{% for %}<for>` loop:
- ``question.choice_set.all`` is interpreted as the Python code
- ``question.choice_set.all()``, which returns an iterable of ``Choice`` objects and is
- suitable for use in the :ttag:`{% for %}<for>` tag.
- See the :doc:`template guide </topics/templates>` for more about templates.
- Removing hardcoded URLs in templates
- ====================================
- Remember, when we wrote the link to a question in the ``polls/index.html``
- template, the link was partially hardcoded like this:
- .. code-block:: html+django
- <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
- The problem with this hardcoded, tightly-coupled approach is that it becomes
- challenging to change URLs on projects with a lot of templates. However, since
- you defined the ``name`` argument in the :func:`~django.urls.path` functions in
- the ``polls.urls`` module, you can remove a reliance on specific URL paths
- defined in your url configurations by using the ``{% url %}`` template tag:
- .. code-block:: html+django
- <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- The way this works is by looking up the URL definition as specified in the
- ``polls.urls`` module. You can see exactly where the URL name of 'detail' is
- defined below::
- ...
- # the 'name' value as called by the {% url %} template tag
- path("<int:question_id>/", views.detail, name="detail"),
- ...
- If you want to change the URL of the polls detail view to something else,
- perhaps to something like ``polls/specifics/12/`` instead of doing it in the
- template (or templates) you would change it in ``polls/urls.py``::
- ...
- # added the word 'specifics'
- path("specifics/<int:question_id>/", views.detail, name="detail"),
- ...
- Namespacing URL names
- =====================
- The tutorial project has just one app, ``polls``. In real Django projects,
- there might be five, ten, twenty apps or more. How does Django differentiate
- the URL names between them? For example, the ``polls`` app has a ``detail``
- view, and so might an app on the same project that is for a blog. How does one
- make it so that Django knows which app view to create for a url when using the
- ``{% url %}`` template tag?
- The answer is to add namespaces to your URLconf. In the ``polls/urls.py``
- file, go ahead and add an ``app_name`` to set the application namespace:
- .. code-block:: python
- :caption: ``polls/urls.py``
- from django.urls import path
- from . import views
- app_name = "polls"
- urlpatterns = [
- path("", views.index, name="index"),
- path("<int:question_id>/", views.detail, name="detail"),
- path("<int:question_id>/results/", views.results, name="results"),
- path("<int:question_id>/vote/", views.vote, name="vote"),
- ]
- Now change your ``polls/index.html`` template from:
- .. code-block:: html+django
- :caption: ``polls/templates/polls/index.html``
- <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- to point at the namespaced detail view:
- .. code-block:: html+django
- :caption: ``polls/templates/polls/index.html``
- <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
- When you're comfortable with writing views, read :doc:`part 4 of this tutorial
- </intro/tutorial04>` to learn the basics about form processing and generic
- views.
|