Spec-Zone.ru › Django 4.0

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

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

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

Если у вас возникнут проблемы с этим руководством, перейдите к разделу Получение помощи раздела 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 использует то, что называется «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. Двоеточие (:) отделяет преобразователь и имя шаблона.

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

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

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.0/intro/tutorial03/

Spec-Zone.ru

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