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