Встроенные универсальные представления на основе классов

Написание веб-приложений может быть однообразным, потому что мы повторяем определенные шаблоны снова и снова. Django пытается убрать часть этой монотонности на уровнях модели и шаблона, но веб-разработчики также испытывают скуку на уровне представления.

Общие представления Django были разработаны, чтобы облегчить эту боль. Они берут определенные общие идиомы и шаблоны, найденные при разработке представлений, и абстрагируют их, чтобы вы могли быстро писать общие представления данных без необходимости писать слишком много кода.

Мы можем распознавать определенные общие задачи, такие как отображение списка объектов, и писать код, отображающий список любого объекта. Затем рассматриваемую модель можно передать как дополнительный аргумент в URLconf.

Django поставляется с общими представлениями для выполнения следующих действий:

  • Отображение списка и страниц сведений для одного объекта. Если бы мы создавали приложение для управления конференциями, то a TalkListViewи a 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"- часть «книги» происходит от имени приложения, которое определяет модель, а бит «издатель» - это версия имени модели в нижнем регистре.

Примечание

Таким образом, когда (например) для APP_DIRSпараметра DjangoTemplates backend установлено значение True in 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 = Publisherqueryset = Publisher.objects.all()querysetQuerySet

Чтобы выбрать пример, мы можем захотеть упорядочить список книг по дате публикации, начиная с самых последних:

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, и более чем несколько издателей станут неразумными. Мы рассмотрим эту проблему в следующем разделе.

Примечание

Если при запросе вы получаете ошибку 404 /books/acme/, убедитесь, что у вас действительно есть издатель с именем «ACME Publishing». У универсальных представлений есть allow_emptyпараметр для этого случая. См. Дополнительную информацию в справочнике по представлениям на основе классов .

Динамическая фильтрация

Другая распространенная потребность - отфильтровать объекты, указанные на странице списка, по некоторому ключу в URL-адресе. Ранее мы жестко указали имя издателя в URLconf, но что, если бы мы хотели написать представление, которое отображало бы все книги какого-то произвольного издателя?

Удобно, что у ListViewнего есть get_queryset()метод, который мы можем переопределить. По умолчанию он возвращает значение querysetатрибута, но мы можем использовать его для добавления дополнительной логики.

Ключевой частью этой работы является то, что при вызове представлений на основе классов хранятся различные полезные вещи self; а также request ( self.request) он включает аргументы positional ( self.args) и name-based ( 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 в представлении.

Copyright ©2021 All rights reserved