Введение в представления на основе классов
Представления на основе классов предлагают альтернативный способ реализации представлений в виде объектов 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.generic import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')
Поскольку решатель URL Django ожидает передачи запроса и связанных аргументов вызываемой функции, а не классу, представления на основе классов имеют метод класса as_view(), который служит точкой входа для вызова вашего класса. Точка входа as_view создает экземпляр вашего класса и вызывает его метод 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.generic 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.generic 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.9/topics/class-based-views/intro/