Использование миксинов с представлениями на основе классов

Осторожность

Это сложная тема. Перед изучением этих методов рекомендуется ознакомиться с представлениями на основе классов в 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_listtemplate_name_suffix_archive_archive_year

Использование миксинов представлений на основе классов Django

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

Предупреждение

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

Справочная документация для Джанго класса на основе взглядов и зрение Mixins основы классов поможет вам понять , какие атрибуты и методы, вероятно, причина конфликта между различными классами и Mixins.

Если есть сомнения, часто лучше отступить и основать свою работу на Viewили TemplateView, возможно, на SingleObjectMixinи MultipleObjectMixin. Хотя вы, вероятно, в конечном итоге напишете больше кода, он, скорее всего, будет ясно понятным для кого-то, кто придет к нему позже, и с меньшим количеством взаимодействий, о которых нужно беспокоиться, вы сэкономите немного времени на размышления. (Конечно, вы всегда можете погрузиться в реализацию общих представлений на основе классов в Django, чтобы узнать, как решать проблемы.)

Использование SingleObjectMixinс View

Если мы хотим написать представление на основе классов, которое реагирует только на POST, мы создадим подкласс Viewи напишем post()метод в подклассе. Однако, если мы хотим, чтобы наша обработка работала с конкретным объектом, идентифицированным по URL-адресу, нам потребуются функциональные возможности, предоставляемые SingleObjectMixin.

Мы продемонстрируем это с помощью Authorмодели, которую мы использовали во введении в общие представления на основе классов .

views.py
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-адресам:

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

Copyright ©2021 All rights reserved