Не существует задач, для которых базовые представления не могут существенно ускорить разработку. Тем не менее, в большинстве проектов наступает момент когда базовые представления перестают удовлетворять текущим требованиям. Действительно, наиболее общим вопросом, задаваемым новыми адептами Django, является «как заставить базовые представления обрабатывать широкий спектр ситуаций».
К счастью почти в каждом из этих случаев существуют способы простого расширения функциональности базовых представлений для обработки различных ситуаций. Эти ситуации обычно попадают под шаблоны, которые мы рассмотрим далее в этой главе.
Вы могли отметить, что тестовый шаблон списка издателей хранит
все книги в переменной object_list
. Пока
всё работает прекрасно, не всё так «дружественно»
по отношению к авторам шаблона: они должны «просто знать», что в данном случае они работают с
книгами. Лучшим именем для этой переменной будет
publisher_list
, тогда содержимое переменной
более очевидно.
Мы можем легко изменить имя этой переменной с помощью
аргумента template_object_name
:
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info)
)
Предоставлять эту полезную переменную всегда является хорошей идеей. Ваши коллеги, которые занимаются разработкой шаблонов, будут вам благодарны.
Часто вам просто требуется предоставить немного больше информации, чем это позволяет базовое представление. Например, подумайте об отображении списка всех других издателей на каждой странице определённого издателя. Базовое представление object_detail предоставляет имя издателя для контекста, но кажется, что нет способа для этого шаблона получить список всех издателей.
Но это не так: все базовые представления могут принимать дополнительный параметр — extra_context. Этим параметром является словарь дополнительных объектов, которые будет добавлен в контекст шаблона. Таким образом, для передачи списка всех издателей в представление мы должны использовать словарь, подобный этому:
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : {"book_list" : Book.objects.all()}
}
Этот код заполнит переменную {{ book_list }}
в контексте шаблона. Этот способ может быть
использован для передачи любой информации в шаблон базового
представления. Это очень удобно.
Тем не менее, здесь есть малозаметная ошибка — можете её найти?
Проблема проявляется в момент вычисления запросов в extra_context. Так как этот пример является фрагментом файла со схемой URL, то Book.objects.all()[13] будет вычислен только однажды, при первой загрузке данного файла. После того, как вы добавите или удалите издателя, вы обнаружите, что базовое представление не отражает эти изменения до момента перезагрузки веб сервера (см. "Caching and QuerySets" в приложении «Справочник по API взаимодействия с базой данных» для получения большего объёма информации по вопросам кэширования и вычисления QuerySet).
Замечание
Эта проблема не затрагивает аргумент queryset базового представления. Так как Django знает, что этот набор данных никогда не должен подвергаться кэшированию, базовое представление берёт на себя задачу очистки кэша после каждого использования представления.
Решение заключается в использовании обработчика (callback) в extra_context вместо значения. Любая функция, передаваемая extra_context, будет вычислена при каждом вызове представления, а не один раз. Вы можете сделать это с помощью явно определённой функции:
def get_books():
return Book.objects.all()
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : {"book_list" : get_books}
}
или можно использовать менее очевидную, но более краткую версию, которая использует факт того, что Book.objects.all сам является функцией:
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : {"book_list" : Book.objects.all}
}
Следует отметить отсутствие скобок после Book.objects.all. Таким образом мы ссылаемся на функцию, не производя её вызов (вызов делает базовое представление позже).
Пришло время рассмотреть этот queryset, который мы используем уже давно. Большинство базовых представлений принимают такой аргумент — именно так представление узнает, какой набор объектов надо отобразить (определение наборов данных было дано в главе «Выборка объектов», а в приложении «Справочник по API взаимодействия с базой данных» приведены все подробности).
В качестве примера получим список книг отсортированный по дате издания, самые новые книги должны быть сначала списка:
book_info = {
"queryset" : Book.objects.all().order_by("-publication_date"),
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
(r'^books/$', list_detail.object_list, book_info),
)
Это достаточно простой пример, но отлично иллюстрирует идею. Естественно, что вам обычно потребуется нечто большее, чем просто сортировка объектов. Если вам нужно отобразить список книг, отфильтровав их по определённому издателю, вы можете использовать такую же методику:
apress_books = {
"queryset": Book.objects.filter(publisher__name="Apress Publishing"),
"template_name" : "books/apress_list.html"
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
(r'^books/apress/$', list_detail.object_list, apress_books),
)
Следует отметить, что совместно с отсортированным queryset мы также используем своё имя для шаблона. Если мы не будем так делать, то базовое представление будет использовать тот же шаблон как «vanilla FIXME» список объектов, что может не совпадать с вашими желаниями.
Также следует отметить, что это не самый элегантный способ получения списка книг, отсортированных по издателю. Если нам потребуется добавить другую страницу для издателя, то придётся добавить ещё одну привязку URL, а если издателей будет гораздо больше? Мы рассмотрим эту задачу в следующей секции.
Замечание
Если вы получите ошибку 404 при обращении к books/apress/, удостоверьтесь, что у вас действительно есть издатель с именем «Apress Publishing». Базовые представления имеют параметр allow_empty как раз для этого случая. Подробности смотрите в приложении «Справочник по базовым представлениям».
Часто бывает необходимо реализовать фильтрацию объектов по какому-либо ключу из URL. Ранее мы жёстко прописывали имя издателя в привязку URL, но что делать, если нам требуется создать представление, которое отображает все книги одного определённого издателя? Мы можем «обернуть» базовое представление object_list для того, чтобы не писать много кода вручную. Как обычно, мы начнём с файла привязок:
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
(r'^books/(\w+)/$', books_by_publisher),
)
Затем, следует написать представление books_by_publisher:
from django.http import Http404
from django.views.generic import list_detail
from mysite.books.models import Book, Publisher
def books_by_publisher(request, name):
# Ищем издателя (и вызываем 404, если ничего не нашли).
try:
publisher = Publisher.objects.get(name__iexact=name)
except Publisher.DoesNotExist:
raise Http404
# Используем представление object_list view.
return list_detail.object_list(
request,
queryset = Book.objects.filter(publisher=publisher),
template_name = "books/books_by_publisher.html",
template_object_name = "books",
extra_context = {"publisher" : publisher}
)
Это работает потому что в базовых представлениях нет ничего особенного — это простые функции языка Python. Подобно
любой другой функции представления, базовые представления
принимают определённый набор аргументов и возвращают объекты
HttpResponse
. Следовательно, невероятно
просто оборачивать небольшие функции вокруг базового
представления, которые выполняют дополнительную работу перед
(или после, см. следующую секцию) передачи управления базовому
представлению.
Замечание
Следует отметить, что в предыдущем примере мы передавали текущего издателя, который был указан в extra_context. Это положительный аспект поведения обёрток. Они позволяют шаблонам знать о родительском объекте.
Последняя задача, которую мы рассмотрим в этой главе, — как выполнять некоторую обработку данных до или после вызова базового представления.
Представьте, что у нас есть поле last_accessed
в объекте Author
, которое мы используем
для хранения информации о времени последнего доступа к
информации об авторе. Базовое представление
object_detail, естественно, ничего не знает об
этом поле, но как и раньше мы можем легко создать отдельное
представление для обработки этого поля.
Сначала, нам надо добавить соответствующую привязку URL, которая будет указывать на наше представление:
from mysite.books.views import author_detail
urlpatterns = patterns('',
#...
(r'^authors/(?P<author_id>\d+)/$', author_detail),
)
Затем, пишем обработчик:
import datetime
from mysite.books.models import Author
from django.views.generic import list_detail
from django.shortcuts import get_object_or_404
def author_detail(request, author_id):
# Получаем объект или вызываем ошибку 404.
author = get_object_or_404(Author, pk=author_id)
# Записываем текущую дату.
author.last_accessed = datetime.datetime.now()
author.save()
# Отображаем страницу.
return list_detail.object_detail(
request,
queryset = Author.objects.all(),
object_id = author_id,
)
Замечание
Этот код не будет работать пока вы не добавите поле
last_accessed к модели
Author
и не создадите шаблон
books/author_detail.html
.
Мы можем использовать аналогичный подход для изменения отклика, который возвращён базовым представлением. Если нам потребуется предоставить скачиваемую текстовую версию списка авторов, мы можем использовать такое представление:
def author_list_plaintext(request):
response = list_detail.object_list(
request,
queryset = Author.objects.all(),
mimetype = "text/plain",
template_name = "books/author_list.txt"
)
response["Content-Disposition"] = "attachment; filename=authors.txt"
return response
Это работает, потому что базовое представление возвращает
простые объекты HttpResponse
, которые
могут рассматриваться как словари при установке HTTP
заголовков. Заголовок Content-Disposition,
между прочим, указывает браузеру, что поток данных следует
скачать и сохранить, вместо отображения в окне браузера.
Пред. | Уровень выше | След. |
Базовые представления объектов | Начало | Глава 10. Расширения для шаблонной системы |
5 comments | Make a comment
Говоря о template_object_name. Не совсем очевиден пункт из документации:
The view will append '_list' to the value of this parameter in determining the variable's name.
То есть, для меня без обращения к докам было неочевидным, что при указании имени объекта шаблона 'publisher', в шаблоне нужно указывать publisher_list.
answer to strider
Говоря о template_object_name. Не совсем очевиден пункт из документации:
The view will append '_list' to the value of this parameter in determining the variable's name.
То есть, для меня без обращения к докам было неочевидным, что при указании имени объекта шаблона 'publisher', в шаблоне нужно указывать publisher_list.
Без обращения к докам вам еще будет много чего не очевидно. Через IDE легко можно перейти на код object_list и посмотреть как заполняется context. Это как альтернатива документации.
вместо:
def get_books():
return Book.objects.all()
...
"extra_context" : {"book_list" : get_books}
...
лучше:
"extra_context" : {"book_list" : lambda: Book.objects.all() }
answer to Te0reTiK
вместо:
def get_books():
return Book.objects.all()
...
"extra_context" : {"book_list" : get_books}
...
лучше:
"extra_context" : {"book_list" : lambda: Book.objects.all() }
Чем? Строку сэкономили? Зато функция инкапсулирует получение книг и ее можно использовать в разных местах.
answer to alerion
Чем? Строку сэкономили? Зато функция инкапсулирует получение книг и ее можно использовать в разных местах.
И как это будет выглядеть?
from myproject.myapp.urls import get_books
...
лямбда все таки посимпотичнее будет