Laziness is the engine of progress
Sometimes, creating models in django, I start to feel like a monkey. Constantly I create the enable attribute, which accepts the default value True, then False. I change the objects manager to my simple EnableManager. And I want to have a mechanism that made these monotonous operations for me. If you want, you can do it.
We represent that we want to receive
The first thought that came to my mind was: to write several abstract classes (if you write 'mixin', then the django-model does not add the fields that we set), which I will connect later when I need it. This is quite an oak method that produces a huge amount of duplicate code, which I did not want.
Following, I thought about the function that will create the necessary classes for me. This will save a huge amount of duplicate code, and in general it doesn’t look bad. “And what if somewhere you should not use, say, your manager, and this is the only case?” - was spinning in my head. For the sake of an exclusive case, I don’t really want to create a class. If you use a decorator, then this can be avoided.
')
Total: we need to create a function that, with certain parameters:
- Returns the abstract class
- Decorates the class, adding to it the right field and the desired manager
Everywhere there are pitfalls
In this case, it was a decorating class. The fact is that in the decorator, using '@foo' we can pass only on which the action will be completely. But how to make it so that we could pass the parameters to the decorator?
The fact is that the syntactic construct '@' is used as a composition of two functions (g • f (x)). In python, both the function and the class are objects, and we can return the function of the '@' syntax, i.e. something like this (l (y)) • (f (x)), where l (y) returns the function. The decorator code will be something like this:
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)
Something similar, only with functions, is used in django for the
login_required decorator.
Now you can and pokodit
It seems all the pitfalls are disassembled, now you can start writing code:
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 is an exception that says that a function cannot be called as a decorator with the mixin = True parameter.
To add managers, use the add_to_class () method, a feature of django-models; if you write in the class objects = YourManager () or cls.objects = YourManager (), it will not work. Through the same method we add the required field to our model.
Examples of using
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
This article was conceived as an example of creating a convenient mechanism that can be used daily.
I hope someone it will be useful. Enjoy your development on the django framework.
PS
It seems that some attentive people will swear that the name of the function “Enable” is written with a capital letter, which does not correspond to PEP8. I did this because this function spawns a new class. Please, master python'a and django, tell me how correct it is done? I met this in some projects, but I never thought about how correct this is.