Spec-Zone.ru › Django 2.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 PublisherList(ListView):
    model = Publisher

Наконец, подключите это представление к вашим URL:

# urls.py
from django.urls import path
from books.views import PublisherList

urlpatterns = [
    path('publishers/', PublisherList.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 PublisherList(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 PublisherDetail(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 PublisherDetail(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 BookList(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 AcmeBookList(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 PublisherBookList

urlpatterns = [
    path('books/<publisher>/', PublisherBookList.as_view()),
]

Далее мы напишем само представление PublisherBookList:

# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher

class PublisherBookList(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)

Как видите, добавить больше логики к выбору набора результатов довольно просто; если бы мы захотели, мы могли бы использовать 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):
        # Call the superclass
        object = super().get_object()
        # Record the last accessed date
        object.last_accessed = timezone.now()
        object.save()
        # Return the object
        return object

Примечание

URLconf здесь использует именованную группу pk — это имя по умолчанию, которое использует DetailView для поиска значения первичного ключа, используемого для фильтрации набора результатов.

Если вы хотите назвать группу по-другому, вы можете установить pk_url_kwarg в представлении. Более подробные сведения можно найти в справке по DetailView

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/

Spec-Zone.ru

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