Class-based Views или Представления, основанные на классах в Django, часть 3

В рассмотрении Class-based views остался последний вопрос. Последний по порядку, а не по значению.

Этот вопрос — микс-ины (Mixins). Понятие это не столько связано с Django, сколько с Python в целом. Но в class-based views это настолько часто употребляется, что микс-ины проще рассматривать на них.

Для начала немного теории. У нас есть такой принцип ООП, как наследование, и дочерний класс наследует от родительского все свойства и методы. Должен ли родительский класс быть один? Должен. Но не всегда. Ряд языков, в том числе и Python, позволяют делать наследование от нескольких родительских классов. Тогда в дочернем классе как бы смешиваются (mix) родительские. Хорошо ли это? Отчасти да, когда такое множественное наследование необходимо реализовать. Отчасти нет, потому что смешение двух сложных полноценных родительских классов может выдать совершенно неожиданный результат.

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

class A:
	def foo(self):
		print('A')
	def bar(self):
		print('BAR')
class B:
    def foo(self):
        print('B')
class C(B, A):
    pass
c = C()
c.foo()
c.bar()

Как видите, метод foo() был переопределен нашим микс-ином B, следовательно вывод изменился. Но этот пример — самый простой. Дальше — больше. Что, если нам нужно не повредить родительский метод? В случае стандартного наследования можно было применить такую конструкцию, как ParentClass.method_name(self, **kwargs):

class A:
    def foo(self):
        print('A')
class B(A):
    def foo(self):
        A.foo(self)
        print('B')
b = B()
b.foo()

В случае с микс-ином все немного сложнее, потому что мы не знаем имя родительского класса. Именно здесь и приходит на помощь встроенный метод super(). Он позволяет определить родителя именно в иерархии микс-инов:

class A:
    def foo(self):
        print('A')
class B:
    def foo(self):
        super(B, self).foo()
        print('B')
class C(B, A):
    pass
c = C()
c.foo()

Как видите, прямого родителя у B нет, но внурти C родитель B определается как A.

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

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

А теперь вкусное, переходим к микс-инам в class-based views. На микс-инах выстроен весь модуль django.views.generic. Например, базовым классом для всех CBV является класс View. Он обеспечивает только методы as_view() и dispatch(). Если его смешать с TemplateMixin, то мы получим дополнительно свойство template_name и метод get_template_names(). Потом примешав к ним FormMixin, можно получить класс FormView, который обладает свойствами не только представления в целом и представления с шаблонами, но и представления с формами. А если примешать FormMixin к DetailView, то мы получим основу для CreateView и UpdateView. И так далее.

На самом деле то, как микс-ины использованы в django.views.generic, не является ярким примером их использования. Основная цель микс-инов — найти функционал, который пересекается между разнородными представлениями, и объединить его. Тогда, чтобы добавить этот функционал, достаточно будет примешать нужный микс-ин к необходимому классу.

Пример 1. Этот пример один из самых очевидных. Нам необходимо расширить контекст, передаваемый в шаблон. В целом ничего сложного, создавай дочерний класс, переопределяй get_context_data() и все. Однако, используется 3 родительских класса: ListView, DetailView и FormView для поиска. Получается, от них нужно сделать 3 промежуточных: ExtConListView и т.д., и потом уже от промежуточных создавать рабочие. Или создать один микс-ин, и дальше его примешивать.

Добавляем в контекст параметр categories, который будет содержать все категории нашего блога:

class ExtendedContextMixin(object):
    def get_context_data(self, **kwargs):
        context = super(ExtendedContextMixin, self).get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        return context

Теперь, добавив этот класс к любому другому, мы будем в контекcте дополнительно получать список категорий. Только следует помнить, что если до этого уже был определен параметр categories, то он изменится.

Пример 2. Менее деструктивный. Он приведен в качестве примера по микс-инам в документации к Django. Есть необходимость формировать ответ не в форме HTML, а в форме JSON (веб-сервис у нас). Вот такой недеструктивный пример:

import json
from django.http import HttpResponse

class JSONResponseMixin(object):
    def render_to_json_response(self, context, **response_kwargs):
        return HttpResponse(
            self.convert_context_to_json(context),
            content_type='application/json',
            **response_kwargs
        )
    def convert_context_to_json(self, context):
        return json.dumps(context)

В микс-ине просто создаются два дополнительных метода. Пример только учебный, потому что метод convert_context_to_json() с вероятностью 99% не сработает: не сможет сериализовать объект Django.

И дальше этот микс-ин начинает работать в остальных классах:

from django.views.generic import TemplateView

class JSONView(JSONResponseMixin, TemplateView):
    def render_to_response(self, context, **response_kwargs):
        return self.render_to_json_response(context, **response_kwargs)

Пример 3. Часть сайта использует как десктопное, так и мобильное представления. Шаблоны для мобильного представления хранятся в каталоге mobile. Переопределение имен шаблонов лучше всего делать в методе get_template_names(). Задача с виду простая, за исключением одного «но». Внутри этого метода мы не знаем, от кого пришел запрос. Предположим, что метод для определения, мобильный ли клиент, у нас есть, и он называется is_mobile(request). Осталось передать ему параметры запроса. Для этого мы вмешиваемся в метод dispatch, самый ранний, где есть параметры запроса, с наименьшими повреждениями:

class MobileTemplateMixin(object):
    def dispatch(self, request, *args, **kwargs):
        self.request = request
        return super(MobileTemplateMixin, self).dispatch(request, *args, **kwargs)
    def get_template_names(self):
        names = super(MobileTemplateMixin, self).get_template_names()
        if is_mobile(self.request):
            names = ['mobile/%s' % name for name in names]
        return names

И все. Теперь request доступен внутри нашего класса и мы можем спокойно проверить его и поправить путь к шаблонам.

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

Напоследок могу сказать одно. Сам модуль django.views.generic не такой то уж и сложный, поэтому не поленитесь залезть туда и самостоятельно посмотреть, как он работает. Тогда и понятно будет, что от штатных классов ожидать, и как свои расширять.

Удачи!


4 responses to “Class-based Views или Представления, основанные на классах в Django, часть 3

  • German

    А где нужно писать миксины, общие для нескольких приложений?

    • truecryer

      Я обычно создаю модуль core для всего проекта, и внутри views.py, и туда уже складываю миксины и представления общего характера.
      Для примера можешь посмотреть мой проект https://github.com/TrueCryer/blendermada_website/ — там есть такой модуль.
      А так бывает ситуация, когда само приложение очень навороченное, и там куча модулей с преставлениями. Тогда внутри приложения создают каталог base и туда уже складывают базовые модели, представления, утилиты и т.д.

  • Илья Качкаев

    Большое спасибо за статьи, максимально просто и подробно описано, как раз для начинающих джангистов.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google photo

Для комментария используется ваша учётная запись Google. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s

%d такие блоггеры, как: