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

Примечание

Таким образом, когда (например) параметр APP_DIRS бэкенда DjangoTemplates установлен в значение True в TEMPLATES, расположение шаблона может быть: /путь/к/проекту/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.0/topics/class-based-views/generic-display/

Spec-Zone.ru

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