Spec-Zone.ru › Django 1.11

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

Создание веб-приложений может быть монотонным, поскольку мы постоянно повторяем определенные шаблоны. 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.11/topics/class-based-views/generic-display/

Spec-Zone.ru

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