Лень двигатель прогресса
Иногда, создавая модели в 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 is EnableManager is required
:type set_manager: bool
:param mixin: sets should be returns model mixin
:type mixin: bool
'''
def decorator(cls):
'''
Adds field and manager if manager is required
'''
cls.add_to_class('enable',
models.BooleanField(_('Enable'), default=status))
if set_manager:
cls.add_to_class('objects', EnableManager())
return cls
class Class(models.Model):
'''
Enable AbstractModels
'''
enbale = models.BooleanField(_('Enable'), default=status)
class Meta:
abstract = True
if cls is None and mixin:
if set_manager:
Class.add_to_class('objects', EnableManager())
return Class
elif cls is None and not mixin:
return decorator
elif cls and not mixin:
return decorator(cls)
elif cls and mixin:
raise DecoratorMixinException
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, скажите на сколько правильно это сделано? Встречал такое в некоторых проектах, но никогда не задумывался о том на сколько это правильно.
Автор: Zapix