Spec-Zone.ru › Django 2.1

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

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.

Нет необходимости добавлять ненужные 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/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 %}

Теперь обновим наше представление 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, если вопрос с запрошенным 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.1/intro/tutorial03/

Spec-Zone.ru

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