Введение в представления на основе классов
Представления на основе классов предлагают альтернативный способ реализации представлений как объектов 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, не сработает должным образом.
Миксины, которые оборачивают as_view()
Один из способов применения общего поведения к многим классам — создание миксина, который оборачивает метод as_view().
Например, если у вас есть несколько обобщенных представлений, которые следует украсить декоратором login_required(), вы можете реализовать миксин следующим образом:
from django.contrib.auth.decorators import login_required
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
class MyView(LoginRequiredMixin, ...):
# this is a generic view
...
Обработка форм с помощью представлений на основе классов
Базовое представление на основе функции, обрабатывающее формы, может выглядеть примерно так:
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)
В этом примере каждый экземпляр ProtectedView будет иметь защиту от входа.
Примечание
method_decorator передает *args и **kwargs в качестве параметров декорированному методу в классе. Если ваш метод не принимает совместимый набор параметров, он вызовет исключение TypeError.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/class-based-views/intro/