Django模型字段上可以使用属性吗?
我觉得问这个问题最好的方式是用一些代码来说明...我可以这样做吗:
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)
注意: 你可以通过给模型字段传递一个 db_column 来保持数据库中的列名为 'foo'。这在你处理一个已有系统时非常有用,因为这样你就不需要为了没有必要的原因去做数据库迁移。
4 个回答
之前的解决方案有问题,因为使用@property会在管理界面和.filter(_foo)时出现麻烦。
一个更好的办法是重写setattr,不过这样做可能会在从数据库初始化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__中加载时,就会用到这个技巧!
如前所述,如果你想实现自己的 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(..)
可以看到),这样就实现了隐藏效果(私有)。请注意,这种写法还允许使用 @property 装饰器,这将是编写可读性更高的代码的一种更好的方式。
再次强调,django 会创建一个 _MyModel
表,里面有两个字段 foo
和 bar
。
模型字段本身就是一个属性,所以我建议你采用第二种方式来避免名字冲突。
当你定义 foo = property(..)
时,它实际上会覆盖掉 foo = models..
这一行,这样这个字段就无法再访问了。
你需要给属性和字段使用不同的名字。实际上,如果你按照示例 #1 的方式来做,当你尝试访问这个属性时,会出现无限循环,因为它会试图返回自己。
补充一下:也许你应该考虑不要使用 _foo
作为字段名,而是用 foo
,然后再为你的属性定义一个不同的名字,因为属性不能在 QuerySet
中使用,所以在进行过滤时,你需要使用实际的字段名。