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

This tutorial begins where Tutorial 2 left off. We’re continuing the web-poll application and will focus on creating the public interface – «views.»

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

Если у вас возникли проблемы с просмотром этого учебника, перейдите в раздел Получение справки FAQ.

Быстрый обзор

A view is a «type» of web page in your Django application that generally serves a specific function and has a specific template. For example, in a blog application, you might have the following views:

  • Домашняя страница блога - отображает последние записи.
  • Страница «Детализация» - отдельная страница одной записи.
  • Страница архива на основе года - отображает все месяцы с записями в данном году.
  • Страница архива на основе месяца - отображает все дни с записями в данном месяце.
  • Дневная архивная страница - отображает все записи за данный день.
  • Действия с комментариями - обрабатывает размещение комментариев к данной записи.

В нашем приложении для опроса будут следующие четыре представления:

  • Главная страница вопросов - отображает последние несколько вопросов.
  • Страница вопроса - отображает текст вопроса, без результатов, но с формой для голосования.
  • Страница результатов вопроса - отображает результаты для конкретного вопроса.
  • Голосования - обрабатывает голосование за определенный выбор в конкретном вопросе.

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

Когда кто-то запрашивает страницу с вашего сайта - скажем, «/polls/34/», Django загрузит модуль Python mysite.urls, поскольку на него указывает параметр ROOT_URLCONF. Он находит переменную с именем `` urlpatterns`` и просматривает шаблоны по порядку. После нахождения соответствия в 'polls/' ``, он убирает соответствующий текст («polls/») и отправляет оставшийся текст - ``"34/" - в „polls.urls“ URLconf для дальнейшей обработки. Там он соответствует '<int:question_id>/', что приводит к вызову представления detail() следующим образом:

detail(request=<HttpRequest object>, question_id=34)

The question_id=34 part comes from <int:question_id>. Using angle brackets «captures» part of the URL and sends it as a keyword argument to the view function. The question_id part of the string defines the name that will be used to identify the matched pattern, and the int part is a converter that determines what patterns should match this part of the URL path. The colon (:) separates the converter and pattern name.

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

Каждое представление отвечает за выполнение одного из двух действий: возвращение объекта HttpResponse, содержащего ответ для запрашиваемой страницы, или создание исключения, такого как Http404. Остальное зависит от вас.

Ваше представление может читать записи из базы данных, или нет. Оно может использовать систему шаблонов, такую как Django или стороннюю систему шаблонов Python, или нет. Он может генерировать PDF-файл, выводить XML, создавать ZIP-файл на лету, что угодно, используя любые библиотеки Python, которые вы хотите.

Все, чего хочет Джанго, это HttpResponse. Или исключение.

Поскольку это удобно, давайте использовать собственное API базы данных Django, который мы рассмотрели в Tutorial 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. Джанго будет искать там шаблоны.

Параметр 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/», и вы увидите маркированный список, содержащий вопрос «What’s up» из Tutorial 2. Ссылка указывает на страницу с подробностями вопроса.

Сокращение render()

Это очень распространенная идиома: загрузить шаблон, заполнить контекст и вернуть объект HttpResponse с результатом визуализации шаблона. Джанго предоставляет сокращение. Вот полное представление 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`` и``voice``).

Функция 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, если объект не существует. Джанго предоставляет сокращение функции. Вот представление 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() нашего приложения poll. Учитывая переменную контекста 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 %}?

Ответ заключается в том, чтобы добавить пространство имен в ваш 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 этого урока, чтобы узнать основы обработки форм и общих представлений.

Вернуться на верх