Написание вашего первого приложения Django, часть 3
Этот учебник продолжается с того места, где закончился Учебник 2. Мы продолжаем работу с веб-приложением для опросов и сосредоточимся на создании публичного интерфейса — «представлений».
Где получить помощь:
Если у вас возникнут трудности с прохождением этого учебника, пожалуйста, перейдите к разделу Получение помощи раздела вопросов и ответов.
Обзор
Представление — это «тип» веб-страницы в вашем приложении Django, который, как правило, выполняет определённую функцию и имеет определённую шаблонную структуру. Например, в приложении блога у вас могут быть следующие представления:
- Главная страница блога — отображает последние несколько записей.
- Страница «детали» записи — страница перманентного URL для одной записи.
- Страница архива по годам — отображает все месяцы с записями в данном году.
- Страница архива по месяцам — отображает все дни с записями в данном месяце.
- Страница архива по дням — отображает все записи в данный день.
- Действие комментария — обрабатывает отправку комментариев к данной записи.
В нашем приложении для опросов у нас будут следующие четыре представления:
- Страница «индекс» вопроса — отображает последние несколько вопросов.
- Страница «детали» вопроса — отображает текст вопроса без результатов, но с формой для голосования.
- Страница «результаты» вопроса — отображает результаты для конкретного вопроса.
- Действие голосования — обрабатывает голосование за конкретный вариант ответа на конкретный вопрос.
В 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() и отобразит предоставленный вами идентификатор в 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, если вопрос с запрошенным ID не существует.
Мы обсудим, что можно добавить в этот 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.1/intro/tutorial03/