Введение в представления на основе классов

Другой способ реализовать представления объектами Python вместо функций - это представления на основе классов. Они не заменяют представления, основанные на функциях, но имеют определенные отличия и преимущества перед представлениями на основе функций:

  • Организация кода, относящаяся к конкретным методам HTTP ( GET и POST т. Д.), Может быть разделена методами вместо использования условного перехода.
  • Объектно-ориентированные методы, такие как «миксины» (множественное наследование), могут использоваться для разложения кода на повторно используемые компоненты.

Привязки и история общих представлений, представлений на основе классов и представлений на основе функций

Изначально существовал только контракт для представления функции, которому Django направляет запрос HttpRequest и от которого ожидает ответа HttpResponse . Это то, что практиковал сам Джанго.

Довольно быстро в развитии взглядов были распознаны общие идиомы и закономерности. Общие представления, основанные на функциях, были введены, чтобы абстрагироваться от этих практик и облегчить разработку представлений для этих общих случаев.

Проблема с универсальными представлениями на основе функций заключается в том, что они хорошо охватывают простые случаи, но их невозможно расширить или настроить за пределы определенных параметров конфигурации, что ограничивает их полезность во многих реальных приложениях.

Общие представления на основе классов были созданы с теми же целями, что и представления на основе функций, для облегчения разработки представлений. Однако способ реализации этого решения с использованием миксинов предоставляет набор инструментов, в результате чего общие представления на основе классов являются гораздо более гибкими и расширяемыми, чем их аналоги на основе функций.

Если вы пытались использовать общие представления на основе функций в прошлом и обнаружили, что они слишком ограничены, вам не следует рассматривать общие представления на основе классов как эквиваленты, а скорее как новый подход к решению исходные проблемы, которые должны были решить общие представления.

Набор базовых классов и миксинов, которые 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 проверяет запрос, чтобы определить, является ли он a GET , a POST и т. д. и передать запрос соответствующему методу, если он существует, или сгенерировать ошибку в HttpResponseNotAllowed противном случае:

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('about/', MyView.as_view()),
]

Стоит отметить, что то, что возвращается методом, совпадает с тем, что возвращается представлением функции типа, в данном случае экземпляром HttpResponse . Это означает, что ярлыки или объекты httpTemplateResponse могут довольно хорошо использоваться в представлении на основе классов.

Несмотря на то, что минимальное представление на основе классов не требует каких-либо атрибутов класса для выполнения своей работы, атрибуты класса полезны во многих архитектурах на основе классов, и есть два способа настроить или определить атрибуты класса.

Первый - это стандартный способ 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() в конфигурации URL:

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 , через конфигурацию URL-адреса или через наследование и перегрузку одного или нескольких его методов (оба могут быть объединены).

Декорирование представлений на основе классов

Расширение представлений на основе классов не ограничивается миксинами. Также возможно использование декораторов. Поскольку представления на основе классов не являются функциями, их оформление работает по-разному в зависимости от того, используете ли вы as_view() подкласс или создаете его.

Украшение в конфиге URL

Можно адаптировать представления на основе классов, украсив результат метода as_view() . Наиболее удобное место для этого - конфигурация URL-адреса, в которой развернуто представление:

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 .

Copyright ©2020 All rights reserved