动态添加属性到django模型

时间:2015-06-12 11:17:47

标签: python django django-models metaclass

我有一个Django模型,其中有很多字段可供选择。所以我不得不写很多" is_something"该类的属性,用于检查实例值是否等于某个选择值。有点像:

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...

为了自动执行此操作并为我节省大量冗余代码,我考虑在创建时修补实例,并添加了一系列执行检查的方法。 代码变为如下(我假设有" normalize"函数使选项的标签成为可用的函数名称):

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)

现在,这有效,但我觉得有更好的方法。也许是在课堂创作时(使用元类或方法)?你对此有什么想法/建议吗?

3 个答案:

答案 0 :(得分:3)

嗯,我不确定如何以你的方式做到这一点,但在这种情况下,我认为要走的路是简单地创建一个新模型,在那里你保持选择,并将字段更改为ForeignKey。这更容易编码和管理。

您可以在Django docs: Models: Relationships的基本级别找到大量信息。在那里,有许多链接可以跟随各种主题进行扩展。 Beyong,我相信它只需要一点想象力,也许一开始就是反复试验。

答案 1 :(得分:3)

我遇到了类似的问题,我需要在运行时编写大量属性,以便在更改模型字段时提供向后兼容性。有两种标准方法可以解决这个问题 -

  1. 首先在模型中使用自定义元类,它继承自模型默认元类。
  2. 其次,是使用类装饰器。类装饰器有时提供了一种替代元类的简单方法,除非你必须在创建类之前做一些事情,在这种情况下你必须使用元类。

答案 2 :(得分:3)

我打赌你知道Django字段提供的选项会自动显示功能。

假设你有一个像这样定义的字段:

category = models.SmallIntegerField(choices=CHOICES)

您只需调用名为get_category_display()的函数即可访问显示值。以下是此功能的Django源代码:

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

因此,我们可以按照这种方法来实现dynamically set property目标。

这是我的情景,与你的情况略有不同,但到最后它是一样的:

我有两个类CourseLesson,类Lesson的ForeignKey字段为Course,我想添加一个属性名称cached_course到类Lesson,它将尝试首先从缓存中获取Course,并在缓存未命中时回退到数据库:

这是一个典型的解决方案:

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

原来我有很多ForeignKey字段要缓存,所以这里的代码遵循Django get_FIELD_display功能的类似方法:

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)

通过自定义CachedForeignKeyField,并使用contribute_to_class方法覆盖BaseModel方法和_cached_FIELD类,每个CachedForeignKeyField将自动拥有cached_FIELD相应的1}}属性。

太好了,真实,勇敢!