Skip to content

Latest commit

 

History

History
393 lines (266 loc) · 11.5 KB

File metadata and controls

393 lines (266 loc) · 11.5 KB

Модуль 8. Урок 52. Mixins как способ улучшения программного кода

Введение

К этому моменту курса мы уже активно используем Class-Based Views: ListView, DetailView, FormView, CreateView, UpdateView. Если внимательно посмотреть на код представлений, легко заметить повторяющиеся фрагменты:

  • передача заголовка страницы (title);
  • передача меню или навигации;
  • общие параметры, которые нужны почти в каждом шаблоне;
  • одинаковая логика формирования контекста.

На небольших примерах это не кажется проблемой, но по мере роста проекта код начинает:

  • дублироваться;
  • усложняться;
  • становиться труднее поддерживаемым.

Для решения этой задачи в Django (и Python в целом) широко используются миксины (Mixins).


1. Что такое миксин и зачем он нужен

Mixin — это вспомогательный класс, который:

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

Ключевая идея миксинов:

«Один миксин — одна ответственность».

Миксин не описывает объект целиком, а лишь добавляет ему одно поведение: контекст, проверку прав, вычисление данных и т.д.


2. Простая аналогия вне Django

Прежде чем перейти к 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.


3. Проблема дублирования в представлениях cinemahub

Предположим, в проекте cinemahub у нас есть несколько представлений:

  • список фильмов;
  • детальная страница фильма;
  • форма добавления фильма;
  • форма редактирования фильма.

Во всех них повторяется примерно один и тот же код:

context['title'] = ...
context['menu'] = ...

Если оставить всё как есть:

  • любое изменение меню придётся править в нескольких местах;
  • код станет менее читаемым;
  • увеличится риск ошибок.

Решение — вынести общий код в DataMixin.


4. Создание собственного миксина DataMixin

4.1. Где хранить миксины

Хорошая практика — выносить миксины и вспомогательные классы в отдельный файл. Создадим файл:

movies/utils.py

4.2. Определение DataMixin

Файл: 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

Что делает этот миксин:

  • добавляет меню;
  • добавляет заголовок страницы;
  • не зависит от конкретной модели или представления.

5. Использование DataMixin в ListView

Теперь применим миксин на практике.

Файл: 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).


6. Проверка результата в браузере

  1. Запускаем сервер.

  2. Переходим на страницу списка фильмов.

  3. Проверяем:

    • отображается заголовок страницы;
    • меню доступно в шаблоне;
    • список фильмов выводится корректно.

Если всё отображается — миксин подключён правильно.


7. Использование DataMixin в DetailView

Теперь используем тот же миксин в другом типе представления.

Файл: movies/views.py

from django.views.generic import DetailView
class 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;
  • добавляет стандартные данные.

8. Улучшение DataMixin: использование атрибутов класса

Постепенно код можно сделать ещё чище.

8.1. Обновлённый DataMixin

Файл: 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

Теперь часть логики можно перенести прямо в класс представления.


8.2. Использование в CreateView

Файл: movies/views.py

from django.views.generic import CreateView
from django.urls import reverse_lazy
class 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;
  • заголовок задаётся декларативно;
  • меню добавляется автоматически.

9. Типовые ошибки при работе с миксинами

Ошибка 1. Миксин указан после CBV

class MovieListView(ListView, DataMixin):
    ...

Проблема: методы миксина могут не вызываться. Решение: миксины всегда идут первыми.


Ошибка 2. Миксин пытается работать с моделью

Миксин не должен:

  • обращаться к self.object;
  • знать про Movie, Genre и т.д.

Он должен быть универсальным.


Ошибка 3. Конфликт имён методов

Если миксин и CBV переопределяют один и тот же метод, важно понимать порядок MRO.


Практические задания

Задание 1

Создайте миксин TitleMixin, который добавляет в контекст только заголовок страницы.


Задание 2

Используйте DataMixin в UpdateView для редактирования фильма.


Задание 3

Подключите DataMixin к ListView жанров (Genre).


Сравнить решение

Решение задания 1

class TitleMixin:
    title_page = None

    def get_title_context(self, context):
        if self.title_page:
            context['title'] = self.title_page
        return context

Решение задания 2

class 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 = 'Редактирование фильма'

Решение задания 3

class GenreListView(DataMixin, ListView):
    model = Genre
    template_name = 'genres/genre_list.html'
    context_object_name = 'genres'
    title_page = 'Жанры'

Вопросы

  1. Что такое миксин и какую задачу он решает?
  2. Почему миксины не используются самостоятельно?
  3. В каком порядке миксины должны указываться при наследовании?
  4. Какие данные логично выносить в миксин?
  5. Почему миксин не должен зависеть от конкретной модели?
  6. Чем миксин отличается от базового класса?
  7. Какие преимущества миксинов в больших проектах?

Предыдущий урок | Следующий урок