Представления, основанные на классах, представляют собой альтернативный путь реализации представлений. Вместо функций здесь используются объекты Python. Этот способ не лучше и не хуже, он другой. Вот основные отличия:
GET, POST, и т.д.) разнесена по соответствующим методам, вместо того, чтобы писать кучу условий.В самом начале были только функции: Django передавал ей HttpRequest и ожидал получения HttpResponse. Это всё, что было предусмотрено.
Ранее были найдены общие шаблоны в разработке представлений. Обобщённые представления, основанные на функциях, были выделены в абстрактные шаблоны, подходящие для большинства случаев.
The problem with function-based generic views is that while they covered the simple cases well, there was no way to extend or customize them beyond some configuration options, limiting their usefulness in many real-world applications.
Представления, основанные на классах, были созданы для решения той же задачи, что и обобщённые функции-представления: упростить разработку. Однако, реализовано это было с помощью примесей: наборов классов, которые делают обобщённые представления-классы более расширяемыми и гибкими в настройке, нежели соответствующие функции.
If you have tried function based generic views in the past and found them lacking, you should not think of class-based generic views as a class-based equivalent, but rather as a fresh approach to solving the original problems that generic views were meant to solve.
The toolkit of base classes and mixins that Django uses to build class-based
generic views are built for maximum flexibility, and as such have many hooks in
the form of default method implementations and attributes that you are unlikely
to be concerned with in the simplest use cases. For example, instead of
limiting you to a class-based attribute for form_class, the implementation
uses a get_form method, which calls a get_form_class method, which in
its default implementation returns the form_class attribute of the class.
This gives you several options for specifying what form to use, from an
attribute, to a fully dynamic, callable hook. These options seem to add hollow
complexity for simple situations, but without them, more advanced designs would
be limited.
По своей сути представления, основанные на классах, позволяют реагировать на различные методы 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')
Because Django’s URL resolver expects to send the request and associated
arguments to a callable function, not a class, class-based views have an
as_view() class method which returns a
function that can be called when a request arrives for a URL matching the
associated pattern. The function creates an instance of the class, calls
setup() to initialize its attributes, and
then calls its dispatch() method.
dispatch looks at the request to determine whether it is a GET,
POST, etc, and relays the request to a matching method if one is defined,
or raises HttpResponseNotAllowed if not:
# 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`ов.
Примеси представляют собой форму множественного наследования, в котором поведение и атрибуты всех родителей соединены в один класс.
Например, в Django есть примесь под названием TemplateResponseMixin, основной задачей которого является определение метода render_to_response(). Когда она будет объединена с поведением базового класса View, получится класс TemplateView, который обрабатывает запрос соответствующим методом (реализация находится в классе View), а затем передаётся в render_to_response(), который, используя атрибут template_name, возвращает объект TemplateResponse (реализовано в TemplateResponseMixin).
Примеси - великолепный путь для повторного использования общего кода, однако у них есть некоторые минусы. Чем больше реализация методов будет размазана по примесям, тем сложнее будет читать дочерний класс и понимать что же он всё-таки делает, какие методы откуда берутся и какие методы уже перекрыты в родительских классах.
Стоит отметить, что можно унаследоваться только от одного обобщённого класса, т.к. каждый из них наследуется от 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})
This is a minimal case, but you can see that you would then have the option
of customizing this view by overriding any of the class attributes, e.g.
form_class, via URLconf configuration, or subclassing and overriding one or
more of the methods (or both!).
Расширение представлений-классов не ограничивается использованием миксинов, также можно подключить декораторы. Так как представления-классы не являются функциями, то работа с ними несколько отличается в зависимости от того вызываете ли вы as_view() или создаёте дочерний класс.
You can adjust class-based views by decorating the result of the
as_view() method. The easiest place to do
this is in the URLconf where you deploy your view:
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())),
]
Этот подход работает только когда нужно обернуть только один вызов, в противном случае следует действовать по-другому.
Для декорирования каждого метода представления-класса, вам нужно было бы обернуть каждый метод. В Django это сделано проще: надо всего лишь декорировать метод 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().
In this example, every instance of ProtectedView will have login
protection. These examples use login_required, however, the same behavior
can be obtained by using
LoginRequiredMixin.
Примечание
method_decorator передаёт *args и **kwargs как параметры в декорированный метод. Если ваш метод не сможет их принять, то будет сгенерировано исключение TypeError.
июн. 04, 2020