Spec-Zone.ru › Django 3.2

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

Представления на основе классов предоставляют альтернативный способ реализации представлений как объектов 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. Это означает, что объекты http shortcuts или TemplateResponse допустимы для использования внутри представления на основе класса.

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

Первый – стандартный способ 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.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.2/topics/class-based-views/intro/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API