Встроенные обобщенные представления на основе классов
Написание веб-приложений может быть монотонным, потому что мы постоянно повторяем одни и те же шаблоны. Django пытается уменьшить эту монотонность на уровнях модели и шаблонов, но веб-разработчики также испытывают это чувство скуки на уровне представлений.
Обобщенные представления Django были разработаны, чтобы облегчить эту задачу. Они берут определенные распространенные выражения и шаблоны, используемые при разработке представлений, и абстрагируют их, чтобы вы могли быстро создавать общие представления данных, не написав слишком много кода.
Мы можем распознать определенные общие задачи, такие как отображение списка объектов, и написать код, отображающий список любых объектов. Затем соответствующая модель может быть передана как дополнительный аргумент в URLconf.
Django поставляется с обобщенными представлениями для выполнения следующих действий:
- Отображение страниц списка и деталей для одного объекта. Если мы создаем приложение для управления конференциями, то
TalkListViewиRegisteredUserListViewбудут примерами представлений списка. Страница одной доклада — пример того, что мы называем представлением «деталей». - Представление объектов, зависящих от даты, на страницах архива по годам/месяцам/дням, связанных страницах деталей и страницах «последних».
- Разрешить пользователям создавать, обновлять и удалять объекты — с или без авторизации.
В совокупности эти представления обеспечивают удобные интерфейсы для выполнения наиболее распространенных задач, с которыми сталкиваются разработчики.
Расширение обобщенных представлений
Нет сомнений в том, что использование обобщенных представлений существенно ускоряет разработку. Однако в большинстве проектов наступает момент, когда обобщенных представлений уже недостаточно. Действительно, наиболее часто задаваемый вопрос начинающих разработчиков Django — как сделать обобщенные представления обрабатывают более широкий спектр ситуаций.
Это одна из причин, по которой обобщенные представления были переработаны для выпуска 1.3 — ранее они были просто функциями представлений с ошеломляющим количеством параметров; теперь, вместо того, чтобы передавать большое количество параметров конфигурации в URLconf, рекомендуемый способ расширения обобщенных представлений — наследование от них и переопределение их атрибутов или методов.
Тем не менее, обобщенные представления будут иметь предел. Если вы обнаружите, что вам трудно реализовать ваше представление как подкласс обобщенного представления, то, возможно, вам будет более эффективно написать только необходимый вам код, используя собственные представления на основе классов или функциональные представления.
Дополнительные примеры обобщенных представлений доступны в некоторых сторонних приложениях, или вы можете написать свои собственные по мере необходимости.
Обобщенные представления объектов
TemplateView определенно полезен, но обобщенные представления Django действительно блещут при отображении представлений содержимого вашей базы данных. Поскольку это очень распространенная задача, Django поставляется с несколькими встроенными обобщенными представлениями, которые делают создание представлений списка и деталей объектов невероятно простыми.
Давайте начнем с рассмотрения некоторых примеров отображения списка объектов или одного объекта.
Мы будем использовать следующие модели:
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
def __str__(self): # __unicode__ on Python 2
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
Теперь нам нужно определить представление:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
Наконец, подключите это представление к вашим URL:
# urls.py
from django.conf.urls import url
from books.views import PublisherList
urlpatterns = [
url(r'^publishers/$', PublisherList.as_view()),
]
Это весь Python-код, который нам нужно написать. Однако нам все еще нужно написать шаблон. Мы можем явно указать представлению, какой шаблон использовать, добавив атрибут template_name к представлению, но в отсутствие явного шаблона Django определит шаблон по имени объекта. В этом случае предполагаемым шаблоном будет "books/publisher_list.html" — часть «books» происходит от имени приложения, определяющего модель, а часть «publisher» — просто нижний регистр названия модели.
Примечание
Таким образом, когда (например) опция APP_DIRS бэкэнда DjangoTemplates установлена в значение True в TEMPLATES, расположение шаблона может быть: /path/to/project/books/templates/books/publisher_list.html
Этот шаблон будет рендериться с контекстом, содержащим переменную под названием object_list, которая содержит все объекты издателей. Очень простой шаблон может выглядеть следующим образом:
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
Это все, что нужно. Все интересные возможности обобщенных представлений основаны на изменении атрибутов, заданных на обобщенном представлении. В документации по обобщенным представлениям подробно описаны все обобщенные представления и их параметры; в этой документации будут рассмотрены некоторые распространенные способы настройки и расширения обобщенных представлений.
Создание «дружественных» контекстов шаблонов
Возможно, вы заметили, что в нашем примере шаблона списка издателей все издатели хранятся в переменной под названием object_list. Хотя это работает, это не очень «дружелюбно» для авторов шаблонов: им нужно «просто знать», что здесь обрабатываются издатели.
Ну, если вы работаете с объектом модели, это уже сделано за вас. Когда вы имеете дело с объектом или набором запросов, Django может заполнить контекст, используя нижний регистр имени класса модели. Это предоставляется в дополнение к стандартной записи object_list, но содержит точно такие же данные, то есть publisher_list.
Если этого все равно недостаточно, вы можете вручную установить имя переменной контекста. Атрибут context_object_name на обобщенном представлении задает переменную контекста для использования:
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favorite_publishers'
Предоставление полезного context_object_name всегда хорошая идея. Ваши коллеги, которые разрабатывают шаблоны, вас поблагодарят.
Добавление дополнительного контекста
Часто вам просто нужно предоставить дополнительную информацию, помимо той, которая предоставляется обобщенным представлением. Например, подумайте об отображении списка всех книг на каждой странице деталей издателя. Обобщенное представление DetailView предоставляет издателя контексту, но как получить дополнительную информацию в этом шаблоне?
Ответ — создать подкласс DetailView и предоставить собственную реализацию метода get_context_data. По умолчанию реализация просто добавляет отображаемый объект в шаблон, но вы можете переопределить его, чтобы отправить больше данных:
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
Примечание
В целом, get_context_data будет объединять данные контекста всех родительских классов с данными текущего класса. Чтобы сохранить это поведение в собственных классах, где вы хотите изменить контекст, вы должны убедиться, что вызывается get_context_data в родительском классе. Если ни один из классов не пытается определить одну и ту же ключевую пару, это даст ожидаемые результаты. Однако, если какой-либо класс пытается переопределить ключ после того, как родительские классы его установили (после вызова super), все дочерние классы этого класса также должны явно установить его после super, если они хотят быть уверены в переопределении всех родителей. Если у вас возникли проблемы, обратите внимание на порядок разрешения методов вашего представления.
Еще один момент — данные контекста на основе классов обобщенных представлений переопределяют данные, предоставляемые обработчиками контекста; см. get_context_data() для примера.
Просмотр подмножеств объектов
Теперь давайте посмотрим более подробно на аргумент model, который мы использовали все это время. Аргумент model, указывающий модель базы данных, с которой будет работать представление, доступен во всех обобщенных представлениях, которые работают с одним объектом или набором объектов. Однако аргумент model — не единственный способ указать объекты, с которыми будет работать представление — вы также можете указать список объектов, используя аргумент queryset:
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetail(DetailView):
context_object_name = 'publisher'
queryset = Publisher.objects.all()
Указание model = Publisher — это просто сокращение для queryset = Publisher.objects.all(). Однако, используя queryset для определения отфильтрованного списка объектов, вы можете более точно указать объекты, которые будут отображаться в представлении (см. Создание запросов для получения дополнительной информации об объектах QuerySet и см. справочник по представлениям на основе классов для получения всех подробностей).
В качестве простого примера, мы можем упорядочить список книг по дате публикации, начиная с самых последних:
from django.views.generic import ListView
from books.models import Book
class BookList(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'
Это довольно простой пример, но он хорошо иллюстрирует идею. Конечно, вам обычно нужно сделать больше, чем просто переупорядочить объекты. Если вы хотите отобразить список книг определенного издателя, вы можете использовать ту же технику:
from django.views.generic import ListView
from books.models import Book
class AcmeBookList(ListView):
context_object_name = 'book_list'
queryset = Book.objects.filter(publisher__name='ACME Publishing')
template_name = 'books/acme_list.html'
Обратите внимание, что вместе с отфильтрованным queryset, мы также используем пользовательское имя шаблона. Если бы мы этого не сделали, обобщенное представление использовало бы тот же шаблон, что и «обычный» список объектов, что может нам не подойти.
Также обратите внимание, что это не очень элегантный способ получить книги определенного издателя. Если мы хотим добавить другую страницу издателя, нам нужно добавить несколько строк в URLconf, а у нескольких издателей это будет неудобно. Мы решим эту проблему в следующем разделе.
Примечание
Если при запросе /books/acme/ появляется ошибка 404, проверьте, действительно ли у вас есть издатель с именем «ACME Publishing». В этом случае обобщенные представления имеют параметр allow_empty. См. справочник по представлениям на основе классов для получения дополнительной информации.
Динамическая фильтрация
Еще одна распространенная необходимость — отфильтровать объекты на странице списка по какому-либо ключу в URL. Ранее мы жестко кодировали имя издателя в URLconf, но что, если мы хотим написать представление, отображающее все книги конкретного издателя?
У обобщенного представления ListView есть метод get_queryset(), который мы можем переопределить. Раньше он просто возвращал значение атрибута queryset, но теперь мы можем добавить дополнительную логику.
Ключевой момент для работы заключается в том, что при вызове представлений на основе классов различные полезные вещи хранятся в self; а также в запросе (self.request) это включает позиционные (self.args) и основанные на имени (self.kwargs) аргументы, полученные в соответствии с URLconf.
Здесь у нас есть URLconf с одной захваченной группой:
# urls.py
from django.conf.urls import url
from books.views import PublisherBookList
urlpatterns = [
url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
]
Далее мы напишем само представление PublisherBookList:
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookList(ListView):
template_name = 'books/books_by_publisher.html'
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.args[0])
return Book.objects.filter(publisher=self.publisher)
Как вы видите, достаточно легко добавить больше логики в выборку набора данных; если мы захотим, мы можем использовать self.request.user для фильтрации по текущему пользователю или другой более сложной логике.
Мы также можем добавить издателя в контекст одновременно, чтобы мы могли использовать его в шаблоне:
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherBookList, self).get_context_data(**kwargs)
# Add in the publisher
context['publisher'] = self.publisher
return context
Выполнение дополнительных задач
Последний распространённый шаблон, который мы рассмотрим, связан с выполнением дополнительных задач до или после вызова представления на основе шаблонов.
Представьте, что у нас есть поле last_accessed в нашей модели Author модели, которое мы использовали для отслеживания последнего времени, когда кто-либо просматривал этого автора:
# models.py
from django.db import models
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
last_accessed = models.DateTimeField()
Общие представления DetailView класса, конечно, ничего не знали бы об этом поле, но ещё раз мы могли бы легко написать пользовательское представление, чтобы обновлять это поле.
Сначала нам нужно добавить часть детали автора в URLconf, чтобы указать на пользовательское представление:
from django.conf.urls import url
from books.views import AuthorDetailView
urlpatterns = [
#...
url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
]
Затем мы напишем наше новое представление — get_object — метод, который извлекает объект — поэтому мы просто переопределяем его и оборачиваем вызов:
from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
# Call the superclass
object = super(AuthorDetailView, self).get_object()
# Record the last accessed date
object.last_accessed = timezone.now()
object.save()
# Return the object
return object
Примечание
URLconf здесь использует именованную группу pk — это имя по умолчанию, которое DetailView использует для поиска значения первичного ключа, используемого для фильтрации набора данных.
Если вы хотите назвать группу по-другому, вы можете установить pk_url_kwarg в представлении. Более подробную информацию можно найти в справке для DetailView
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/