Создание первого приложения на Django, часть 3¶
Этот учебник продолжает вас знакомить с Django с места, где мы остановились в Tutorial 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 использует так называемый URLconfs. URLconf сопоставляет шаблоны URL с представлениями.
Этот учебник содержит основные инструкции по использованию URLconfs, и вы можете обратиться к Диспетчер 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()
и отобразит любой идентификатор, который вы указали в URL. Попробуйте также «/polls/34/results/» и «/polls/34/vote/» - они отобразят результаты и страницы голосования.
Когда кто-то запрашивает страницу с вашего сайта - скажем, «/polls/34/», Django загрузит модуль Python mysite.urls
, поскольку на него указывает параметр ROOT_URLCONF
. Он находит переменную с именем `` urlpatterns`` и просматривает шаблоны по порядку. После нахождения соответствия в 'polls/' ``, он убирает соответствующий текст (
«polls/») и отправляет оставшийся текст - ``"34/"
- в „polls.urls“ URLconf для дальнейшей обработки. Там он соответствует '<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, которые вы хотите.
Все, чего хочет Джанго, это HttpResponse
. Или исключение.
Поскольку это удобно, давайте использовать собственное API базы данных Django, который мы рассмотрели в Tutorial 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
. Джанго будет искать там шаблоны.
Параметр 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/», и вы увидите маркированный список, содержащий вопрос «What’s up» из Tutorial 2. Ссылка указывает на страницу с подробностями вопроса.
Сокращение render()
¶
Это очень распространенная идиома: загрузить шаблон, заполнить контекст и вернуть объект HttpResponse
с результатом визуализации шаблона. Джанго предоставляет сокращение. Вот полное представление 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`` и``voice``).
Функция 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
, если вопрос с запрошенным идентификатором не существует.
Мы обсудим, что вы можете вставить в этот шаблон polls/detail.html
чуть позже, но если вы хотите быстро получить приведенный выше пример, файл, содержит только:
{{ question }}
для начала.
Сокращение get_object_or_404()
¶
Это очень распространенная практика: использовать get()
и вызывать Http404
, если объект не существует. Джанго предоставляет сокращение функции. Вот представление 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()
нашего приложения poll. Учитывая переменную контекста 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 %}
?
Ответ заключается в том, чтобы добавить пространство имен в ваш URLconf. В файле 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 руководства, чтобы узнать о простой обработке форм и общих представлениях.