Введение в представления на основе классов
Представления на основе классов предлагают альтернативный способ реализации представлений как объектов 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/2.2/topics/class-based-views/intro/