Введение в представления на основе классов
Представления на основе классов предлагают альтернативный способ реализации представлений как объектов 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-короткописи или 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().
Была добавлена возможность использования method_decorator() для класса и возможность принимать список или кортеж декораторов.
В этом примере каждый экземпляр ProtectedView будет иметь защиту от входа.
Примечание
method_decorator передает *args и **kwargs в качестве параметров декорированному методу в классе. Если ваш метод не принимает совместимый набор параметров, он вызовет исключение TypeError.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/topics/class-based-views/intro/