Написание вашей первой Django-приложения, часть 3
Этот учебник начинается там, где Учебник 2 остановился. Мы продолжаем приложение Web-poll и сосредоточимся на создании публичного интерфейса — «представлений».
Философия
Представление — это «тип» веб-страницы в вашем Django-приложении, которое обычно выполняет определённую функцию и имеет определённую шаблонную страницу. Например, в приложении блога у вас могут быть следующие представления:
- Главная страница блога — отображает несколько последних записей.
- Страница записи «подробностей» — страница постоянной ссылки на одну запись.
- Страница архива по годам — отображает все месяцы с записями в заданном году.
- Страница архива по месяцам — отображает все дни с записями в заданном месяце.
- Страница архива по дням — отображает все записи в заданный день.
- Действие комментария — обрабатывает отправку комментариев к данной записи.
В нашем приложении опросов у нас будет четыре представления:
- Страница «список» вопросов — отображает несколько последних вопросов.
- Страница вопроса «подробностей» — отображает текст вопроса без результатов, но с формой для голосования.
- Страница «результатов» вопроса — отображает результаты для конкретного вопроса.
- Действие голосования — обрабатывает голосование за конкретный выбор в конкретном вопросе.
В Django веб-страницы и другое содержимое предоставляются представлениями. Каждое представление представлено простой функцией Python (или методом в случае представлений на основе классов). Django выберет представление, проверив URL-адрес запроса (точнее, часть URL-адреса после доменного имени).
Сейчас, возможно, вы сталкивались в интернете с такими красотами, как «ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B». Вы будете рады узнать, что Django позволяет нам гораздо более элегантные URL-шаблоны, чем это.
URL-шаблон — это просто общий вид URL-адреса, например: /newsarchive/<year>/<month>/.
Чтобы перейти от URL-адреса к представлению, Django использует то, что известно как «URLconf». URLconf сопоставляет URL-шаблоны (описанные как регулярные выражения) с представлениями.
Этот учебник предоставляет базовые инструкции по использованию URLconf, и вы можете обратиться к django.core.urlresolvers за дополнительной информацией.
Напишите ваше первое представление
Давайте напишем первое представление. Откройте файл polls/views.py и поместите в него следующий код Python:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
Это самое простое возможное представление в Django. Чтобы вызвать представление, нам нужно сопоставить его с URL-адресом, а для этого нам нужен URLconf.
Чтобы создать URLconf в каталоге опросов, создайте файл под названием urls.py. Теперь каталог вашего приложения должен выглядеть так:
polls/
__init__.py
admin.py
models.py
tests.py
urls.py
views.py
В файле polls/urls.py включите следующий код:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
Следующим шагом является указание корневого URLconf на модуль polls.urls. В mysite/urls.py вставьте include(), оставив вам:
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^polls/', include('polls.urls')),
url(r'^admin/', include(admin.site.urls)),
]
Не соответствует тому, что вы видите?
Если вы видите admin.autodiscover() перед определением urlpatterns, скорее всего, вы используете версию Django, которая не соответствует версии этого учебника. Вам нужно будет либо переключиться на более старый учебник, либо на более новую версию Django.
Вы теперь связали представление index с URLconf. Перейдите по адресу http://localhost:8000/polls/ в вашем браузере, и вы должны увидеть текст «Привет, мир. Вы находитесь на главной странице опросов.», который вы определили в представлении index.
Функция url() принимает четыре аргумента, два обязательных: regex и view, и два необязательных: kwargs и name. На данном этапе стоит пересмотреть назначение этих аргументов.
url() аргумент: regex
Термин «regex» — это общепринятое сокращение от «регулярное выражение», которое представляет собой синтаксис для сопоставления шаблонов в строках, или в данном случае, шаблонов URL-адресов. Django начинает с первого регулярного выражения и продвигается вниз по списку, сравнивая запрашиваемый URL-адрес с каждым регулярным выражением, пока не найдет совпадение.
Обратите внимание, что эти регулярные выражения не выполняют поиск параметров GET и POST, а также доменного имени. Например, в запросе на http://www.example.com/myapp/ URLconf будет искать myapp/. В запросе на http://www.example.com/myapp/?page=3 URLconf также будет искать myapp/.
Если вам нужна помощь с регулярными выражениями, ознакомьтесь со статьёй Википедии по этому вопросу и с документацией модуля re. Также отличной книгой является «Мастерство регулярных выражений» Джеффри Фридла от издательства O'Reilly. На практике, однако, вам не нужно быть экспертом по регулярным выражениям, так как вам нужно знать только как захватить простые шаблоны. Фактически, сложные регулярные выражения могут иметь низкую производительность поиска, поэтому, вероятно, не стоит полагаться на всю мощь регулярных выражений.
И, наконец, примечание о производительности: эти регулярные выражения компилируются в первый раз при загрузке модуля URLconf. Они очень быстрые (пока запросы не слишком сложны, как отмечалось выше).
url() аргумент: представление
Когда Django находит совпадение с регулярным выражением, Django вызывает указанную функцию представления, с объектом HttpRequest в качестве первого аргумента и любыми «захваченными» значениями из регулярного выражения в качестве других аргументов. Если регулярное выражение использует простые захватчики, значения передаются как позиционные аргументы; если оно использует именованные захватчики, значения передаются как именованные аргументы. Мы дадим пример этого немного позже.
url() аргумент: kwargs
Произвольные именованные аргументы могут передаваться в целевое представление в виде словаря. В данном учебнике мы не будем использовать эту функцию Django.
url() аргумент: имя
Наименование URL-адреса позволяет вам однозначно ссылаться на него из других частей Django, особенно из шаблонов. Эта мощная функция позволяет выполнять глобальные изменения в шаблонах URL-адресов вашего проекта, затрагивая только один файл.
Написание дополнительных представлений
Теперь давайте добавим несколько дополнительных представлений в polls/views.py. Эти представления немного отличаются, потому что они принимают аргумент:
def 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, добавив следующие вызовы url():
from django.conf.urls import url
from . import views
urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/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 и перебирает регулярные выражения по порядку. Функции include(), которые мы используем, просто ссылаются на другие URLconf. Обратите внимание, что регулярные выражения для функций include() не имеют $ (символа совпадения с концом строки), а вместо этого имеют trailing slash.
Всякий раз, когда Django сталкивается с include(), он отбрасывает часть URL, которая совпала до этого момента, и отправляет оставшуюся строку в включённый URLconf для дальнейшей обработки.
Идея, лежащая в основе include(), заключается в том, чтобы сделать подключение URL-адресов простым. Поскольку опросы находятся в собственном URLconf (polls/urls.py), их можно разместить под «/polls/», или под «/fun_polls/», или под «/content/polls/», или под любым другим корнем пути, и приложение по-прежнему будет работать.
Вот что происходит, если пользователь переходит по адресу «/polls/34/» в этой системе:
- Django найдёт совпадение в
'^polls/' -
Затем Django удалит соответствующий текст (
"polls/") и отправит оставшийся текст —"34/"— в URLconf «polls.urls» для дальнейшей обработки, которая совпадает сr'^(?P<question_id>[0-9]+)/$', что приводит к вызову представленияdetail()следующим образом:detail(request=<HttpRequest object>, question_id='34')
Часть question_id='34' берется из (?P<question_id>[0-9]+). Использование скобок вокруг шаблона «захватывает» текст, соответствующий этому шаблону, и отправляет его в функцию представления; ?P<question_id> определяет имя, которое будет использоваться для идентификации соответствующего шаблона; и [0-9]+ — это регулярное выражение для соответствия последовательности цифр (то есть числу).
Поскольку URL-шаблоны являются регулярными выражениями, на самом деле нет ограничений на то, что вы с ними можете сделать. И нет необходимости добавлять URL-мусор, такой как .html — если вы этого не хотите, в таком случае вы можете сделать что-то вроде этого:
url(r'^polls/latest\.html$', views.index),
Но не делайте этого. Это глупо.
Напишите представления, которые что-то делают
Каждое представление отвечает за выполнение одного из двух действий: возвращение объекта HttpResponse с содержимым запрашиваемой страницы или поднятие исключения, такого как Http404. Остальное зависит от вас.
Ваш вид может читать записи из базы данных, а может и нет. Он может использовать систему шаблонов, такую как Django’s – или стороннюю систему шаблонов Python – или нет. Он может генерировать файл PDF, выводить XML, создавать ZIP-архив на лету, всё что угодно, используя любые библиотеки Python, которые вам нужны.
Всё, что нужно Django, это HttpResponse. Или исключение.
Для удобства давайте воспользуемся собственным API базы данных Django, о котором мы говорили в Уроке 1. Вот один из вариантов нового index() вида, который отображает последние 5 вопросов опроса в системе, разделенные запятыми, в соответствии с датой публикации:
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([p.question_text for p 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. Таким образом Django знает, как найти шаблоны опроса, даже если мы не изменили опцию DIRS, как мы делали в Уроке 2.
Организация шаблонов
Мы могли бы разместить все наши шаблоны вместе, в одном большом каталоге шаблонов, и это работало бы идеально. Однако этот шаблон принадлежит приложению опросов, поэтому, в отличие от шаблона администрирования, созданного в предыдущем руководстве, мы разместим его в директории шаблонов приложения (polls/templates), а не в директории проекта (templates). Мы более подробно обсудим в руководстве по многоразовому использованию приложений почему мы это делаем.
Внутри директории templates, которую вы только что создали, создайте ещё одну директорию, называемую polls, а в ней создайте файл, называемый index.html. Другими словами, ваш шаблон должен быть по адресу polls/templates/polls/index.html. Из-за того, как работает загрузчик шаблонов app_directories, как описано выше, вы можете ссылаться на этот шаблон в Django просто как polls/index.html.
Пространство имён шаблонов
Сейчас мы возможно смогли бы обойтись без размещения шаблонов непосредственно в polls/templates (вместо создания другой polls поддиректории), но это было бы плохой идеей. Django выберет первый шаблон, имя которого совпадает, и если у вас был шаблон с тем же именем в другом приложении, Django не смог бы отличить их. Нам необходимо указать Django на нужный шаблон, а самый простой способ сделать это — использовать пространство имён, то есть разместить эти шаблоны внутри другой директории, названной в честь самого приложения.
Вставьте следующий код в этот шаблон:
{% 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 %}
Теперь обновим наше представление index в приложении polls/views.py для использования шаблона:
from 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/», и вы должны увидеть список с пунктом «Что нового?» из урока 1. Ссылка ведет на страницу подробностей вопроса.
Сокращение: render()
Это очень распространённая конструкция: загрузить шаблон, заполнить контекст и вернуть объект HttpResponse с результатом рендеринга шаблона. Django предоставляет сокращение. Вот полное index() представление, переписанное:
from 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
Теперь давайте рассмотрим представление детали вопроса — страницу, которая отображает текст вопроса для заданного опроса. Вот представление:
from 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 шаблон немного позже, но если вы хотите быстро запустить пример выше, файл, содержащий только:
{{ question }}
пока поможет вам начать работу.
Сокращение: get_object_or_404()
Очень часто используется get() и вызов Http404, если объект не существует. Django предоставляет сокращение. Вот detail() представление, переписанное:
from 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:
<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 в функциях url() в модуле 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
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
Если вы хотите изменить URL-адрес представления подробностей опросов на что-то другое, например, на polls/specifics/12/, вместо того, чтобы делать это в шаблоне (или шаблонах), вы измените его в polls/urls.py:
... # added the word 'specifics' url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'), ...
Назначение имён URL-адресов
В проекте учебника всего один app, polls. В реальных проектах Django может быть пять, десять, двадцать и более приложений. Как Django различает имена URL-адресов между ними? Например, приложение polls имеет представление detail, и точно такое же представление может быть в приложении одного проекта для блога. Как заставить Django узнать, какое представление приложения создать для URL-адреса при использовании тега шаблона {% url %}?
Ответ заключается в добавлении пространств имён в ваш основной URLconf. В файле mysite/urls.py измените его, чтобы он включал назначение пространств имён:
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^polls/', include('polls.urls', namespace="polls")),
url(r'^admin/', include(admin.site.urls)),
]
Теперь измените свой шаблон polls/index.html с:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
на указание на представление detail с назначенным пространством имён:
<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/1.8/intro/tutorial03/