看来我们可以在Django模型上创建一个property,然后 向该查询集添加具有完全相同名称的annotation模型。
例如,我们的FooBarModel
有foo = property(...)
,最重要的是我们可以FooBarModel.objects.annotate(foo=...)
。
请注意,名称相同:如果foo
是普通属性,而不是property
,则annotate(foo=...)
将引发ValueError
。在这种情况下,不会出现此类错误。
初步测试表明,这种方法行之有效,使我们能够创建例如准可过滤属性。
我想知道的是:这是一个好方法,还是可能导致某种冲突或意想不到的意外?
我们有一个现有的数据库,该数据库的FooBarModel
字段为bar
,在我们的项目代码中的许多地方都使用了该数据库,主要用于过滤FooBarModel
查询集。
现在,由于某种原因,我们需要向模型添加一个新字段foo
,而不是bar
字段。 bar
字段仍然有目的,因此我们也要保留该目的。如果尚未设置foo
(例如,对于现有数据库条目),我们将退回到bar
。
这必须同时适用于新的(未保存的)模型实例和查询集。
注意:尽管对于下面提供的特定示例,一个简单的data migration可以作为替代解决方案(备用),但是在更复杂的情况下,数据迁移是不可行的。
现在,为了完成这项工作,我们使用foo
property
,_foo
模型字段,set_foo
方法和get_foo
来实现模型提供后备逻辑的方法(如果尚未设置bar
,则返回_foo
。
但是,据我所知,property
不能用于查询集过滤器,因为foo
不是实际的数据库字段(_foo
是,但是没有后备逻辑)。以下SO问题似乎支持这一点:Filter by property,Django empty field fallback,Do properties work on django model fields,Django models and python properties,Cannot resolve keyword
因此,我们添加了一个自定义管理器,该管理器使用Coalesce类注释了初始查询集。这在数据库级别复制了get_foo
的后备逻辑(空字符串除外),可用于过滤。
最终结果显示如下(在python 2.7 / 3.6和Django 1.9 / 2.0中进行了测试):
from django.db import models
from django.db.models.functions import Coalesce
class FooManager(models.Manager):
def get_queryset(self):
# add a `foo` annotation (with fallback) to the initial queryset
return super(FooManager,self).get_queryset().annotate(foo=Coalesce('_foo', 'bar'))
class FooBarModel(models.Model):
objects = FooManager() # use the extended manager with 'foo' annotation
_foo = models.CharField(max_length=30, null=True, blank=True) # null=True for Coalesce
bar = models.CharField(max_length=30, default='something', blank=True)
def get_foo(self):
# fallback logic
if self._foo:
return self._foo
else:
return self.bar
def set_foo(self, value):
self._foo = value
foo = property(fget=get_foo, fset=set_foo, doc='foo with fallback to bar')
现在,我们可以在新的(未保存的)foo
实例上使用FooBarModel
属性,例如FooBarModel(bar='some old value').foo
,我们也可以使用FooBarModel
过滤foo
个查询集,例如FooBarModel.objects.filter(foo='what I'm looking for')
。
注意:不幸的是,后者在related objects上(例如, SomeRelatedModel.objects.filter(foobar__foo='what I'm looking for')
,因为在这种情况下是manager is not used。
此方法的缺点是,get_foo
中的“后备”逻辑需要在自定义管理器中复制(以Coalesce
的形式)。
我想也可以在批注部分使用Django的conditional expressions,将此“属性+批注”模式应用于更复杂的property
逻辑。
上面的方法模拟了可过滤的模型属性。但是,名为foo
的注释遮盖了名为property
的{{1}},因此我不确定这是否会在某些时候导致冲突或其他意外情况。有人知道吗?
如果我们向查询集添加“正常”注释,即与属性名称(例如foo
不匹配)的z
会显示为属性(当我对该查询集的实例调用z
时)。但是,如果我们在vars()
的实例上使用vars()
,它将显示名为FooBarModel.objects
的 no 属性。这是否意味着foo
方法会覆盖注释?