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