Spec-Zone.ru › Django 4.2

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

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

Spec-Zone.ru

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