Spec-Zone.ru › Django 3.2

Создание вашей первой 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 загрузит модуль mysite.urls Python, потому что он указан настройкой 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.

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

Каждое представление отвечает за выполнение одного из двух действий: возвращение объекта 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).

END_OF_DOCUMENT_MARKER

Функция 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 %}?

Ответ заключается в добавлении пространств имен в ваш 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>

на указание на пространственное представление detail:

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.2/intro/tutorial03/

Spec-Zone.ru

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