Spec-Zone.ru › Django 5.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 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" — часть «книги» взята из имени приложения, определяющего модель, а часть «издатель» — это прописная версия имени модели.

Примечание

Таким образом, когда (например) опция 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.0/topics/class-based-views/generic-display/

Spec-Zone.ru

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