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

предосторожность

Это сложный предмет. Мы рекомендуем, чтобы у вас были хорошие предварительные знания о представлениях на основе классов 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() возвращает словарь; in ContextMixin он возвращает свои именованные параметры, но обычно это переопределено, чтобы добавить больше содержимого в этот словарь. Вы также можете использовать атрибут extra_context .

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

Давайте посмотрим, как два представления Django на основе классов построены из миксинов, которые обеспечивают изолированную функциональность. Мы рассмотрим, DetailView кто создает подробное представление объекта и ListView кто создает список объектов, обычно из набора запросов, с необязательной разбивкой на страницы. Это приведет нас к рассмотрению четырех миксинов, которые в сочетании обеспечивают полезную функциональность при обработке одного объекта Django или набора объектов.

Существуют также классы миксинов в общих представлениях редактирования ( FormView и представлениях для конкретных моделей CreateView , UpdateView и DeleteView ), а также в общих представлениях, ориентированных на дату. Эти классы описаны в справочной документации миксинов .

DetailView : обработка одного объекта Django

Чтобы отобразить детали объекта, в основном нужно сделать две вещи: получить объект, затем создать ответ, TemplateResponse используя соответствующий шаблон, передав объект в контексте.

Чтобы получить объект, DetailView зависит от того, SingleObjectMixin кто предоставляет метод get_object() , который найдет объект на основе URL-адреса запроса (он ищет именованные параметры pk и, slug как объявлено в конфигурации URL-адреса, и выбирает объект используя либо атрибут представления model , либо атрибут, queryset если он присутствует). SingleObjectMixin перегрузка get_context_data() , которая используется во всех представлениях на основе классов Django для предоставления контекстных данных для шаблонов отрисовки.

Чтобы получить ответ TemplateResponse , DetailView используйте SingleObjectTemplateResponseMixin , который расширяется TemplateResponseMixin , перегрузка, get_template_names() как описано выше. На самом деле он предоставляет большое количество сложных опций, но большинство людей будет использовать именно его <étiquette_application>/<nom_modèle>_detail.html . Эта часть _detail может быть изменена путем определения template_name_suffix другого атрибута в подклассе (например, общие представления редактирования используются _form для создания и обновления _confirm_delete представлений и для удаления представлений).

ListView : обработка нескольких объектов Django

Списки объектов следуют примерно той же модели: сначала получить список объектов (потенциально разбитых на страницы), обычно объект QuerySet , а затем создать ответ TemplateResponse с соответствующим шаблоном для использования этого списка объектов.

Чтобы получить предметы, ListView используйте MultipleObjectMixin who give both get_queryset() and paginate_queryset() . В отличие от этого SingleObjectMixin , нет необходимости идентифицировать элементы URL, чтобы определить, какой набор запросов обрабатывать; поведение по умолчанию - использовать атрибут queryset или model класс представления. Распространенной причиной необходимости перегрузки здесь get_queryset() является динамическая адаптация списка объектов, например, на основе текущего пользователя или исключение сообщений с датой в будущем в блоге.

MultipleObjectMixin также перегрузки get_context_data() для включения переменных контекста, подходящих для разбиения на страницы (с указанием пустых значений, если разбиение на страницы отключено). Он рассчитывает на наличие object_list в передаваемых именованных параметрах, о которых заботится ListView .

Чтобы сгенерировать ответ TemplateResponse , ListView используйте MultipleObjectTemplateResponseMixin ; как и SingleObjectTemplateResponseMixin выше, get_template_names() перегружен, чтобы предоставить наиболее часто используемое имя шаблона, в котором игра также исходит из атрибута (общие представления, сосредоточенные на датах, используют суффиксы, такие как и т. соответствующие различным представлениям специализированных списков дат).série d'options <étiquette_application>/<nom_modèle>_list.html _list template_name_suffix _archive _archive_year

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

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

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

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

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

В случае сомнений часто лучше упростить вещи на основе 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 RecordInterest(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 RecordInterest

urlpatterns = [
    #...
    path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
]

Обратите внимание на названную группу, pk которая будет использоваться get_object() для поиска экземпляра Author . Также возможно использование «slug» или любой другой функции SingleObjectMixin .

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

ListView включает разбиение на страницы, но, например, может быть желательно разбить на страницы список объектов, которые все связаны (внешним ключом) с другим объектом. В нашем примере публикации вы можете захотеть разбить на страницы все книги от определенного издателя.

Один из способов сделать это - объединить ListView с SingleObjectMixin , так что набор запросов к списку книг с разбивкой на страницы может зависеть от издателя, найденного как отдельный объект. Для этого необходимо иметь два разных набора запросов:

Набор запросов, Book используемыйListView
Поскольку у нас есть доступ к редактору Publisher книг, которые мы хотим отобразить, мы перегружаем get_queryset() и используем диспетчер обратных внешних ключей объекта Publisher .
Набор запросов, Publisher используемыйget_object()
Мы полагаемся на реализацию по умолчанию, get_object() чтобы получить правильный объект Publisher . Однако нам нужно явно передать параметр, queryset потому что в противном случае реализация по умолчанию get_object() вызовет, get_queryset() который мы перегружаем, чтобы возвращать объекты Book вместо объектов Publisher .

Заметка

С этим нужно быть осторожным get_context_data() . Поскольку оба класса SingleObjectMixin и ListView будут размещать элементы в данных контекста под значением context_object_name , если оно установлено, мы вместо этого явно убедимся, что объект Publisher находится в данных контекста. ListView позаботьтесь добавить контент page_obj и paginator адекватное, пока мы не забываем позвонить super() .

Теперь мы можем написать новое представление PublisherDetail :

from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher

class PublisherDetail(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 in, get() чтобы затем использовать его в get_context_data() и get_queryset() . Если вы не зададите template_name , имя шаблона примет значение по умолчанию, выбранное by 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 чтобы позволить Django POST формы , Form чтобы представить на тот же URL , который используется для отображения объекта с DetailView .

Использование FormMixin с DetailView

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

На этом этапе естественно использовать форму Form для хранения информации, отправляемой браузером пользователя в Django. Предполагая, что мы также придерживаемся принципов REST , мы хотели бы использовать тот же URL-адрес для публикации автора, что и для получения сообщения от пользователя. Давайте перепишем нашу точку зрения AuthorDetailView с учетом этого.

Мы продолжим обработку GET класса DetailView , даже если потребуется добавить форму Form в данные контекста, чтобы иметь возможность отображать ее в шаблоне. Мы также воспользуемся преимуществами обработки формы FormMixin и напишем код, чтобы при отправке POST форма создавалась соответствующим образом.

Заметка

Мы используем FormMixin и реализуем методы сами , post() а не пытаться смешивать его DetailView с FormView (который уже обеспечивает действенный метод post() ), поскольку оба этих взгляды реализовать get() и что бы добавить к путанице.

Вот как выглядит наше новое представление AuthorDetail :

# 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 AuthorDetail(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 как единственную универсальную функциональность, даже если написание кода обработки форм требует большого количества дублирования.

В противном случае, безусловно, более экономичным подходом, чем выше, было бы иметь отдельное представление для обработки формы, что позволило бы использовать FormView отдельно от DetailView .

Лучшее альтернативное решение

Фактически, мы пытаемся использовать два разных представления классов для одного и того же URL-адреса. Почему бы просто не сделать это? Разделение здесь очень четкое: запросы GET должны попадать в представление DetailView (с добавлением формы в данные контекста), а запросы - POST в представление FormView . Давайте сначала настроим эти два представления.

Вид AuthorDisplay почти такой же, как и во введении . Нам нужно написать наш собственный метод, 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 AuthorDisplay(DetailView):
    model = Author

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = AuthorInterestForm()
        return context

Тогда для AuthorInterest , это подкласс FormView , но нам нужно добавить к нему, SingleObjectMixin чтобы иметь возможность воздействовать на автора текущим действием, и не забудьте установить, template_name чтобы убедиться, что ошибки форм отображаются в том же шаблоне, который AuthorDisplay используется во время запроса 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 AuthorInterest(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})

Наконец, мы связываем все это вместе, создавая новый вид AuthorDetail . Мы уже знаем, что вызов в представлении as_view() на основе классов возвращает объект, который ведет себя точно так же, как представление функционального типа. Таким образом, мы можем сделать этот вызов, когда нам нужно будет выбрать между двумя «вспомогательными представлениями».

Вы можете передать аргументы ключевого слова так as_view() же, как и в вашем URLconf, например, если вы хотите, чтобы AuthorInterest поведение также отображалось на другом URL, но с использованием другого шаблона:

from django.views import View

class AuthorDetail(View):

    def get(self, request, *args, **kwargs):
        view = AuthorDisplay.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        view = AuthorInterest.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 с django.views.generic.detail.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 ©2020 All rights reserved