Использование миксинов с представлениями на основе классов ¶
Осторожность
Это сложная тема. Перед изучением этих методов рекомендуется ознакомиться с представлениями на основе классов в Django .
Встроенные представления на основе классов в Django предоставляют множество функций, но некоторые из них вы можете использовать отдельно. Например, вы можете захотеть написать представление, которое отображает шаблон для ответа HTTP, но вы не можете его использовать
TemplateView
; возможно, вам нужно рендерить шаблон только на POST
, GET
делая что-то совсем другое. Хотя вы можете использовать
TemplateResponse
напрямую, это, скорее всего, приведет к дублированию кода.
По этой причине Django также предоставляет ряд миксинов, которые обеспечивают более дискретную функциональность. Отрисовка шаблона, например, инкапсулируется в
TemplateResponseMixin
. Справочная документация Django содержит полную документацию по всем миксинам .
Ответы по контексту и шаблону ¶
Предоставляются два центральных миксина, которые помогают обеспечить согласованный интерфейс для работы с шаблонами в представлениях на основе классов.
TemplateResponseMixin
Каждое встроенное представление, которое возвращает,
TemplateResponse
будет вызыватьrender_to_response()
метод, которыйTemplateResponseMixin
предоставляет. В большинстве случаев он будет вызываться для вас (например, он вызываетсяget()
методом, реализованным обоимиTemplateView
иDetailView
); аналогично, маловероятно, что вам понадобится его переопределить, хотя, если вы хотите, чтобы ваш ответ возвращал что-то, не отображаемое с помощью шаблона Django, вы захотите это сделать. Пример этого см. В примере JSONResponseMixin .render_to_response()
сам вызываетget_template_names()
, который по умолчанию будет искать в представленииtemplate_name
на основе классов; два других миксина (SingleObjectTemplateResponseMixin
иMultipleObjectTemplateResponseMixin
) отменяют это, чтобы обеспечить более гибкие настройки по умолчанию при работе с реальными объектами.ContextMixin
- Каждое встроенное представление, которому требуются данные контекста, например, для рендеринга шаблона (в том числе
TemplateResponseMixin
выше), должно вызыватьget_context_data()
передачу любых данных, которые они хотят обеспечить, чтобы они присутствовали в качестве аргументов ключевого слова.get_context_data()
возвращает словарь; вContextMixin
нем он возвращает свои аргументы ключевого слова, но обычно это переопределено, чтобы добавить больше членов в словарь. Вы также можете использоватьextra_context
атрибут.
Создание общих представлений на основе классов Django ¶
Давайте посмотрим, как два общих представления на основе классов в Django построены из миксинов, обеспечивающих дискретную функциональность. Мы рассмотрим
DetailView
, который отображает «подробное» представление объекта и
ListView
который отображает список объектов, обычно из набора запросов, и, при необходимости, разбивает их на страницы. Это познакомит нас с четырьмя миксинами, которые обеспечивают полезную функциональность при работе либо с одним объектом Django, либо с несколькими объектами.
Также присутствуют миксины, участвующие в общих представлениях редактирования ( FormView
, и представлениях для конкретных моделей CreateView
,
UpdateView
и
DeleteView
), а также в общих представлениях на основе даты. Они описаны в справочной документации по миксинам .
DetailView
: работа с одним объектом Django ¶
Чтобы показать детали объекта, нам в основном нужно сделать две вещи: нам нужно найти объект, а затем нам нужно создать
TemplateResponse
с подходящим шаблоном и этот объект в качестве контекста.
Чтобы получить объект, DetailView
полагается на SingleObjectMixin
, который предоставляет
get_object()
метод, который определяет объект на основе URL-адреса запроса (он ищет pk
и slug
ключевые аргументы, как объявлено в URLConf, и ищет объект либо из
model
атрибута в представлении, или
queryset
атрибут, если он предусмотрен). SingleObjectMixin
также переопределяет
get_context_data()
, который используется во всех встроенных представлениях Django на основе классов для предоставления контекстных данных для отрисовки шаблонов.
Чтобы затем создать TemplateResponse
,
DetailView
использует
SingleObjectTemplateResponseMixin
, который расширяется TemplateResponseMixin
, переопределяя,
get_template_names()
как описано выше. На самом деле он предоставляет довольно сложный набор опций, но основная, которую собираются использовать большинство людей, - это
<app_label>/<model_name>_detail.html
. _detail
Часть может быть изменена путем установки
template_name_suffix
на подклассе к чему - то еще. (Например, общие представления редактирования используются _form
для создания и обновления представлений, а также
_confirm_delete
для удаления представлений.)
ListView
: работа со многими объектами Django ¶
Списки объектов следуют примерно той же схеме: нам нужен (возможно, разбитый на страницы) список объектов, обычно a
QuerySet
, а затем нам нужно создать a
TemplateResponse
с подходящим шаблоном, используя этот список объектов.
Чтобы получить объекты, ListView
использует
MultipleObjectMixin
, который предоставляет как
get_queryset()
и
paginate_queryset()
. В отличие от with SingleObjectMixin
, нет необходимости отключать части URL-адреса, чтобы определить набор запросов для работы, поэтому по умолчанию используется
атрибут queryset
или
model
в классе представления. Распространенной причиной переопределения
get_queryset()
здесь было бы динамическое изменение объектов, например, в зависимости от текущего пользователя или исключение сообщений в будущем для блога.
MultipleObjectMixin
также переопределяет
get_context_data()
включение соответствующих переменных контекста для разбивки на страницы (предоставляя пустышки, если разбивка на страницы отключена). Он полагается на то, object_list
что передается в качестве аргумента ключевого слова, который его ListView
устраивает.
Чтобы сделать TemplateResponse
,
ListView
затем использует
MultipleObjectTemplateResponseMixin
; как и SingleObjectTemplateResponseMixin
выше, это переопределяет, get_template_names()
чтобы предоставить наиболее часто используемому существу
, чтобы часть снова была взята из
атрибута. (Общие представления на основе даты используют суффиксы, такие как ,
и т. Д., Чтобы использовать разные шаблоны для различных специализированных представлений списков на основе даты.)a range of
options
<app_label>/<model_name>_list.html
_list
template_name_suffix
_archive
_archive_year
Использование миксинов представлений на основе классов Django ¶
Теперь мы увидели, как общие представления на основе классов Django используют предоставленные миксины, давайте посмотрим, как мы можем их комбинировать. Мы по-прежнему собираемся комбинировать их либо со встроенными представлениями на основе классов, либо с другими общими представлениями на основе классов, но есть ряд более редких проблем, которые вы можете решить, чем это предусмотрено Django из коробки.
Предупреждение
Не все миксины можно использовать вместе, и не все представления на основе общих классов можно использовать со всеми другими миксинами. Здесь мы представляем несколько действительно работающих примеров; если вы хотите объединить другие функции, вам нужно будет рассмотреть взаимодействия между атрибутами и методами, которые перекрываются между различными классами, которые вы используете, и то, как порядок разрешения методов повлияет на то, какие версии методов будут вызываться в каком порядке.
Справочная документация для Джанго класса на основе взглядов и зрение Mixins основы классов поможет вам понять , какие атрибуты и методы, вероятно, причина конфликта между различными классами и Mixins.
Если есть сомнения, часто лучше отступить и основать свою работу на
View
или TemplateView
, возможно, на
SingleObjectMixin
и
MultipleObjectMixin
. Хотя вы, вероятно, в конечном итоге напишете больше кода, он, скорее всего, будет ясно понятным для кого-то, кто придет к нему позже, и с меньшим количеством взаимодействий, о которых нужно беспокоиться, вы сэкономите немного времени на размышления. (Конечно, вы всегда можете погрузиться в реализацию общих представлений на основе классов в Django, чтобы узнать, как решать проблемы.)
Использование SingleObjectMixin
с View ¶
Если мы хотим написать представление на основе классов, которое реагирует только на POST
, мы создадим подкласс View
и напишем post()
метод в подклассе. Однако, если мы хотим, чтобы наша обработка работала с конкретным объектом, идентифицированным по URL-адресу, нам потребуются функциональные возможности, предоставляемые
SingleObjectMixin
.
Мы продемонстрируем это с помощью Author
модели, которую мы использовали во введении в
общие представления на основе классов .
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
class RecordInterestView(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
# Look up the author we're interested in.
self.object = self.get_object()
# Actually record interest somehow here!
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
На практике вы, вероятно, захотите записывать интерес в хранилище значений ключей, а не в реляционной базе данных, поэтому мы оставили этот бит. Единственная часть представления, о которой нужно беспокоиться,
SingleObjectMixin
- это то, где мы хотим найти интересующего нас автора, что происходит с помощью вызова
self.get_object()
. Обо всем остальном позаботится миксин.
Мы можем достаточно легко подключить это к нашим URL-адресам:
from django.urls import path
from books.views import RecordInterestView
urlpatterns = [
#...
path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
]
Обратите внимание на pk
именованную группу, которая
get_object()
используется для поиска Author
экземпляра. Вы также можете использовать slug или любую другую функцию
SingleObjectMixin
.
Использование SingleObjectMixin
с ListView
¶
ListView
предоставляет встроенную разбивку на страницы, но вы можете захотеть разбить на страницы список объектов, которые все связаны (внешним ключом) с другим объектом. В нашем примере публикации вы можете захотеть пролистать все книги определенного издателя.
Один из способов сделать это - объединить ListView
с
SingleObjectMixin
, чтобы набор запросов для разбитого на страницы списка книг мог зависнуть от издателя, найденного как единственный объект. Для этого нам нужно иметь два разных набора запросов:
Book
набор запросов для использованияListView
- Так как мы имеем доступ к
Publisher
чьим книгам мы хотим список, мы переопределитьget_queryset()
и использоватьPublisher
«s обратного внешнего ключа менеджера . Publisher
набор запросов для использования вget_object()
- Мы будем полагаться на реализацию по умолчанию,
get_object()
чтобы получить правильныйPublisher
объект. Однако нам нужно явно передатьqueryset
аргумент, потому что в противном случае реализация по умолчаниюget_object()
вызовет,get_queryset()
который мы переопределили, чтобы возвращатьBook
объекты вместоPublisher
них.
Примечание
Надо хорошенько подумать get_context_data()
. Поскольку оба SingleObjectMixin
и
ListView
будут помещать вещи в данные контекста под значением
context_object_name
if, мы вместо этого явно гарантируем, что это Publisher
находится в данных контекста. ListView
добавим подходящие page_obj
и paginator
для нас, если мы не забудем позвонить super()
.
Теперь мы можем написать новый PublisherDetailView
:
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
class PublisherDetailView(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['publisher'] = self.object
return context
def get_queryset(self):
return self.object.book_set.all()
Обратите внимание на то, как мы установили self.object
внутри, get()
чтобы мы могли использовать его позже в get_context_data()
и get_queryset()
. Если вы не установите template_name
этот параметр, по умолчанию будет ListView
выбран обычный шаблон
, который в данном случае будет заключаться в том,
"books/book_list.html"
что это список книг;
ListView
ничего не знает
SingleObjectMixin
, поэтому он не имеет ни малейшего представления о том, что это представление имеет какое-либо отношение к Publisher
.
В paginate_by
этом примере преднамеренно маленький размер, поэтому вам не нужно создавать много книг, чтобы увидеть, как работает нумерация страниц! Вот шаблон, который вы хотите использовать:
{% extends "base.html" %}
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
Избегайте чего-то более сложного ¶
Как правило, их можно использовать
TemplateResponseMixin
и
SingleObjectMixin
тогда, когда они вам нужны. Как было показано выше, с немного ухода вы можете даже комбинировать SingleObjectMixin
с
ListView
. Однако по мере того, как вы пытаетесь это сделать, все становится все сложнее, и хорошее практическое правило:
Намекать
Каждое из ваших представлений должно использовать только миксины или представления из одной из групп общих представлений на основе классов: детали, список , редактирование и дата. Например, можно комбинировать
TemplateView
(встроенный вид) с
MultipleObjectMixin
(общим списком), но у вас, вероятно, возникнут проблемы с объединением SingleObjectMixin
(общие детали) с MultipleObjectMixin
(общим списком).
Чтобы показать, что происходит, когда вы пытаетесь стать более сложным, мы показываем пример, в котором жертвуют удобочитаемостью и ремонтопригодностью, когда есть более простое решение. Во-первых, давайте посмотрим на наивную попытку объединить
DetailView
с,
FormMixin
чтобы позволить нам
POST
Django Form
по тому же URL-адресу, который мы отображаем с помощью объекта DetailView
.
Использование FormMixin
с DetailView
¶
Вернемся к нашему предыдущему примеру использования View
и
SingleObjectMixin
вместе. Мы фиксировали интерес пользователя к конкретному автору; скажите сейчас, что мы хотим, чтобы они оставили сообщение о том, почему они им нравятся. Опять же, давайте предположим, что мы собираемся хранить это не в реляционной базе данных, а в чем-то более эзотерическом, о чем мы не будем здесь беспокоиться.
На этом этапе естественно обратиться к a, Form
чтобы инкапсулировать информацию, отправляемую из браузера пользователя в Django. Скажем также, что мы активно инвестируем в REST , поэтому мы хотим использовать тот же URL-адрес для отображения автора, что и для получения сообщения от пользователя. Давайте перепишем наш AuthorDetailView
для этого.
Мы сохраним GET
обработку от DetailView
, хотя нам придется добавить Form
в данные контекста, чтобы мы могли отобразить его в шаблоне. Мы также захотим получить обработку формы FormMixin
и написать немного кода, чтобы POST
форма вызывалась соответствующим образом.
Примечание
Мы используем FormMixin
и реализовать
post()
себя , а не пытаться смешивать DetailView
с
FormView
(который обеспечивает подходящую post()
уже) , так как взгляды реализации get()
, и все было бы получить гораздо более запутанным.
Наша новинка AuthorDetailView
выглядит так:
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.
from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetailView(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
# Here, we would record the user's interest using the message
# passed in form.cleaned_data['message']
return super().form_valid(form)
get_success_url()
предоставляет место для перенаправления, которое используется в реализации по умолчанию form_valid()
. post()
Как отмечалось ранее, мы должны предоставить свои собственные .
Лучшее решение ¶
Количество тонких взаимодействий между
FormMixin
и DetailView
уже проверяет нашу способность управлять вещами. Вряд ли вы захотите написать такой класс самостоятельно.
В этом случае вы могли бы написать post()
метод самостоятельно, сохранив
DetailView
единственную общую функциональность, хотя написание
Form
кода обработки требует большого количества дублирования.
В качестве альтернативы, было бы все равно меньше работы, чем вышеупомянутый подход, чтобы иметь отдельное представление для обработки формы, которое можно было бы использовать
FormView
отдельно от
DetailView
без проблем.
Альтернативное лучшее решение ¶
На самом деле мы пытаемся использовать два разных представления на основе классов с одного и того же URL-адреса. Так почему бы не сделать именно это? У нас здесь очень четкое разделение: GET
запросы должны получать
DetailView
(с Form
добавленными в контекст данные), а POST
запросы должны получать FormView
. Давайте сначала настроим эти представления.
Представление AuthorDetailView
почти такое же, как когда мы впервые представили AuthorDetailView ; мы должны написать свой собственный, get_context_data()
чтобы сделать его
AuthorInterestForm
доступным для шаблона. Мы пропустим
get_object()
предыдущее переопределение для ясности:
from django import forms
from django.views.generic import DetailView
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = AuthorInterestForm()
return context
Тогда AuthorInterestForm
это a FormView
, но мы должны ввести,
SingleObjectMixin
чтобы мы могли найти автора, о котором говорим, и мы должны не забыть установить, template_name
чтобы гарантировать, что ошибки формы будут отображать тот же шаблон, AuthorDetailView
который используется на GET
:
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
class AuthorInterestFormView(SingleObjectMixin, FormView):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
Наконец, мы сводим это воедино в новом AuthorView
взгляде. Мы уже знаем, что вызов представления as_view()
на основе классов дает нам что-то, что ведет себя точно так же, как представление на основе функций, поэтому мы можем сделать это в момент выбора между двумя подпредставлениями.
Вы можете передать аргументы ключевого слова так
as_view()
же, как и в вашем URLconf, например, если бы вы хотели, чтобы AuthorInterestFormView
поведение также отображалось на другом URL-адресе, но с использованием другого шаблона:
from django.views import View
class AuthorView(View):
def get(self, request, *args, **kwargs):
view = AuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = AuthorInterestFormView.as_view()
return view(request, *args, **kwargs)
Этот подход также можно использовать с любыми другими общими представлениями на основе классов или вашими собственными представлениями на основе классов, наследующими напрямую от
View
или TemplateView
, поскольку он сохраняет различные представления как можно более раздельными.
Больше, чем просто HTML ¶
Представления на основе классов лучше всего подходят тогда, когда вы хотите делать одно и то же много раз. Предположим, вы пишете API, и каждое представление должно возвращать JSON вместо визуализированного HTML.
Мы можем создать класс миксина для использования во всех наших представлениях, обработав преобразование в JSON один раз.
Например, миксин JSON может выглядеть примерно так:
from django.http import JsonResponse
class JSONResponseMixin:
"""
A mixin that can be used to render a JSON response.
"""
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
return JsonResponse(
self.get_data(context),
**response_kwargs
)
def get_data(self, context):
"""
Returns an object that will be serialized as JSON by json.dumps().
"""
# Note: This is *EXTREMELY* naive; in reality, you'll need
# to do much more complex handling to ensure that arbitrary
# objects -- such as Django model instances or querysets
# -- can be serialized as JSON.
return context
Примечание
Ознакомьтесь с документацией по сериализации объектов Django для получения дополнительной информации о том, как правильно преобразовать модели и наборы запросов Django в JSON.
Этот миксин предоставляет render_to_json_response()
метод с той же сигнатурой, что и render_to_response()
. Чтобы использовать его, нам нужно смешать его, TemplateView
например, и вместо этого переопределить
render_to_response()
вызов render_to_json_response()
:
from django.views.generic import TemplateView
class JSONView(JSONResponseMixin, TemplateView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
Точно так же мы могли бы использовать наш миксин с одним из общих представлений. Мы можем создать нашу собственную версию DetailView
, смешав ее
JSONResponseMixin
с
BaseDetailView
- (
DetailView
было добавлено поведение до рендеринга шаблона):
from django.views.generic.detail import BaseDetailView
class JSONDetailView(JSONResponseMixin, BaseDetailView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
Затем это представление можно развернуть так же, как и любое другое
DetailView
, с точно таким же поведением - за исключением формата ответа.
Если вы хотите быть по-настоящему смелым, вы можете даже смешать
DetailView
подкласс, который может возвращать содержимое как HTML, так и JSON, в зависимости от некоторого свойства HTTP-запроса, такого как аргумент запроса или HTTP-заголовок. Смешайте и
JSONResponseMixin
и
SingleObjectTemplateResponseMixin
, и переопределите реализацию,
render_to_response()
чтобы отложить до соответствующего метода рендеринга в зависимости от типа ответа, запрошенного пользователем:
from django.views.generic.detail import SingleObjectTemplateResponseMixin
class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
if self.request.GET.get('format') == 'json':
return self.render_to_json_response(context)
else:
return super().render_to_response(context)
Из-за того, как Python разрешает перегрузку методов, вызов в
super().render_to_response(context)
заканчивается тем, что вызывает
render_to_response()
реализацию TemplateResponseMixin
.