[Django]-Dynamically add properties to a django model

3👍

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.

👤Wtower

3👍

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.

3👍

I bet you know Django fields with choices provided will automatically have a display function.

Say you have a field defined like this:

category = models.SmallIntegerField(choices=CHOICES)

You can simply call a function called get_category_display() to access the display value. Here is the Django source code of this feature:

https://github.com/django/django/blob/baff4dd37dabfef1ff939513fa45124382b57bf8/django/db/models/base.py#L962

https://github.com/django/django/blob/baff4dd37dabfef1ff939513fa45124382b57bf8/django/db/models/fields/init.py#L704

So we can follow this approach to achieve our dynamically set property goal.

Here is my scenario, a little bit different from yours but down to the end it’s the same:

I have two classes, Course and Lesson, class Lesson has a ForeignKey field of Course, and I want to add a property name cached_course to class Lesson which will try to get Course from cache first, and fallback to database if cache misses:

Here is a typical solution:

from django.db import models


class Course(models.Model):
    # some fields


class Lesson(models.Model):
    course = models.ForeignKey(Course)

    @property
    def cached_course(self):
        key = key_func()
        course = cache.get(key)
        if not course:
            course = get_model_from_db()
            cache.set(key, course)
        return course

Turns out I have so many ForeignKey fields to cache, so here is the code following the similar approach of Django get_FIELD_display feature:

from django.db import models
from django.utils.functional import curry


class CachedForeignKeyField(models.ForeignKey):

    def contribute_to_class(self, cls, name, **kwargs):
        super(models.ForeignKey, self).contribute_to_class(cls, name, **kwargs)
        setattr(cls, "cached_%s" % self.name,
                property(curry(cls._cached_FIELD, field=self)))


class BaseModel(models.Model):

    def _cached_FIELD(self, field):
        value = getattr(self, field.attname)
        Model = field.related_model
        return cache.get_model(Model, pk=value)

    class Meta:
        abstract = True


class Course(BaseModel):
    # some fields


class Lesson(BaseModel):
    course = CachedForeignKeyField(Course)

By customizing CachedForeignKeyField, and overwrite the contribute_to_class method, along with BaseModel class with a _cached_FIELD method, every CachedForeignKeyField will automatically have a cached_FIELD property accordingly.

Too good to be true, bravo!

Leave a comment