Django模型字段上可以使用属性吗?

46 投票
4 回答
47177 浏览
提问于 2025-04-15 14:28

我觉得问这个问题最好的方式是用一些代码来说明...我可以这样做吗:

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 个回答

10

之前的解决方案有问题,因为使用@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__中加载时,就会用到这个技巧!

20

如前所述,如果你想实现自己的 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 表,里面有两个字段 foobar

26

模型字段本身就是一个属性,所以我建议你采用第二种方式来避免名字冲突。

当你定义 foo = property(..) 时,它实际上会覆盖掉 foo = models.. 这一行,这样这个字段就无法再访问了。

你需要给属性和字段使用不同的名字。实际上,如果你按照示例 #1 的方式来做,当你尝试访问这个属性时,会出现无限循环,因为它会试图返回自己。

补充一下:也许你应该考虑不要使用 _foo 作为字段名,而是用 foo,然后再为你的属性定义一个不同的名字,因为属性不能在 QuerySet 中使用,所以在进行过滤时,你需要使用实际的字段名。

撰写回答