我认为提出这个问题的最好方法是使用一些代码......我可以这样做吗? (编辑:答案:否)
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self.foo
def set_foo(self, input):
self.foo = input
foo = property(get_foo, set_foo)
或者我必须这样做:
class MyModel(models.Model):
_foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self._foo
def set_foo(self, input):
self._foo = input
foo = property(get_foo, set_foo)
note :您可以通过将db_column传递给模型字段,将列名保留为数据库中的'foo'。当您处理现有系统并且不希望无缘无故地进行数据库迁移时,这非常有用
答案 0 :(得分:20)
模型字段已经是属性,所以我想你必须以第二种方式来避免名称冲突。
当您定义foo = property(..)时,它实际上会覆盖foo = models ..行,因此该字段将不再可访问。
您需要为属性和字段使用不同的名称。实际上,如果你按照示例#1中的方式执行它,当你尝试访问属性时,你会得到一个无限循环,因为它现在试图返回它。
编辑:也许您还应该考虑不使用_foo作为字段名称,而是使用foo,然后为您的属性定义另一个名称,因为属性不能在QuerySet中使用,因此您需要使用实际的字段名称例如,你做了一个过滤器。答案 1 :(得分:16)
如上所述,实现自己的django.db.models.Field类的正确替代方法,应该使用 - db_column 参数和自定义(或隐藏)类属性。我只是在@Jiaaro的编辑中重写代码,遵循python中更严格的OOP约定(例如,如果_foo实际上应该被隐藏):
class MyModel(models.Model):
__foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
@property
def foo(self):
if self.bar:
return self.bar
else:
return self.__foo
@foo.setter
def foo(self, value):
self.__foo = value
__ foo 将被解析为 _MyModel__foo (由 dir(..)看到),从而隐藏(private)。请注意,此表单还允许使用@property decorator,这最终将是编写可读代码的更好方法。
再次,django将创建具有两个字段 foo 和 bar 的* _MyModel表。
答案 2 :(得分:5)
之前的解决方案受到影响,因为@property会导致admin和.filter(_foo)出现问题。
更好的解决方案是覆盖 setattr ,但这会导致从DB初始化ORM对象时出现问题。然而,有一个解决这个问题的技巧,它是普遍的。
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def __setattr__(self, attrname, val):
setter_func = 'setter_' + attrname
if attrname in self.__dict__ and callable(getattr(self, setter_func, None)):
super(MyModel, self).__setattr__(attrname, getattr(self, setter_func)(val))
else:
super(MyModel, self).__setattr__(attrname, val)
def setter_foo(self, val):
return val.upper()
秘密是' attrname in self .__ dict __ '。当模型从 __ dict __ 中的new或hydrated初始化时!
答案 3 :(得分:0)
这取决于您的property
本身是一种手段还是目的。
如果在过滤查询集(而无需首先评估它们)时想要这种“覆盖”(或“后备”)行为,我认为属性不能解决问题。 As far as I know,Python属性在数据库级别不起作用,因此不能在查询集过滤器中使用。请注意,您可以在过滤器中使用_foo
(而不是foo
),因为它表示实际的表列,但随后是get_foo()
中的替代逻辑将不适用。
但是,如果您的用例允许,Coalesce()
(docs)中的django.db.models.functions
类可能会有所帮助。
Coalesce()
...接受至少两个字段名称的列表,或者 表达式并返回第一个非空值(请注意, 字符串不视为空值)。 ...
这意味着您可以使用bar
将foo
指定为Coalesce('bar','foo')
的替代。除非bar
是bar
,否则它将返回null
,在这种情况下,它将返回foo
。与您的get_foo()
相同(除了它不适用于空字符串),但在数据库级别为。
剩下的问题是如何实现这一点。
如果您不在很多地方使用它,只需annotating即可简化查询集。使用您的示例,没有属性的东西:
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
然后按以下方式进行查询:
from django.db.models.functions import Coalesce
queryset = MyModel.objects.annotate(bar_otherwise_foo=Coalesce('bar', 'foo'))
现在,您的查询集中的项目具有魔术属性bar_otherwise_foo
,可以对其进行过滤,例如queryset.filter(bar_otherwise_foo='what I want')
,也可以直接在实例上使用,例如print(queryset.all()[0].bar_otherwise_foo)
从queryset.query
得到的SQL query显示Coalesce()
确实在数据库级别有效:
SELECT "myapp_mymodel"."id", "myapp_mymodel"."foo", "myapp_mymodel"."bar",
COALESCE("myapp_mymodel"."bar", "myapp_mymodel"."foo") AS "bar_otherwise_foo"
FROM "myapp_mymodel"
注意:您也可以先将模型字段命名为_foo
,然后再命名为foo=Coalesce('bar', '_foo')
,依此类推。使用foo=Coalesce('bar', 'foo')
可能很诱人,但这会引发ValueError: The annotation 'foo' conflicts with a field on the model.
必须有几种创建DRY实现的方法,例如编写custom lookup或custom(ized) Manager。
可轻松实现自定义管理器,如下所示(请参见example in docs):
class MyModelManager(models.Manager):
""" standard manager with customized initial queryset """
def get_queryset(self):
return super(MyModelManager, self).get_queryset().annotate(
bar_otherwise_foo=Coalesce('bar', 'foo'))
class MyModel(models.Model):
objects = MyModelManager()
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
现在,MyModel
的每个查询集将自动具有bar_otherwise_foo
批注,可以如上所述使用。
但是请注意,例如在实例上更新bar
不会更新注释,因为注释是在查询集上进行的。查询集将需要首先重新评估,例如通过从查询集中获取更新的实例。
也许可以使用带有注释的自定义管理器和Python property
的组合来获得两全其美(example at CodeReview)。