Spec-Zone.ru › Django 3.0

Встроенные обобщённые представления на основе классов

Создание веб-приложений может быть монотонным, поскольку мы постоянно повторяем одни и те же шаблоны. 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):
        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):
        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.urls import path
from books.views import PublisherList

urlpatterns = [
    path('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 Book, Publisher

class PublisherDetail(DetailView):

    model = Publisher

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super().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.urls import path
from books.views import PublisherBookList

urlpatterns = [
    path('books/<publisher>/', 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.kwargs['publisher'])
        return Book.objects.filter(publisher=self.publisher)

Использование get_queryset для добавления логики к выбору набора данных так же удобно, как и мощно. Например, если бы мы захотели, мы могли бы использовать self.request.user для фильтрации по текущему пользователю или другой более сложной логике.

Мы также можем добавить издателя в контекст одновременно, чтобы мы могли использовать его в шаблоне:

# ...

def get_context_data(self, **kwargs):
    # Call the base implementation first to get a context
    context = super().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.urls import path
from books.views import AuthorDetailView

urlpatterns = [
    #...
    path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
]

Затем мы напишем наше новое представление — get_object — это метод, который извлекает объект, — поэтому мы переопределяем его и оборачиваем вызов:

from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author

class AuthorDetailView(DetailView):

    queryset = Author.objects.all()

    def get_object(self):
        obj = super().get_object()
        # Record the last accessed date
        obj.last_accessed = timezone.now()
        obj.save()
        return obj

Примечание

URLconf здесь использует именованную группу pk — это имя по умолчанию, которое DetailView использует для поиска значения первичного ключа, используемого для фильтрации набора данных.

Если вы хотите назвать группу по-другому, вы можете установить pk_url_kwarg в представлении. Более подробные сведения можно найти в справочнике по DetailView

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-display/

Spec-Zone.ru

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