Spec-Zone.ru › Django 2.0

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

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

Обзор

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

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

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

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

В 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. Эти представления немного отличаются, потому что они принимают аргумент:

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():

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

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

Вставьте следующий код в этот шаблон:

{% 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 для использования шаблона:

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(), переписанное:

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

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

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

{{ question }}

позволит вам начать работу.

Сокращение: get_object_or_404()

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

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.

<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 для установки пространства имён приложения:

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 с:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

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

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

Spec-Zone.ru

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