1

我有两个相关的模型:

class FirstModel(models.Model):
    base_value = models.FloatField()

class SecondModel(models.Model):
    parent = models.ForeignKey(FirstModel)

    @property
    def parent_value(self):
        return self.parent.base_value

    @property
    def calculate(self):
        return self.parent_value + 1

一般来说,SecondModel.calculate多用于与其相关的上下文中FirstModel。但是,我有时希望能够calculate使用临时值作为它的parent_value. 像这样的东西:

foo = SecondModel()
# would look in the database for the related FirstModel and add 1 to its base_value
foo.calculate

foo.parent_value = 10
foo.calculate      # should return 11

显然你不能这样做,因为它parent_value是一个只读属性。我也有许多类似于 SecondModel 的不同模型需要具备这种能力。

我已经考虑并尝试了几件事,但似乎都没有奏效:

1)编写一个 Django 代理模型- 可能,但是对象的数量相当多,所以我会编写很多类似的代码。此外,似乎存在与覆盖属性相关的错误:https ://code.djangoproject.com/ticket/16176 。但它看起来像这样:

class ModelProxy(SecondModel):
    class Meta:
         proxy = True
    def __init__(self, temp_value):
         self.parent_value = temp_value

2)重载parent_value实例上的属性- 像这样:

foo = SecondModel()
setattr(foo, 'parent_value', 10)

但你不能这样做,因为属性是类的成员,而不是实例。我只想为实例设置临时值

3)元类或类生成器?- 似乎过于复杂。另外,我不确定如果我使用元类动态生成作为models.Model 的子类的类会发生什么。我会遇到数据库表不同步的问题吗?

4)用适当的 getter 和 setter 重写属性?- 也许解决方案是重写 SecondModel 以便可以设置属性?

有什么建议么?

4

3 回答 3

1

我相信 mixin 可以实现您想要做的事情,并提供一种简单且可重用的方式来支持计算中的临时值。通过将以下示例混合到您希望此行为的每个模型中,您可以:

  • 在每个模型上设置一个临时父值
  • 调用calculate时,它会检查是否有可用的属性parent_value,如果没有,它将在计算中使用临时父值。

下面的代码应该可以实现您正在寻找的东西 - 抱歉我还不能测试它,但它应该是正确的 - 如果有任何需要编辑的问题,请告诉我。

class CalculateMixin(object):

    @property
    def temp_parent_value(self):
        return self._temp_parent_value

    @temp_parent_value.setter
    def temp_parent_value(self, value):
        self._temp_parent_value = value

    @property
    def calculate(self):
        parent_value = self.parent_value if self.parent_value else self.temp_parent_value
        return parent_value + 1


class SecondModel(models.Model, CalculateMixin):
    parent = models.ForeignKey(FirstModel)

    self.temp_parent_value = 'Whatever value you desire'

    @property
    def parent_value(self):
        return self.parent.base_value
于 2013-06-25T09:58:15.840 回答
0

您可以使用属性设置器:

class SecondModel(models.Model):
    _base_value = None

    parent = models.ForeignKey(FirstModel)

    @property
    def parent_value(self):
        if self._base_value is None:
            return self.parent.base_value
        else:
            return self._base_value

    @parent_value.setter
    def parent_value(self, value):
        self._base_value = value

    @property
    def calculate(self):
        return self.parent_value + 1
于 2013-06-25T09:54:40.410 回答
0

I think you can do what you need to using the mixin PropertyOverrideMixin shown below which, if some property value isn't available, then it will look for the same property prefixed with temp_. This will allow you to provide temporary values that can be used when the real property values can't be looked up.

Below is the mixin, some example models and a unit test to show how this can work. Hopefully this can be adapted for your problem! Finally it is worth mentioning that the properties here can be interchanged with normal object attributes and it should still all work.

from unittest import TestCase


class PropertyOverrideMixin(object):

    def __getattribute__(self, name):
        """
        Override that, if an attribute isn't found on the object, then it instead
        looks for the same attribute prefixed with 'temp_' and tries to return
        that value.
        """

        try:
            return object.__getattribute__(self, name)
        except AttributeError:
            temp_name = 'temp_{0}'.format(name)
            return object.__getattribute__(self, temp_name)


class ParentModel(object):

    attribute_1 = 'parent value 1'


class Model(PropertyOverrideMixin):

    # Set our temporary property values
    @property
    def temp_attribute_1(self):
        return 'temporary value 1'

    @property
    def temp_attribute_2(self):
        return 'temporary value 2'

    # Attribute 1 looks up value on its parent
    @property
    def attribute_1(self):
        return self.parent.attribute_1

    # Attribute 2 looks up a value on this object
    @property
    def attribute_2(self):
        return self.some_other_attribute


class PropertyOverrideMixinTest(TestCase):

    def test_attributes(self):
        model = Model()

        # Looking up attributes 1 and 2 returns the temp versions at first
        self.assertEquals('temporary value 1', model.attribute_1)
        self.assertEquals('temporary value 2', model.attribute_2)

        # Now we set the parent, and lookup of attribute 1 works on the parent
        model.parent = ParentModel()
        self.assertEquals('parent value 1', model.attribute_1)

        # now we set attribute_2, so this gets returned and the temporary ignored
        model.some_other_attribute = 'value 2'
        self.assertEquals('value 2', model.attribute_2)
于 2013-07-02T12:12:38.273 回答