Spec-Zone.ru › Django 3.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/3.2/topics/class-based-views/generic-display/

Spec-Zone.ru

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