Spec-Zone.ru › Django 4.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 в родительском классе. Когда два класса не пытаются определить одну и ту же метку, это даст ожидаемые результаты. Однако, если какой-либо класс пытается переопределить метку после того, как родительские классы установили ее (после вызова супер), любые дочерние классы этого класса также должны явно установить ее после супер, если они хотят быть уверены, что переопределили всех родителей. Если у вас возникнут проблемы, изучите порядок разрешения методов вашего представления.

Еще один момент — данные контекста из классовых обобщенных представлений будут переопределять данные, предоставляемые обработчиками контекста; см. 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.1/topics/class-based-views/generic-display/

Spec-Zone.ru

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