Представление (view) — это объект-страница в вашем Django-приложении, которое имеет специальные функции и специальный шаблон. Например, в приложении-блоге у вас могут быть такие представления:
домашняя страница блога — отображает последние записи ленты;
страница отдельной записи — постоянная страница для одной записи;
архивы записей по годам — отображает все месяцы, в которых были сделаны записи в заданном году;
архивы записей по месяцам — отображает все дни, в которых были сделаны записи в заданном месяце;
архивы записей по дням — отображает все записи за указанный день;
система комментариев — обрабатывает комментирование отдельной записи.
В нашем приложении-голосовании у нас будет 4 представления:
главная страница вопросов — отображение нескольких последних вопросов;
детали вопроса — отображение текста вопроса, без результатов голосования но с формой добавления голоса;
результаты голосования по вопросу — отображение результатов по каждому конкретному вопросу;
голосование — обработка голосования по каждому варианту ответа для вопроса.
В Django страницы и другой контент создаются представлениями. Каждое представление является простой функцией Python (или методом, в случае представлений, основанных на классах ООП). Django выбирает представление, основываясь на URL-е обращения (если точнее — то на той части URL-а, которая следует за доменным именем).
Вы, вероятно, уже встречались с такими «прелестными» URL-ами как «ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B» — и будете приятно удивлены, что Django предлагает намного более элегантный вид URL-ов для вашего проекта.
Паттерн URL-а — это часть запроса, следующая за доменом, например — /newsarchive/<year>/<month>/.
Что бы передать URL представлению — Django использует то, что мы называем URLconf. URLconf связывает паттерны URL-ов с представлениями.
В этой части мы рассмотрим основы работы с URLconf, а более полную информацию вы можете получить на странице django.core.urlresolvers.
Пишем ваше первое представление
Давайте напишем первое представление. Откройте файл polls/views.py и впишите туда такой код:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
Это самое простое представление в Django. Что бы вызвать его — нам надо связать его с URL-ом — и для этого нам потребуется URLconf.
Что бы создать URLconf — создайте файл 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)),
]
Теперь у вас есть представление index(), связанное с вашим URLconf.
Запустите ваш сервер разработки, если он ещё не запущен:
$ python manage.py runserver 77.***.***.20:8000
И перейдите на страницу http://ваш_IP:8000/polls — вы должны увидеть текст «Hello, world. You’re at the polls index», который мы вписали в представление index().
Функции url() передаётся четыре аргумента: два обязательных — regex и view, и два опциональных — kwargs и name. Давайте рассмотрим их.
Термин regex — сокращение от regular expression (регулярное выражение), которое предоставляет специальный синтаксис для получения совпадения по шаблону из строки, или, в нашем случае, из URL. Django начинает проверку с первого выражения в списке, и продолжает проверку всех выражений по очереди вниз, сравнивая переданный URL с каждым выражением, пока не найдёт совпадение.
Заметьте, что регулярные выражения в URLconf не выполняет поиск в GET или POST запросах, как и в самом доменном имени. Например, в запросе http://www.example.com/myapp/ URLconf будет выполнять поиск только по myapp/. В запросе http://www.example.com/myapp/?page=3 — аналогично, поиск будет выполняться только по myapp/.
Если вам нужна помощь в регулярных выражениях — вы можете получить её на странице в Википедии, в документации по модулю Python re, или в таблице RegEx: полная таблица.
Когда Django находит совпадение в URL с заданным регулярным выражением — вызывается указанная функция представления, передавая ей первым аргументом объект HttpRequest, и «захваченные» значения из регулярного выражения другими аргументами. Если regex использует простой захват — значения передаются как позиционные аргументы; если используется именованный захват — значения передаются в виде аргументов с ключевыми словами.
Произвольные аргументы в виде ключевых слов могут быть переданы в виде словаря целевому представлению. Мы не будем использовать этот подход в нашем руководстве.
Создание имени для ваших URL-ов позволяет обращаться к ним во всем проекте (или приложении), что может быть особенно удобно в шаблонах. Это очень полезная возможность, которая позволяет вам производить глобальные изменения в шаблонах 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():
Откройте в браузере страницу /polls/34/. Она вызовет функцию detail() и отобразит ID, который вы передали в URL. Попробуйте так же открыть /polls/34/results/ и /polls/34/vote/.
Когда кто-то запрашивает страницу на вашем сайте, например — /polls/34/, Django загружает модуль mysite.urls, так как он указан в параметре ROOT_URLCONF. Там находится переменная urlpatterns, в которой по очереди проверяются регулярные выражения. Функция include() используется для создания ссылки на другой URLconf. Заметьте, что регулярное выражение для include() не имеет закрывающего символа $ (символ конца строки), а вместо этого — используется символ косой черты. Каждый раз, когда Django встречает include() — он «обрезает» URL до точки совпадения с выражением, передаёт оставшуюся часть подключенному URLconf-у для дальнейшей обработки.
Основная идея в использовании include() — это возможность простого подключения новых URL-ов. Так как голосование имеет собственный URLconf (файл polls/urls.py) — её URL-ы могут быть расположены «под» /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]+ — регулярное выражение для получения набора цифр.
Пишем представление, которое выполняет реальные действия
Каждое представление ответственно за одну из двух задач — либо возвращает объект HttpResponse, содержащий контент для запрошенной страницы, либо — вызывает исключение, такое как Http404. Всё остальное — на ваше усмотрение.
Ваше представление может считывать записи из базы — или нет. Оно может использовать систему шаблонов — родную, Django, или систему сторонних разработчиков — или нет. Оно может генерировать PDF-файл, XML, создавать ZIP-архив — что угодно, используя все доступные библиотеки Python.
Всё, чего хочет Django — это объект HttpResponse. Или исключение.
В нашем случае — мы будем использовать Django API для работы с базой данных, который мы рассматривали в предыдущей части. Вот как мы можем изменить наше представление 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)
...
Остальную часть оставьте без изменений.
Однако — тут имеется проблема: дизайн страницы жестко прописан прямо в коде представления. Если вы захотите изменить внешний вид страницы — вам придётся редактировать код Python. Давайте используем систему шаблонов Django, что бы отделить дизайн страницы от представления, и создадим шаблон, который будет использоваться представлением.
Для начал — создайте директорию templates в каталоге polls — Django будет искать шаблоны именно в ней.
Параметр TEMPLATES в файле settings.py указывает Django как загружать и рендерить шаблоны. По умолчанию в Django активирован модуль DjangoTemplates, параметр APP_DIRS которого установлен в True, что указывает Django выполнять поиск каталога templates в каталоге каждого приложения из списка INSTALLED_APPS. Вот как Django знает, что шаблоны приложения polls надо искать каталоге templates, хотя мы не меняли настройки DIRS, как мы делали это в третьей части.
Внутри каталога templates, который вы только что создали, создайте ещё один каталог — polls, а в нём создайте файл index.html. Другими словами — ваш шаблон должен находиться в файле polls/templates/polls/index.html. Учитывая рассмотренное выше — вы можете обращаться к нему как polls/index.html.
Пространство имён в шаблонах
Вы можете располагать ваши шаблоны прямо в каталоге polls/templates, а не создавать ещё одну вложенную директорию — однако это может быть очень плохой идеей. 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 %}
Теперь — давайте обновим наше представление index() в файле polls/views.py, что бы его использовать:
Этот код загружает шаблон polls/index.html и передаёт ему контекст. Контекст — это словарь, связывающий переменные в шаблоне с объектами Python.
Обновите страницу /polls/ в браузере, и вы должны увидеть маркированный список вопросов, в котором будет вопрос «What’s up?» из второй части этого руководства. Ссылка направлена на страницу деталей этого вопроса.
Задача по загрузке шаблона, наполнению контекста и возврату HttpResponse — очень частая. Поэтому, Django предоставляет в использование функцию render(). Вот переписанное представление index() с её использованием:
Функция render() принимает первым аргументом объект запроса, имя шаблона — вторым аргументом и словарь — опциональным третьим аргументом, а возвращает объект HttpResponse из шаблона с контекстом.
Создание ошибки 404
Теперь — давайте приведём в порядок функцию detail() — страницу, которая отображает детали выбранного вопроса:
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, если запрошенный ID не существует.
Мы рассмотрим подробнее содержимое страницы polls/detail.html позже, но если вы хотите посмотреть результат прямо сейчас — просто добавьте в файл polls/templates/polls/detail.html такую строку:
Очень часто функции get() и Http404 используются вместе, что бы вызвать 404, если объект не найден. Поэтому — Django предлагает сокращение для таких случаев.
Функция get_object_or_404() принимает первым аргументом модель Django, и набор ключевых слов остальными аргументами, которые передаются функции менеджера модели get(). В случае, если объект не найден — вызывается исключение Http404.
Возвращаясь к представлению detail() нашего приложения polls. Используя переданную переменную question — вот как мы можем использовать её в файле шаблона 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 %}: {% for %} question.choice_set.all интерпретируется как Python вызов question.choice_set.all(), который возвращает объект Choice, над которым можно выполнить итерацию и который допустим к использованию в >{% for %}.
Проблема в таком подходе заключается в том, что замена URL-а при использовании множества файлов шаблонов становится очень сложной задачей. Однако, так как вы объявили аргумент name в функции url() в модуле polls.urls — вы можете удалить эту ссылку, и переписать её с использованием тега {% url %} в шаблоне:
Если вы захотите изменить URL для деталей вопроса, например на polls/specifics/12, вместо того, что бы менять его во всех файлов шаблонов, где он используется, вам достаточно будет изменить его в polls/urls.py:
...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
Пространства имён в URL-ах
Наш проект имеет только одно приложение — polls. В реальных проектах Django — у вас может быть и пять, и двадцать пять приложений. Как Django различает имена URL-ов между ними? Например, приложение polls содержит представление detail(), а так же, допустим, другое приложение — блог, в этом же проекте. Как сделать так, что бы Django знал — чьё представление использовать для адреса, указанного в теге {% 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)),
]