代码之家  ›  专栏  ›  技术社区  ›  Jiaaro

属性在Django模型字段上工作吗?

  •  36
  • Jiaaro  · 技术社区  · 15 年前

    我想问这个问题的最好方法是用一些代码…我可以这样做吗?( 编辑 答:不)

    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_列传递到model字段,可以在数据库中将列名保持为“foo”。当您在一个现有的系统上工作时,如果您不想无缘无故地进行数据库迁移,这将非常有用。

    4 回复  |  直到 6 年前
        1
  •  19
  •   Andre Miller    15 年前

    模型字段已经是属性,所以我认为您必须用第二种方法来避免名称冲突。

    当您定义foo=property(..)时,它实际上会覆盖foo=models.。行,这样字段将不再可访问。

    您需要为属性和字段使用不同的名称。事实上,如果按照示例1中的方法操作,当您尝试访问属性时,会得到一个无限循环,因为它现在试图返回自身。

    编辑:也许您还应该考虑不使用foo作为字段名,而是使用foo,然后为您的属性定义另一个名称,因为属性不能在queryset中使用,所以您在执行筛选时需要使用实际的字段名。

        2
  •  16
  •   Yauhen Yakimovich    12 年前

    如前所述,实现自己的django.db.models.field类的正确替代方法应该使用- dB列 参数和自定义(或隐藏)类属性。我只是在@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
    

    夜猫子 将被分解为 我的模特儿 (如所见) DIR(…) 如此隐藏 private )。请注意,此表单还允许使用 @property decorator 这最终将是编写可读代码的更好方法。

    同样,Django将创建带两个字段的*MyModel表 酒吧 .

        3
  •  5
  •   Justin Alexander    9 年前

    以前的解决方案会受到影响,因为@property会导致admin和.filter(foo)出现问题。

    更好的解决方案是重写 塞特拉特 但这可能会导致从数据库初始化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()
    

    秘密是' 在自我的状态下。__ '.当模型从新的或从 第二节 !

        4
  •  0
  •   djvg Carl Meyer    6 年前

    这取决于 property 是达到目的的一种手段,或是达到目的本身。

    如果您希望在筛选查询集(不需要首先对其进行评估)时使用这种“覆盖”(或“回退”)行为,我认为属性不能做到这一点。 As far as I know ,python属性在数据库级别不起作用,因此不能在queryset过滤器中使用。注意你 可以 使用 _foo 在过滤器中(而不是 foo ,因为它表示一个实际的表列,但随后是 get_foo() 不适用。

    但是,如果您的用例允许,那么 Coalesce() 来自的类 django.db.models.functions ( docs 可能会有帮助。

    集合() ……接受至少两个字段名或 表达式并返回第一个非空值(请注意, 字符串不被视为空值)。…

    这意味着您可以指定 bar 作为覆盖 使用 Coalesce('bar','foo') . 这种回报 酒吧 ,除非 酒吧 null ,在这种情况下,它返回 . 和你一样 GETH() (除了它不适用于空字符串),但是 在数据库级别。

    剩下的问题是如何实现这一点。

    如果你不在很多地方使用它,简单地说 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'))
    

    现在,查询集中的项具有magic属性 bar_otherwise_foo ,可以过滤,例如 queryset.filter(bar_otherwise_foo='what I want') 也可以直接在实例上使用,例如 print(queryset.all()[0].bar_otherwise_foo)

    结果 SQL query queryset.query 表明 集合() 确实可以在数据库级别工作:

    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=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 将自动拥有 巴鲁,否则 注释,可按上述说明使用。

    但是,请注意,例如更新 酒吧 在实例上不会更新批注,因为批注是在查询集上生成的。首先需要重新评估查询集,例如从查询集获取更新的实例。

    可能是一个带有注释的自定义管理器和一个Python的组合 财产 可以用来获得最好的两个世界( example at CodeReview )