Введение в представления на основе классов
Представления на основе классов предлагают альтернативный способ реализации представлений в качестве объектов 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, соответствующий ассоциированному шаблону. Функция создает экземпляр класса и вызывает его метод dispatch(). dispatch проверяет запрос, чтобы определить, является ли он GET, POST, и т.д., и передает запрос соответствующему методу, если он определен, или вызывает HttpResponseNotAllowed, если нет:
# urls.py
from django.conf.urls import url
from myapp.views import MyView
urlpatterns = [
url(r'^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 = [
url(r'^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 = [
url(r'^about/$', login_required(TemplateView.as_view(template_name="secret.html"))),
url(r'^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(ProtectedView, self).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 будет иметь защиту от входа.
Примечание
method_decorator передает *args и **kwargs в качестве параметров декорированному методу в классе. Если ваш метод не принимает совместимый набор параметров, он вызовет исключение TypeError.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/topics/class-based-views/intro/