Spec-Zone.ru › Django 6.0

Создание первого приложения 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.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, добавив следующие вызовы path():

polls/urls.py
from 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() и отобразит идентификатор, указанный вами в URL. Попробуйте также «/polls/34/results/» и «/polls/34/vote/» — на этих страницах будут показаны заглушки для результатов и голосования.

Когда кто-нибудь запрашивает страницу вашего сайта — например, «/polls/34/», Django загружает модуль Python mysite.urls, поскольку на него указывает настройка ROOT_URLCONF. Затем Django находит переменную с именем urlpatterns и последовательно перебирает шаблоны. Найдя совпадение в 'polls/', Django удаляет совпавший текст ("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.py
from 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.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/» в браузере: вы должны увидеть маркированный список с вопросом «Как дела?» из руководства 2. Ссылка ведёт на страницу подробностей вопроса.

Упрощённый способ: render()

Очень часто шаблон загружают, заполняют контекстом и возвращают объект HttpResponse с результатом отображения шаблона. Django предоставляет упрощённый способ. Вот переписанное целиком представление index():

polls/views.py
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

Теперь займёмся представлением подробностей вопроса — страницей, которая отображает текст вопроса для конкретного опроса. Вот это представление:

polls/views.py
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. А пока, чтобы быстро запустить приведённый выше пример, создайте файл, содержащий только:

polls/templates/polls/detail.html
{{ question }}

Этого пока будет достаточно, чтобы начать.

Упрощённый способ: get_object_or_404()

Очень часто вызывают get() и вызывают исключение Http404, если объект не существует. Django предоставляет упрощённый способ. Вот переписанное представление detail():

polls/views.py
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 может выглядеть так:

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. Если это не удаётся, Django пытается получить атрибут — в данном случае это работает. Если бы получить атрибут не удалось, Django попытался бы обратиться к элементу списка по индексу.

Вызов метода происходит в цикле {% 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.py
from 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/6.0/intro/tutorial03/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API