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