属性是否适用于django模型字段?

时间:2009-09-21 14:13:18

标签: python django properties model

我认为提出这个问题的最好方法是使用一些代码......我可以这样做吗? (编辑:答案:否)

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'。当您处理现有系统并且不希望无缘无故地进行数据库迁移时,这非常有用

4 个答案:

答案 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() ...接受至少两个字段名称的列表,或者   表达式并返回第一个非空值(请注意,   字符串不视为空值)。 ...

这意味着您可以使用barfoo指定为Coalesce('bar','foo')的替代。除非barbar,否则它将返回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 lookupcustom(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)。