Spec-Zone.ru › Django 5.1

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

Написание веб-приложений может быть монотонным, потому что мы постоянно повторяем определенные шаблоны. 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" — часть «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.1/topics/class-based-views/generic-display/

Spec-Zone.ru

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