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

Представления на основе классов предоставляют альтернативный способ реализации представлений как объектов Python вместо функций. Они не заменяют функциональные представления, но имеют определенные отличия и преимущества по сравнению с функциональными представлениями:

  • Организация кода, связанного с конкретными HTTP-методами ( GET, POSTи т. Д.), Может быть решена отдельными методами вместо условного перехода.
  • Объектно-ориентированные методы, такие как миксины (множественное наследование), могут использоваться для разделения кода на повторно используемые компоненты.

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

Вначале был только контракт функции просмотра, Django передал вашу функцию HttpRequestи ожидал возврата HttpResponse. Это был предел того, что предоставил Django.

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

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

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

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

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

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

По своей сути, представление на основе классов позволяет вам отвечать на различные методы HTTP-запроса с помощью разных методов экземпляра класса, а не с помощью кода условного ветвления внутри одной функции представления.

Итак, где код для обработки HTTP GETв функции просмотра будет выглядеть примерно так:

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

В представлении на основе классов это будет:

from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

Поскольку преобразователь URL-адресов Django ожидает отправки запроса и связанных аргументов вызываемой функции, а не классу, представления на основе as_view()классов имеют метод класса, который возвращает функцию, которая может быть вызвана при поступлении запроса на URL-адрес, соответствующий соответствующему шаблону. Функция создает экземпляр класса, вызывает setup()для инициализации его атрибуты, а затем вызывает его dispatch()метод. dispatchсмотрит на запрос, чтобы определить, является ли он GET, POSTи т. д., и передает запрос методу сопоставления, если он определен, или вызывает, HttpResponseNotAllowedесли нет:

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('about/', MyView.as_view()),
]

Стоит отметить, что то, что возвращает ваш метод, идентично тому, что вы возвращаете из функционального представления, а именно некоторой формы HttpResponse. Это означает, что ярлыки или TemplateResponseобъекты http допустимы для использования внутри представления на основе классов.

В то время как минимальное представление на основе классов не требует каких-либо атрибутов класса для выполнения своей работы, атрибуты класса полезны во многих проектах на основе классов, и есть два способа настроить или установить атрибуты класса.

Первый - это стандартный способ Python для создания подклассов и переопределения атрибутов и методов в подклассе. Итак, если у вашего родительского класса был такой атрибут greeting:

from django.http import HttpResponse
from django.views import View

class GreetingView(View):
    greeting = "Good Day"

    def get(self, request):
        return HttpResponse(self.greeting)

Вы можете переопределить это в подклассе:

class MorningGreetingView(GreetingView):
    greeting = "Morning to ya"

Другой вариант - настроить атрибуты класса в качестве аргументов ключевого слова для as_view()вызова в URLconf:

urlpatterns = [
    path('about/', GreetingView.as_view(greeting="G'day")),
]

Примечание

Хотя ваш класс создается для каждого отправленного ему запроса, атрибуты класса, установленные через as_view()точку входа, настраиваются только один раз во время импорта ваших URL-адресов.

Использование миксинов

Примеси - это форма множественного наследования, при которой поведение и атрибуты нескольких родительских классов можно комбинировать.

Например, в общих представлениях на основе классов есть вызываемая примесь TemplateResponseMixin, основная цель которой - определить метод render_to_response(). В сочетании с поведением View базового класса результатом является TemplateView класс, который будет отправлять запросы соответствующим методам сопоставления (поведение, определенное в Viewбазовом классе), и у которого есть render_to_response() метод, который использует template_name атрибут для возврата TemplateResponse объекта (поведение определено в TemplateResponseMixin).

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

Также обратите внимание, что вы можете наследовать только от одного общего представления, то есть только один родительский класс может наследовать от, Viewа остальные (если есть) должны быть миксинами. Попытка наследовать более чем от одного класса, который наследуется от View- например, попытка использовать форму в верхней части списка и объединение ProcessFormViewи ListView- не будет работать должным образом.

Обработка форм с помощью представлений на основе классов

Базовое функциональное представление, обрабатывающее формы, может выглядеть примерно так:

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm

def myview(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')
    else:
        form = MyForm(initial={'key': 'value'})

    return render(request, 'form_template.html', {'form': form})

Аналогичное представление на основе классов может выглядеть так:

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from .forms import MyForm

class MyFormView(View):
    form_class = MyForm
    initial = {'key': 'value'}
    template_name = 'form_template.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form': form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')

        return render(request, self.template_name, {'form': form})

Это минимальный случай, но вы можете видеть, что тогда у вас будет возможность настроить это представление, переопределив любой из атрибутов класса, например form_class, через конфигурацию URLconf, или создав подкласс и переопределив один или несколько методов (или оба!) .

Украшать взгляды на основе классов

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

Декорирование в URLconf

Вы можете настроить представления на основе классов, украсив результат as_view()метода. Проще всего это сделать в URLconf, где вы развертываете свое представление:

from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView

from .views import VoteView

urlpatterns = [
    path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
    path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
]

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

Украшение класса

Чтобы украсить каждый экземпляр представления на основе классов, вам необходимо украсить само определение класса. Для этого вы применяете декоратор к dispatch()методу класса.

Метод в классе не совсем то же самое, что и отдельная функция, поэтому вы не можете просто применить декоратор функции к методу - вам нужно сначала преобразовать его в декоратор метода. method_decoratorДекоратора превращает функцию декоратора в метод декоратора , так что он может быть использован на метод экземпляра. Например:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

class ProtectedView(TemplateView):
    template_name = 'secret.html'

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

Или, более кратко, вы можете вместо этого украсить класс и передать имя метода, который нужно оформить, в качестве аргумента ключевого слова name:

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

Если у вас есть набор общих декораторов, используемых в нескольких местах, вы можете определить список или кортеж декораторов и использовать его вместо method_decorator()многократного вызова . Эти два класса эквивалентны:

decorators = [never_cache, login_required]

@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

Декораторы будут обрабатывать запрос в том порядке, в котором они передаются декоратору. В этом примере never_cache()запрос будет обработан раньше login_required().

В этом примере у каждого экземпляра ProtectedViewбудет защита входа в систему. В этих примерах используется login_required, однако такое же поведение можно получить, используя LoginRequiredMixin.

Примечание

method_decoratorпередает *argsи в **kwargs качестве параметров декорированному методу класса. Если ваш метод не принимает совместимый набор параметров, он вызовет TypeErrorисключение.

Copyright ©2021 All rights reserved