Spec-Zone.ru › Django 2.2

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

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

Обзор

Представление — это «тип» веб-страницы в вашем приложении Django, которое, как правило, выполняет определённую функцию и имеет определённую шаблонную разметку. Например, в приложении блога у вас могут быть следующие представления:

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

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

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

В Django веб-страницы и другой контент предоставляются представлениями. Каждое представление представлено простой функцией Python (или методом, в случае представлений на основе классов). Django выберет представление, проанализировав запрашиваемый URL (точнее, часть URL после доменного имени).

Теперь в своём интернет-путешествии вы, возможно, столкнулись с такими красотами, как «ME2/Sites/dirmod.asp?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() и отобразит любой 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, где опция APP_DIRS установлена в значение True. По соглашению DjangoTemplates ищет поддиректорию «templates» в каждом из приложений, перечисленных в INSTALLED_APPS.

Внутри директории templates, которую вы только что создали, создайте ещё одну директорию с именем polls, и внутри неё создайте файл с именем index.html. Другими словами, ваш шаблон должен быть в polls/templates/polls/index.html. Благодаря тому, как работает механизм загрузки шаблонов app_directories, в Django к этому шаблону можно обратиться просто как к polls/index.html.

Именование шаблонов

Теперь мы возможно сможем обойтись без создания отдельной поддиректории polls, поместив шаблоны непосредственно в 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 для использования шаблона:

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» из Учебника 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, если вопрос с запрошенным ID не существует.

Мы обсудим, что вы можете поместить в этот 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/2.2/intro/tutorial03/

Spec-Zone.ru

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