Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically add properties to a django model

I have a Django model where a lot of fields are choices. So I had to write a lot of "is_something" properties of the class to check whether the instance value is equal to some choice value. Something along the lines of:

class MyModel(models.Model):
    some_choicefield = models.IntegerField(choices=SOME_CHOICES)

    @property
    def is_some_value(self):
        return self.some_choicefield == SOME_CHOICES.SOME_CHOICE_VALUE

    # a lot of these...

In order to automate this and spare me a lot of redundant code, I thought about patching the instance at creation, with a function that adds a bunch of methods that do the checks. The code became as follows (I'm assuming there's a "normalize" function that makes the label of the choice a usable function name):

def dynamic_add_checks(instance, field):
    if hasattr(field, 'choices'):
        choices = getattr(field, 'choices')
        for (value,label) in choices:
            def fun(instance):
                return getattr(instance, field.name) == value
            normalized_func_name = "is_%s_%s" % (field.name, normalize(label))
            setattr(instance, normalized_func_name, fun(instance))

class MyModel(models.Model):
    def __init__(self, *args, **kwargs):
        super(MyModel).__init__(*args, **kwargs)
        dynamic_add_checks(self, self._meta.get_field('some_choicefield')

    some_choicefield = models.IntegerField(choices=SOME_CHOICES)

Now, this works but I have the feeling there is a better way to do it. Perhaps at class creation time (with metaclasses or in the new method)? Do you have any thoughts/suggestions about that?

like image 666
mp85 Avatar asked Mar 01 '26 14:03

mp85


2 Answers

Well I am not sure how to do this in your way, but in such cases I think the way to go is to simply create a new model, where you keep your choices, and change the field to ForeignKey. This is simpler to code and manage.

You can find a lot of information at a basic level in Django docs: Models: Relationships. In there, there are many links to follow expanding on various topics. Beyong that, I believe it just needs a bit of imagination, and maybe trial and error in the beginning.

like image 60
Wtower Avatar answered Mar 04 '26 02:03

Wtower


I came across a similar problem where I needed to write large number of properties at runtime to provide backward compatibility while changing model fields. There are 2 standard ways to handle this -

  1. First is to use a custom metaclass in your models, which inherits from models default metaclass.
  2. Second, is to use class decorators. Class decorators sometimes provides an easy alternative to metaclasses, unless you have to do something before the creation of class, in which case you have to go with metaclasses.
like image 30
hspandher Avatar answered Mar 04 '26 04:03

hspandher