Spec-Zone.ru › Django 5.2

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

Написание веб-приложений может быть монотонным, потому что мы снова и снова повторяем определенные шаблоны. Django пытается устранить часть этой монотонности на уровнях модели и шаблонов, но веб-разработчики также испытывают скуку на уровне представлений.

Обобщенные представления Django были разработаны, чтобы облегчить эту задачу. Они берут некоторые распространенные идиомы и шаблоны, используемые при разработке представлений, и абстрагируют их, так что вы можете быстро создавать общие представления данных, не написав слишком много кода.

Мы можем распознать определенные общие задачи, такие как отображение списка объектов, и написать код, который отображает список любых объектов. Затем интересующая модель может быть передана в качестве дополнительного аргумента в URLconf.

Django поставляется с обобщенными представлениями для выполнения следующих действий:

  • Отображение страниц списка и подробностей для одного объекта. Если бы мы создавали приложение для управления конференциями, то TalkListView и RegisteredUserListView были бы примерами представлений списка. Страница одной лекции является примером того, что мы называем представлением «подробностей».
  • Представление объектов, основанных на дате, на страницах архивов по году/месяцу/дню, связанных деталях и страницах «последних».
  • Разрешить пользователям создавать, обновлять и удалять объекты — с разрешением или без него.

Вместе эти представления предоставляют интерфейсы для выполнения наиболее распространенных задач, с которыми сталкиваются разработчики.

Расширение обобщенных представлений

Нет сомнений, что использование обобщенных представлений значительно ускоряет разработку. Однако в большинстве проектов наступает момент, когда обобщенные представления больше не подходят. Действительно, самый частый вопрос, задаваемый новичками-разработчиками Django, заключается в том, как заставить обобщенные представления обрабатывать более широкий круг ситуаций.

Это одна из причин, по которой обобщенные представления были переработаны для выпуска 1.3 — ранее они были функциями представлений с поразительным множеством вариантов; теперь, вместо того, чтобы передавать большое количество конфигурации в URLconf, рекомендуемый способ расширения обобщенных представлений — наследование от них и переопределение их атрибутов или методов.

Тем не менее, обобщенные представления будут иметь ограничения. Если вы обнаружите, что вам сложно реализовать свое представление как подкласс обобщенного представления, возможно, вам будет эффективнее написать только необходимый код, используя собственные представления на основе классов или функциональные представления.

Дополнительные примеры обобщенных представлений доступны в некоторых сторонних приложениях, или вы можете написать свои по мере необходимости.

Общие представления объектов

TemplateView определённо полезен, но джанговые общие представления по-настоящему сияют при представлении данных из вашей базы данных. Поскольку это очень распространённая задача, 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 PublisherListView(ListView):
    model = Publisher

Наконец, подключим это представление к вашим URL:

# urls.py
from django.urls import path
from books.views import PublisherListView

urlpatterns = [
    path("publishers/", PublisherListView.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 PublisherListView(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 PublisherDetailView(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 PublisherDetailView(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 BookListView(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 AcmeBookListView(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 PublisherBookListView

urlpatterns = [
    path("books/<publisher>/", PublisherBookListView.as_view()),
]

Далее мы напишем само представление PublisherBookListView:

# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher


class PublisherBookListView(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 в представлении.

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

Spec-Zone.ru

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