• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

Создаём своё первое приложение с Django, часть 3

Продолжаем начатое во второй части учебника. Мы продолжим разрабатывать приложение для голосования и сосредоточимся на создании страниц сайта – «представлений».

Где получить помощь:

При наличии проблем с данной инструкцией, пожалуйста, обратитесь к разделу FAQ Получение помощи.

Обзор

Представление – это «тип» страниц вашего приложения, которое является функцией для обработки запроса и использует шаблон для генерации страницы. Например, блог может состоять из следующих представлений:

  • Главная страница – показывает несколько последних записей блога.

  • Страница записи – страница отображения одной записи блога.

  • Страница-архив записей по годам – показывает все месяца года и записи блога, сгруппированные по этим месяцам.

  • Страница-архив записей по месяцам – показывает все дни месяца и записи блога, сгруппированные по этим дням.

  • Страница-архив записей по дням – показывает все записи за указанный день.

  • Форма комментариев – предоставляет возможность добавить комментарий к записи блога.

В нашем приложении для голосования мы реализуем следующие представления:

  • Главная страница вопросов – показывает несколько последних вопросов.

  • Страница вопроса – показывает вопрос без результатов но с формой для ответа.

  • Страница результата опроса – показывает результаты опроса.

  • Обрабатывает процесс опроса – обрабатывает ответ на вопрос.

В Django страницы и остальной контент отдается представлениями. Представление - это просто функция Python(или метод представления-класса). Django выбирает представление, анализируя запрошенный URL(точнее часть URL-а после домена).

В наше время в интернете можно наткнуться на такие прелести как «ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B». Рады вам сообщить, что Django позволяет использовать более элегантные URL шаблоны чем этот.

URL-шаблон - это общая форма URL-а. Например: /newsarchive/<year>/<month>/.

Чтобы из URL-а получить представление, Django используется так называемый „URLconf“. URLconf определяет соответствие URL-шаблонов и представлений.

В этом разделе мы опишем основы использования настроек URL-ов, больше информации вы можете найти в разделе Менеджер URL-ов.

Добавь парочку представлений

Теперь создадим еще парочку представлений в polls/views.py. Эти представления немного отличаются, так как принимают аргументы:

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)

Привяжем наше представление новостей в модуле polls.urls добавив вызов path():

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"),
]

Откройте страницу «/polls/34/». Будет выполнена функция detail() и показан ID, который вы указали в URL. Откройте «/polls/34/results/» и «/polls/34/vote/» – вы увидите наши будущие страницы результатов и голосования.

При запросе страницы – например, «/polls/34/», Django загружает модуль mysite.urls т.к. он указан в ROOT_URLCONF. Находит переменную urlpatterns и перебирает все параметры переменных по порядку. Когда находит 'polls/', отрезает найденный текст ("polls/") и отправляет оставшийся – "34/" – в URLconf из „polls.urls“ для последующей обработки. Там находит соответствие с '<int:question_id>/', в результате чего вызывается представление detail():

detail(request=<HttpRequest object>, question_id=34)

Аргумент question_id='34' получен из <int:question_id>`. Использование скобок позволяет распознать часть URL и передать в представление. ``:question_id> определяет название переменной при передаче, <int: определяет какой шаблон должен использоваться при распознавании части URL.

Добавим функционал в наши представления

Каждое представление должно выполнить одно из двух действий: вернуть экземпляр HttpResponse с содержимым страницы, или вызвать исключения такое как Http404. Все остальное на ваше усмотрение.

Ваше представление может обращаться к базе данных или нет. Может использовать систему шаблонов Django – или любую другую – или не использовать. Может генерировать PDF файл, возвращать XML, создавать ZIP архив «на лету», все что угодно, используя любые библиотеки Python.

Все что нужно Django – это HttpResponse. Или исключение.

Мы будем использовать API Django для работы с базой данных, которое мы рассматривали в Части 1. Изменим index() так, чтобы оно отображало последние 5 вопросов разделенные запятой от самого нового к самому старому:

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

Однако здесь есть проблема: дизайн страницы жестко запрограммирован в представлении. Если вы хотите изменить внешний вид страницы, вам придется отредактировать этот код Python. Итак, давайте воспользуемся системой шаблонов Django, чтобы отделить дизайн от Python, создав шаблон, который может использовать представление.

Для начала создайте каталог templates в каталоге приложения polls. Django будет искать шаблоны в этом каталоге.

Настройка TEMPLATES указывает Django как загружать и выполнять шаблоны. По умолчанию используется бэкенд DjangoTemplates, с опцией APP_DIRS равной True. В этом случае DjangoTemplates проверяет подкаталог «templates» в приложениях, указанных в INSTALLED_APPS.

В только что созданном каталоге templates, создайте каталог polls, и в нем создайте файл index.html. То есть создайте файл polls/templates/polls/index.html. Учитывая как работает загрузчик шаблонов app_directories , вы сможете обращаться к шаблону как polls/index.html.

Пространства имен для шаблонов

Мы бы могли создать наш шаблон непосредственно в polls/templates (а не подкаталоге polls), но это плохая идея. Django будет использовать первый найденный шаблон, и если существует шаблон с аналогичным названием в другом приложении, Django не сможет различить их. Чтобы этого избежать, мы будем использовать пространство имен. Точнее, просто добавим их в еще один подкаталог с названием, аналогичным названию приложения.

Добавьте следующий код в шаблон:

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 %}

Примечание

Чтобы сделать этот урок покороче, в шаблонах используется HTML не в полной форме. В своих проектах всегда используйте только полные валидные HTML документы.

Теперь изменим наше представление index в polls/views.py, чтобы использовать шаблон:

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))

Этот код загружает шаблон polls/index.html и передает ему контекст. Контекст - это словарь, содержащий название переменных шаблона и соответствующие им значения.

Загрузите страницу в браузере по адресу «/polls/», вы должны увидеть список с опросом «What’s up» из Части 2. Ссылка ведет на страницу опроса.

Функция render()

Процесс загрузки шаблона, добавления контекста и возврат объекта HttpResponse, вполне тривиальный. Django предоставляет функцию для всех этих операций. Вот как будет выглядеть наш index():

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)

Так как мы используем такой подход во всех наших представлениях, нет необходимости импортировать loader и HttpResponse (HttpResponse еще нужен, если остались старые detail, results и vote).

Функция render() первым аргументом принимает объект запроса, также название шаблона и необязательный словарь значений контекста. Возвращает объект HttpResponse содержащий выполненный шаблон с указанным контекстом.

Вызов 404 исключения

Теперь создадим страницу опроса, которая отображает вопрос и варианты ответа. Вот так будет выглядеть наше представление:

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})

Представление вызывает исключение Http404, если вопрос с указанным ID не существует.

Содержимое шаблона polls/detail.html обсудим позже, но если хотите прям вот сразу, чтобы все заработало, вот его содержимое:

polls/templates/polls/detail.html
{{ question }}

чтобы можно было загрузить страницу.

Функция get_object_or_404()

Вызов get() и Http404 при отсутствии объекта – обыденные операции. Django предоставляет функцию, которая выполняет эти действия. Вот как будет выглядеть наше представление detail():

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})

Функция get_object_or_404() первым аргументом принимает Django модель и произвольное количество именованных аргументов, которые передаются в метод get() менеджера модели. Если объект не найден, вызывается исключение Http404.

Философия

Зачем мы используем функцию get_object_or_404() вместо того, чтобы автоматически перехватывать исключения ObjectDoesNotExist уровнем выше, или вызывать на уровне API моделей исключение Http404 вместо ObjectDoesNotExist?

Потому что это связывает уровень моделей с уровнем представления. Один из главных принципов проектирования Django – слабая связанность. Некоторая связанная функциональность находится в модуле django.shortcuts.

Существует также функция get_list_or_404(), которая работает аналогично get_object_or_404(), но использует filter() вместо get(). Вызывает Http404, если получен пустой список.

Использование системы шаблонов

Вернемся к представлению detail(). Вот как может выглядеть наш шаблон polls/detail.html, использующий контекстную переменную question:

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>

Система шаблонов использует точку для доступа к атрибутам переменной. Например, для {{ question.question_text }} Django сначала пытается обратиться к question как к словарю. При неудаче ищется атрибут переменной, в данном случае он и используется. Если атрибут не найден, будет искаться индекс в списке.

В теге {% for %} выполняется вызов метода: question.choice_set.all, интерпретируется как код Python question.choice_set.all(), который возвращает итератор по Choice для использования в теге {% for %}.

Подробности о шаблонах смотрите в разделе о Языке шаблонов Django.

Избавляемся от «хардкода» URL-ов в шаблонах

Помните, когда мы указывали ссылку в шаблоне polls/index.html, она была прописана прямо в коде:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

Проблема в том, что нам будет очень сложно поменять URL-ы в проекте с большим количеством шаблонов. Однако, так как мы указали названия при вызове path() в модуле polls.urls, мы можем ссылаться на шаблоны URL-ов используя шаблонный тег {% url %}:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Определение URL-а будет найдено в модуле polls.urls. Вот где определен наш URL с названием „detail“:

...
# the 'name' value as called by the {% url %} template tag
path("<int:question_id>/", views.detail, name="detail"),
...

Теперь, если вы захотите поменять URL, например на polls/specifics/12/, вам не придется менять все шаблоны, вы можете сделать это в polls/urls.py:

...
# added the word 'specifics'
path("specifics/<int:question_id>/", views.detail, name="detail"),
...

Пространства имен в названиях URL-ов

Наш проект содержит только одно приложение polls. В реальных проектах может быть 5, 10, 20 и больше приложений. Как же Django понимает где чей URL по его названию? Например, приложение polls содержит представление detail, аналогичное представление может быть и в приложении для блогов. Как же Django понимает для какого представления создается URL при использовании тега {% url %}?

Для этого используются пространства имен в URLconf. Изменим polls/urls.py и добавим пространство имен в app_name:

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"),
]

Теперь поменяем в шаблоне polls/index.html:

polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

чтобы использовать пространство имен URL-ов:

polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Научившись создавать представления, перейдем к четвёртой части учебника, чтобы научиться обрабатывать формы и использовать встроенные представления.

Back to Top