К этому моменту курса мы уже активно используем Class-Based Views: ListView, DetailView, FormView, CreateView, UpdateView.
Если внимательно посмотреть на код представлений, легко заметить повторяющиеся фрагменты:
- передача заголовка страницы (
title); - передача меню или навигации;
- общие параметры, которые нужны почти в каждом шаблоне;
- одинаковая логика формирования контекста.
На небольших примерах это не кажется проблемой, но по мере роста проекта код начинает:
- дублироваться;
- усложняться;
- становиться труднее поддерживаемым.
Для решения этой задачи в Django (и Python в целом) широко используются миксины (Mixins).
Mixin — это вспомогательный класс, который:
- не используется сам по себе;
- добавляет конкретный, изолированный функционал;
- подключается к другим классам через множественное наследование.
Ключевая идея миксинов:
«Один миксин — одна ответственность».
Миксин не описывает объект целиком, а лишь добавляет ему одно поведение: контекст, проверку прав, вычисление данных и т.д.
Прежде чем перейти к Django, рассмотрим абстрактный пример на чистом Python:
class LegsMixin:
def has_legs(self):
return True
class BackMixin:
def has_back(self):
return True
class RectangularCoverMixin:
def cover_shape(self):
return 'rectangular'
class RoundCoverMixin:
def cover_shape(self):
return 'round'
class Table(LegsMixin, RectangularCoverMixin):
pass
class Chair(LegsMixin, RectangularCoverMixin, BackMixin):
pass
class RoundTable(LegsMixin, RoundCoverMixin):
passКаждый миксин добавляет один признак, а итоговый класс собирается из них, как из конструктора.
Тот же подход мы применим и к представлениям Django.
Предположим, в проекте cinemahub у нас есть несколько представлений:
- список фильмов;
- детальная страница фильма;
- форма добавления фильма;
- форма редактирования фильма.
Во всех них повторяется примерно один и тот же код:
context['title'] = ...
context['menu'] = ...Если оставить всё как есть:
- любое изменение меню придётся править в нескольких местах;
- код станет менее читаемым;
- увеличится риск ошибок.
Решение — вынести общий код в DataMixin.
Хорошая практика — выносить миксины и вспомогательные классы в отдельный файл. Создадим файл:
movies/utils.py
Файл: movies/utils.py
menu = [
{'title': 'Главная', 'url_name': 'movies:list'},
{'title': 'Добавить фильм', 'url_name': 'movies:add'},
]
class DataMixin:
"""
Миксин для добавления стандартных данных в контекст шаблонов
"""
def get_mixin_context(self, context, **kwargs):
context['menu'] = menu
context['title'] = kwargs.get('title', '')
return contextЧто делает этот миксин:
- добавляет меню;
- добавляет заголовок страницы;
- не зависит от конкретной модели или представления.
Теперь применим миксин на практике.
Файл: movies/views.py
from django.views.generic import ListView
from .models import Movie
from .utils import DataMixinСоздадим представление списка фильмов:
class MovieListView(DataMixin, ListView):
model = Movie
template_name = 'movies/movie_list.html'
context_object_name = 'movies'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return self.get_mixin_context(
context,
title='Каталог фильмов'
)Обратите внимание на порядок наследования:
class MovieListView(DataMixin, ListView):Миксины всегда указываются первыми, затем основной CBV. Это важно из-за порядка разрешения методов (MRO).
-
Запускаем сервер.
-
Переходим на страницу списка фильмов.
-
Проверяем:
- отображается заголовок страницы;
- меню доступно в шаблоне;
- список фильмов выводится корректно.
Если всё отображается — миксин подключён правильно.
Теперь используем тот же миксин в другом типе представления.
Файл: movies/views.py
from django.views.generic import DetailViewclass MovieDetailView(DataMixin, DetailView):
model = Movie
template_name = 'movies/movie_detail.html'
context_object_name = 'movie'
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
movie = context['movie']
return self.get_mixin_context(
context,
title=movie.title
)Здесь миксин:
- не знает ничего о фильмах;
- просто принимает готовый
context; - добавляет стандартные данные.
Постепенно код можно сделать ещё чище.
Файл: movies/utils.py
class DataMixin:
title_page = None
menu = [
{'title': 'Главная', 'url_name': 'movies:list'},
{'title': 'Добавить фильм', 'url_name': 'movies:add'},
]
def get_mixin_context(self, context, **kwargs):
context['menu'] = self.menu
if self.title_page:
context['title'] = self.title_page
context.update(kwargs)
return contextТеперь часть логики можно перенести прямо в класс представления.
Файл: movies/views.py
from django.views.generic import CreateView
from django.urls import reverse_lazyclass MovieCreateView(DataMixin, CreateView):
model = Movie
fields = ['title', 'description', 'year', 'is_published']
template_name = 'movies/movie_form.html'
success_url = reverse_lazy('movies:list')
title_page = 'Добавление фильма'Код стал короче и чище:
- нет
get_context_data; - заголовок задаётся декларативно;
- меню добавляется автоматически.
class MovieListView(ListView, DataMixin):
...Проблема: методы миксина могут не вызываться. Решение: миксины всегда идут первыми.
Миксин не должен:
- обращаться к
self.object; - знать про
Movie,Genreи т.д.
Он должен быть универсальным.
Если миксин и CBV переопределяют один и тот же метод, важно понимать порядок MRO.
Создайте миксин TitleMixin, который добавляет в контекст только заголовок страницы.
Используйте DataMixin в UpdateView для редактирования фильма.
Подключите DataMixin к ListView жанров (Genre).
class TitleMixin:
title_page = None
def get_title_context(self, context):
if self.title_page:
context['title'] = self.title_page
return contextclass MovieUpdateView(DataMixin, UpdateView):
model = Movie
fields = ['title', 'description', 'year', 'is_published']
template_name = 'movies/movie_form.html'
success_url = reverse_lazy('movies:list')
title_page = 'Редактирование фильма'class GenreListView(DataMixin, ListView):
model = Genre
template_name = 'genres/genre_list.html'
context_object_name = 'genres'
title_page = 'Жанры'- Что такое миксин и какую задачу он решает?
- Почему миксины не используются самостоятельно?
- В каком порядке миксины должны указываться при наследовании?
- Какие данные логично выносить в миксин?
- Почему миксин не должен зависеть от конкретной модели?
- Чем миксин отличается от базового класса?
- Какие преимущества миксинов в больших проектах?