将属性动态添加到 django 模型

Posted

技术标签:

【中文标题】将属性动态添加到 django 模型【英文标题】:Dynamically add properties to a django model 【发布时间】:2015-08-28 09:15:07 【问题描述】:

我有一个 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...

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

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)

现在,这可行,但我觉得有更好的方法来做到这一点。也许在类创建时(使用元类或在 new 方法中)?您对此有什么想法/建议吗?

【问题讨论】:

【参考方案1】:

好吧,我不确定如何以您的方式执行此操作,但在这种情况下,我认为要走的路是简单地创建一个新模型,在其中保留您的选择,并将字段更改为 ForeignKey。这更易于编码和管理。

您可以在Django docs: Models: Relationships 中找到大量基本信息。在那里,有许多链接可以扩展各种主题。除此之外,我相信它只需要一点想象力,也许一开始就需要反复试验。

【讨论】:

每个字段的选择都不同。您的意思是创建一个新模型而不是每个包含选项的字段吗?就表演而言,这不是矫枉过正吗? 这种行为也可以建模,可能更容易。矫枉过正?是也不是,这真的取决于很多事情。 谢谢,这是一个有趣的方法,我应该读一读。您能否指出任何可以更深入地解释这种模式的资源? 每个类都是一个称为元类的特殊类的实例。默认情况下,Python 中的所有类都是类类型的实例。但是,您可以通过从类型继承来创建 ypur 自己的元类。事实上,django 模型已经使用自定义元类。如果您想要代码示例,我现在无法使用我的笔记本电脑,有机会我会提供。【参考方案2】:

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

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

【讨论】:

嘿,我认为你的方法是正确的。您介意添加一个示例吗? @jsmedmar 我现在做不到,看看我的另一个答案***.com/questions/32091352/… 看看是否有帮助 我没有从你的回答中理解如何动态地向我的模型对象添加属性...【参考方案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,并用_cached_FIELD 方法覆盖contribute_to_class 方法以及BaseModel 类,每个CachedForeignKeyField 将自动具有相应的cached_FIELD 属性。

好得令人难以置信,太棒了!

【讨论】:

以上是关于将属性动态添加到 django 模型的主要内容,如果未能解决你的问题,请参考以下文章

Django 中的动态模型字段

在django中动态添加参数到queryset过滤器调用[重复]

Django Admin:动态添加内联

向 Django 模型添加动态字段

Django 动态网址

在 django formset 中动态添加行