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