Spec-Zone.ru › Django 3.0

Создание вашего первого приложения Django, часть 3

Этот учебник начинается там, где закончился Учебник 2. Мы продолжаем приложение Web-poll и сосредоточимся на создании публичного интерфейса — «представлений».

Где получить помощь:

Если у вас возникнут трудности с прохождением этого учебника, пожалуйста, перейдите к разделу Получение помощи раздела 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 использует то, что известно как «URLconfs». URLconf сопоставляет URL-паттерны с представлениями.

Этот учебник содержит основные инструкции по использованию URLconfs, и вы можете обратиться к Диспечеру 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() и отобразит любой ID, который вы укажете в URL. Попробуйте также «/polls/34/results/» и «/polls/34/vote/» — они отобразят страницы результатов и голосования по умолчанию.

Когда кто-то запрашивает страницу с вашего сайта — скажем, «/polls/34/», Django загрузит модуль Python mysite.urls, потому что он указан в настройке 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.

Нет необходимости добавлять ненужные символы URL, такие как .html — если только вы не хотите, в этом случае вы можете сделать что-то вроде этого:

path('polls/latest.html', views.index),

Но не делайте этого. Это глупо.

Напишите представления, которые действительно что-то делают

Каждое представление отвечает за выполнение одного из двух действий: возвращение объекта 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 backend, у которого параметр 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. Если это не удаётся, он пытается получить атрибут — что в данном случае работает. Если бы поиск по атрибуту также не увенчался успехом, он бы попытался найти элемент по индексу в списке.

Вызов методов происходит в цикле {% 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 %}?

Ответ заключается в добавлении пространств имён в вашу конфигурацию URL-адресов. В файле 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/3.0/intro/tutorial03/

Spec-Zone.ru

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