Django: пример создания приложения – часть 4: ваше первое представление (view)

Автор: | 05/15/2015
 

django_logo_2

Предыдущая часть

Введение

Представление (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 использует то, что мы называем URLconfURLconf связывает паттерны 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. Теперь ваш каталог с приложением должен теперь выглядеть так:

$ tree -L 2 polls/
polls/
├── admin.py
├── admin.pyc
├── __init__.py
├── __init__.pyc
├── migrations
│   ├── 0001_initial.py
│   ├── 0001_initial.pyc
│   ├── __init__.py
│   └── __init__.pyc
├── models.py
├── models.pyc
├── 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)),
]

Теперь у вас есть представление 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. Давайте рассмотрим их.

url() аргумент: regex

Термин regex – сокращение от regular expression (регулярное выражение), которое предоставляет специальный синтаксис для получения совпадения по шаблону из строки, или, в нашем случае, из URL. Django начинает проверку с первого выражения в списке, и продолжает проверку всех выражений по очереди вниз, сравнивая переданный URL с каждым выражением, пока не найдёт совпадение.

Заметьте, что регулярные выражения в URLconf не выполняет поиск в GET или POST запросах, как и в самом доменном имени. Например, в запросе http://www.example.com/myappURLconf будет выполнять поиск только по myapp/. В запросе http://www.example.com/myapp/?page=3 – аналогично, поиск будет выполняться только по myapp/.

Если вам нужна помощь в регулярных выражениях – вы можете получить её на странице в Википедии, в документации по модулю Python re, или в таблице RegEx: полная таблица.

url() аргумент: view

Когда Django находит совпадение в URL с заданным регулярным выражением – вызывается указанная функция представления, передавая ей первым аргументом объект HttpRequest, и “захваченные” значения из регулярного выражения другими аргументами. Если regex использует простой захват – значения передаются как позиционные аргументы; если используется именованный захват – значения передаются в виде аргументов с ключевыми словами.

url() аргумент: kwargs

Произвольные аргументы в виде ключевых слов могут быть переданы в виде словаря целевому представлению. Мы не будем использовать этот подход в нашем руководстве.

url() аргумент: name

Создание имени для ваших 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():

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 загружает модуль 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/” – к URLconfpolls.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 в каталоге pollsDjango будет искать шаблоны именно в ней.

Параметр 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, что бы его использовать:

from django.http import HttpResponse
from django.template import RequestContext, 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 = RequestContext(request, {
        'latest_question_list': latest_question_list,
    })
    return HttpResponse(template.render(context))
...

Этот код загружает шаблон polls/index.html и передаёт ему контекст. Контекст – это словарь, связывающий переменные в шаблоне с объектами Python.

Обновите страницу /polls/ в браузере, и вы должны увидеть маркированный список вопросов, в котором будет вопрос “What’s up?” из второй части этого руководства. Ссылка направлена на страницу деталей этого вопроса.

функция render()

Задача по загрузке шаблона, наполнению контекста и возврату HttpResponse – очень частая. Поэтому, Django предоставляет в использование функцию render(). Вот переписанное представление 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)
...

Помните, что если переписать все три функции на использование render() – то нам больше не потребуется выполнять импорт loader, RequestContext и HttpResponse.

Функция 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 такую строку:

{{ question }}

Функция get_object_or_404()

Очень часто функции get() и Http404 используются вместе, что бы вызвать 404, если объект не найден. Поэтому – 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_list_or_404(), которая работает аналогично get_object_or_404(), за исключением того, что она использует функцию filter(), а не 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 %}.

Больше информации о шаблонах можно найти в template guide или в цикле Django Book: основы системы шаблонов.

Удаляем URL-ы из шаблонов

Помните, когда мы вписывали ссылку на вопрос к файле шаблона polls/index.html – она в нём была жестко прописана:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

Проблема в таком подходе заключается в том, что замена URL-а при использовании множества файлов шаблонов становится очень сложной задачей. Однако, так как вы объявили аргумент name в функции url() в модуле polls.urls – вы можете удалить эту ссылку, и переписать её с использованием тега {% url %} в шаблоне:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Таким образом – выполняется поиск указанного URL в модуле polls.urls, который в нашем случае выглядит так:

...
    # ex: /polls/5/
    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-ах

Наш проект имеет только одно приложение – 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)),
]

Теперь – измените шаблон polls/index.html с:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

на:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Готово.

Продолжение – Django: пример создания приложения – часть 5: создание форм и общие представления (generic views)