Чуть-чуть «извращений» над моделями django

Django

Лень двигатель прогресса


Иногда, создавая модели в django, я себя начинаю чувствовать мартышкой. Постоянно создаю атрибут enable, который принимает по умолчанию то значение True, то False. Меняю менеджер objects на свой простой EnableManager. А хочется иметь механизм, который эти монотонные операции делал за меня. Раз хочется, то можно сделать.


Представляем, что мы хотим получить


Первая мысль, пришедшая мне в голову, была: написать несколько абстрактных классов(если писать 'mixin', то django-модель не добавляет поля, заданные нами), которые потом я буду подключать, когда мне это необходимо. Это достаточно дубовый метод, который плодит огромное количество повторяющегося кода, чего мне не хотелось.

Следом я подумал про функцию, которая будет создавать мне нужные классы. Это избавит от огромного количества повторяющегося кода, и в целом выглядит не плохо. «А что если, где-то не стоит использовать, скажем, твой менеджер, и это единственный случай?» — крутилось у меня в голове. Ради эксклюзивного случая создавать класс не очень хочется. Если использовать декоратор, то и этого можно избежать.

Итого: нам нужно создать функцию, которая при определенных параметрах:
  • Возвращает абстрактный класс
  • Декорирует класс, добавляя в него нужное поле и нужный менеджер


Везде есть подводные камни


В данном случае, это было декорирования класса. Дело в том, что в декораторе, используя '@foo' мы можем передать только, над которым будет совершенно действие. Но как сделать так, что бы мы могли передать параметры декоратору?
Дело в том, что синтаксическая конструкция '@' используется как композиция двух функций(g•f(x)). В python'e и функция и класс является обьектом, и мы можем вернуть функцию синтаксической конструкции '@', т.е. примерно так (l(y))•(f(x)), где l(y) — возвращает функцию. Код декоратора, будет примерно таким:

def foo(cls=None, param="DefaultValue"):
    def decorator(cls):
        # do something with class
        return cls
    if cls is None:
        return decorator
    else:
        return decorator(cls)


Что то похожее, только с функциями, используется в django для декоратора login_required.

Теперь можно и покодить


Вроде все подводные камни разобраны, теперь можно приступить к написанию кода:

def enable(cls=None, status=True, set_manager=True, mixin=False):
    '''
    Adds enable field into cls or return mixin
    :param cls: class that would updates
    :param status: default value for enable field
    :type status: bool
    :param set_manager: sets if EnableManager is required
    :type set_manager: bool
    :param mixin: sets should be returned model mixin
    :type mixin: bool
    '''
    def decorator(cls):
        '''
        Adds field and manager if manager is required
        '''
        cls.add_to_class('enabled',
                         models.BooleanField(_('Enabled'), default=status))
        if set_manager:
            cls.add_to_class('objects', EnableManager())
        return cls

    class Class(models.Model):
        '''
        Enable AbstractModels
        '''
        enbaled = models.BooleanField(_('Enabled'), default=status)

        class Meta:
            abstract = True

    if cls and mixin:
        raise DecoratorMixinException
    elif mixin:
        if set_manager:
            Class.add_to_class('objects', EnableManager())
        return Class
    elif cls:
        return decorator(cls)
    else:
        return decorator


DecoratorMixinException — это исключение, которое говорит о том что функция не может быть вызвана как декоратор с параметром mixin = True.

Для добавления менеджеров используется метод add_to_class(), особенность django-моделей, если писать в классе objects = YourManager() или cls.objects = YourManager(), то работать не будет. Через этот же метод мы и добавляем нашей модели необходимое поле.

Примеры использования


EnableFalseMixin = enable(status=false, mixin=True)

class SimpleModel(EnableFalseMixin,  models.Model):
    '''
    Simple model
    '''
    # some field here

@enable
class TestFalseEnable(models.Model):
    '''
    Test enable
    '''
    # some fields here

@enable(status=false)
class TestFalseEnable(models.Model):
    '''
    Test enable
    '''
    # some fields here


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

P.S.
Кажется, некоторые внимательные люди будут ругаться, что имя функции «Enable» написано с большой буквы, что не соответствует PEP8. Я это сделал, потому что данная функция порождает новый класс. Пожалуйста, мастера python'а и django, скажите на сколько правильно это сделано? Встречал такое в некоторых проектах, но никогда не задумывался о том на сколько это правильно.
Tags:djangomodelsdecoratorsabstract classpython
Hubs: Django
+9
6k 32
Comments 12

Popular right now

Python QA Engineer
March 16, 202160,000 ₽OTUS
Python-разработчик
January 28, 202199,000 ₽Яндекс.Практикум
Мидл python-разработчик
February 11, 202185,000 ₽Яндекс.Практикум
Project Manager
January 26, 202124,000 ₽GeekBrains
Профессия Product Manager
January 27, 2021108,500 ₽Нетология