Общие представления на основе классов, предоставляемые Django

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

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

Вы можете определить некоторые общие задачи, такие как отображение списка объектов, и написать код, отображающий список любого объекта. Затем затронутый шаблон можно передать в качестве дополнительного параметра в конфигурации URL-адреса.

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

  • Отображение списка и страниц сведений для одного типа объекта. Если бы мы создали приложение для управления конференциями, представление TalkListView и представление RegisteredUserListView были бы примерами представлений списков. Отдельная страница конференции может быть примером того, что мы называем «подробным» представлением.
  • Отображение датированных объектов на страницах архива по годам / месяцам / дням, со связанными деталями и страницами "недавних элементов".
  • Разрешить пользователям создавать, обновлять и удалять объекты с управлением разрешениями или без него.

Взятые вместе, эти представления предоставляют интерфейсы для выполнения наиболее распространенных задач, с которыми сталкиваются разработчики.

Расширение общих представлений

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

Это одна из причин, по которой общие представления были пересмотрены для версии 1.3; раньше это были функции просмотра с очень широким набором опций. Теперь, вместо того, чтобы передавать большой кусок конфигурации в конфигурацию URL-адреса, рекомендуемый способ расширения общих представлений - создать их подклассы и переопределить их атрибуты и методы.

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

Вы найдете больше примеров общих представлений в некоторых сторонних приложениях, но не стесняйтесь писать свои собственные представления по мере необходимости.

Общие виды объектов

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" , часть «книги» происходит от имени приложения, содержащего этот шаблон, а «издатель» - от имени шаблона в нижнем регистре.

Заметка

Поэтому , когда (например) вариант APP_DIRS мотора DjangoTemplates определяется True в TEMPLATES месте шаблона может быть: /chemin/vers/projet/books/templates/books/publisher_list.html .

Этот шаблон будет отображен с контекстом, содержащим переменную, называемую самим собой, object_list содержащую объекты Publisher . Шаблон может выглядеть так:

{% 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 ), любой дочерний класс этого класса также должен будет явно определить этот ключ после того, super как обязательно переопределит все. родители. Если это вызывает у вас проблемы, проанализируйте порядок разрешения методов в вашем представлении.

Также следует отметить, что данные контекста из общих представлений на основе классов будут перезаписывать данные, предоставленные обработчиками контекста; см. get_context_data() как пример.

Отображение подмножеств объектов

Теперь 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 отфильтрованному параметру мы также указываем имя настраиваемого шаблона. Без этого в общем представлении использовался бы тот же шаблон, что и в «стандартном» списке объектов, что не соответствовало бы намеченному назначению.

Также обратите внимание, что это не очень элегантный способ отображения книг конкретного издателя. Если бы мы затем захотели добавить страницу для другого издателя, нам пришлось бы добавить несколько дополнительных строк в конфигурацию URL, и мы были бы в значительной степени ограничены несколькими редакторами. Мы обсудим эту проблему в следующем разделе.

Заметка

Если при доступе вы получаете ошибку 404 /books/acme/ , убедитесь, что у вас есть объект Publisher с именем «ACME Publishing». В общих представлениях есть настройки allow_empty для этой ситуации. Подробнее см. В справочнике по представлениям на основе классов .

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

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

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

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

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

# 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)

Использование 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 класс ничего не знает об этом поле, но мы снова можем написать собственное представление, чтобы это поле обновлялось.

Во-первых, нам нужно добавить строку сведений об авторе в конфигурацию URL-адреса для ссылки на настраиваемое представление:

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

Заметка

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

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

Copyright ©2020 All rights reserved