Создание вашей первой 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 будет загружать и отображать шаблоны. Файл настроек по умолчанию настраивает 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 %}?
Ответ заключается в добавлении пространств имён в ваш файл 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.1/intro/tutorial03/