Написание вашей первой Django-приложения, часть 3
Этот учебник начинается там, где закончился Учебник 2. Мы продолжаем веб-приложение для опросов и сосредоточимся на создании публичного интерфейса — «представлениях».
Где получить помощь:
Если у вас возникнут проблемы с прохождением этого учебника, обратитесь к разделу Получение помощи раздела FAQ.
Обзор
Представление — это «тип» веб-страницы в вашем Django-приложении, которое обычно выполняет определенную функцию и имеет определенную шаблонную структуру. Например, в приложении блога у вас могут быть следующие представления:
- Главная страница блога — отображает последние несколько записей.
- Страница «детали» записи — страница с постоянной ссылкой на отдельную запись.
- Архивная страница по годам — отображает все месяцы с записями в указанном году.
- Архивная страница по месяцам — отображает все дни с записями в указанном месяце.
- Архивная страница по дням — отображает все записи в данный день.
- Действие комментария — обрабатывает отправку комментариев к данной записи.
В нашем приложении для опросов у нас будет четыре представления:
- Страница «индекс» вопроса — отображает последние несколько вопросов.
- Страница «детали» вопроса — отображает текст вопроса без результатов, но с формой для голосования.
- Страница «результаты» вопроса — отображает результаты для конкретного вопроса.
- Действие голосования — обрабатывает голосование за конкретный вариант ответа на конкретный вопрос.
В Django веб-страницы и другой контент предоставляются представлениями. Каждое представление представлено функцией Python (или методом, в случае представлений на основе классов). Django выберет представление, проанализировав URL-адрес запроса (точнее, часть URL-адреса после имени домена).
Теперь в ваше время в интернете вы могли встретить такие чудеса, как ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B. Вы будете рады узнать, что Django позволяет нам намного более элегантные URL-паттерны, чем это.
URL-паттерн — это общий вид URL-адреса — например: /newsarchive/<year>/<month>/.
Для перехода от URL-адреса к представлению Django использует то, что известно как «URLconf». URLconf сопоставляет URL-паттерны с представлениями.
Этот учебник предоставляет базовую информацию об использовании URLconf, и вы можете обратиться к диспетчеру URL для получения дополнительной информации.
Написание дополнительных представлений
Теперь давайте добавим несколько дополнительных представлений к polls/views.py. Эти представления немного отличаются, потому что они принимают аргумент:
polls/views.pydef 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.pyfrom 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 загрузит модуль Python 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, или стороннюю систему Python шаблонов, или нет. Оно может генерировать файл PDF, выводить XML, создавать ZIP-архив на лету, всё, что вы хотите, используя любые Python-библиотеки, которые вы хотите.
Всё, что нужно Django, это HttpResponse. Или исключение.
Для удобства давайте воспользуемся собственным API базы данных Django, который мы рассмотрели в Уроке 2. Вот один способ реализации нового представления index(), которое отображает последние 5 вопросов опроса в системе, разделённые запятыми, в соответствии с датой публикации:
polls/views.pyfrom 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, как описано выше, вы можете ссылаться на этот шаблон внутри Django как на polls/index.html.
Именование шаблонов
Сейчас мы, возможно, сможем обойтись без размещения наших шаблонов непосредственно в polls/templates (вместо создания другой поддиректории polls), но на самом деле это плохая идея. Django выберет первый шаблон, имя которого совпадает, и если у вас был шаблон с таким же именем в другом приложении, 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.pyfrom 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 и передает ему контекст. Контекст — это словарь, сопоставляющий имена переменных шаблона с объектами Python.
Загрузите страницу, указав в браузере адрес «/polls/», и вы должны увидеть маркированный список, содержащий вопрос «Что нового» из Урока 2. Ссылка указывает на страницу подробностей вопроса.
Сокращение: render()
Очень распространённый приём — загрузить шаблон, заполнить контекст и вернуть объект HttpResponse с результатом отображения шаблона. Django предоставляет сокращение. Вот полное представление index(), переписанное:
polls/views.pyfrom 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.pyfrom 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, если вопрос с запрошенным идентификатором не существует.
Мы обсудим, что вы можете поместить в этот polls/detail.html шаблон немного позже, но если вы хотите быстро запустить пример выше, файл, содержащий только:
polls/templates/polls/detail.html{{ question }}
позволит вам начать работу на данном этапе.
Сокращение: get_object_or_404()
Очень распространенным приемом является использование get() и вызов Http404, если объект не существует. Django предоставляет сокращение. Вот представление detail(), переписанное:
polls/views.pyfrom 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() для нашего приложения опросов. Учитывая переменную контекста question, вот как может выглядеть шаблон polls/detail.html:
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 %}.
Дополнительную информацию о шаблонах см. в руководстве по шаблонам.
Удаление жестко закодированных URL-адресов в шаблонах
Помните, когда мы писали ссылку на вопрос в шаблоне polls/index.html, ссылка была частично жестко закодирована следующим образом:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
Проблема с этим жестко закодированным, тесно связанным подходом заключается в том, что становится сложно изменять URL-адреса в проектах с большим количеством шаблонов. Однако, поскольку вы определили аргумент name в функциях path() в модуле polls.urls, вы можете избавиться от зависимости от определенных URL-путей, определенных в вашей конфигурации 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. В реальных проектах Django может быть пять, десять, двадцать или более приложений. Как Django различает имена URL-адресов между ними? Например, приложение polls имеет представление detail, и так же может быть приложение в том же проекте, предназначенное для блога. Как заставить Django знать, какое представление приложения создать для URL-адреса при использовании тега шаблона {% url %}?
Ответ заключается в добавлении пространств имен в вашу конфигурацию URL-адресов. В файле polls/urls.py добавьте app_name, чтобы установить пространство имен приложения:
polls/urls.pyfrom 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>
на указание на представление с пространством имен «detail»:
polls/templates/polls/index.html<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
Когда вы будете готовы к написанию представлений, прочитайте часть 4 этого учебника, чтобы узнать основы обработки форм и общих представлений.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/intro/tutorial03/