Spec-Zone.ru › Django 1.9

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

Написание веб-приложений может быть монотонным, потому что мы снова и снова повторяем определённые шаблоны. 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, 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.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 Publishing». Обобщённые представления имеют параметр 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.9/topics/class-based-views/generic-display/

Spec-Zone.ru

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