Создание вашего первого приложения 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 загрузит модуль mysite.urls Python, так как он указан параметром 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 будет загружать и рендерить шаблоны. Файл стандартных настроек настраивает backend 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>
Система шаблонов использует синтаксис dot-lookup для доступа к атрибутам переменных. В примере {{ 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 %}?
Ответ заключается в добавлении пространств имен в ваш URLconf. В файле 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/4.2/intro/tutorial03/