Spec-Zone.ru › Django 1.8

Встроенные классовые обобщённые представления

Написание веб-приложений может быть монотонным, так как мы постоянно повторяем одни и те же шаблоны. 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):              # __unicode__ on Python 2
        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):              # __unicode__ on Python 2
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField('Author')
    publisher = models.ForeignKey(Publisher)
    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.conf.urls import url
from books.views import PublisherList

urlpatterns = [
    url(r'^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 Publisher, Book

class PublisherDetail(DetailView):

    model = Publisher

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(PublisherDetail, self).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». В этом случае для обобщённых представлений используется параметр allow_empty. Для получения дополнительной информации см. справочник по классовым представлениям.

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

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

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

Ключевой момент в работе с представлением на основе классов заключается в том, что при вызове различных полезных вещей хранятся в self; а также в запросе (self.request) это включает позиционные (self.args) и основанные на именах (self.kwargs) аргументы, захваченные в соответствии с URLconf.

Здесь у нас есть URLconf с одной захваченной группой:

# urls.py
from django.conf.urls import url
from books.views import PublisherBookList

urlpatterns = [
    url(r'^books/([\w-]+)/$', 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.args[0])
        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(PublisherBookList, self).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.conf.urls import url
from books.views import AuthorDetailView

urlpatterns = [
    #...
    url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
]

Затем мы напишем наше новое представление – get_object — это метод, который извлекает объект – поэтому мы просто переопределяем его и оборачиваем вызов:

from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author

class AuthorDetailView(DetailView):

    queryset = Author.objects.all()

    def get_object(self):
        # Call the superclass
        object = super(AuthorDetailView, self).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/1.8/topics/class-based-views/generic-display/

Spec-Zone.ru

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