Python/Django-Django模型的动态属性重载

Python/Django-Django模型的动态属性重载,python,django,oop,Python,Django,Oop,我有两个相关的模型: 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 @proper

我有两个相关的模型:

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
,使用临时值作为其
父值。大概是这样的:

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
class ModelProxy(SecondModel):
    class Meta:
         proxy = True
    def __init__(self, temp_value):
         self.parent_value = temp_value
foo = SecondModel()
setattr(foo, 'parent_value', 10)
显然,您不能这样做,因为
parent\u值
是只读属性。我也有许多不同的模型,类似于SecondModel,需要具备这种能力

我考虑过并尝试过几种方法,但似乎没有一种是有效的:

1) 编写Django代理模型是可能的,但是对象的数量相当多,所以我会编写很多类似的代码。此外,似乎存在与重写属性相关的错误:。但看起来是这样的:

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
class ModelProxy(SecondModel):
    class Meta:
         proxy = True
    def __init__(self, temp_value):
         self.parent_value = temp_value
foo = SecondModel()
setattr(foo, 'parent_value', 10)
2) 重载实例上的
父\u值
属性
-如下所示:

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
class ModelProxy(SecondModel):
    class Meta:
         proxy = True
    def __init__(self, temp_value):
         self.parent_value = temp_value
foo = SecondModel()
setattr(foo, 'parent_value', 10)
但是您不能这样做,因为属性是类的成员,而不是实例的成员。我只希望为实例设置临时值

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

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


有什么建议吗?

您可以使用属性设置器:

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

您可以使用属性设置器:

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

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

  • 在每个模型上设置临时父值
  • 调用calculate时,它将检查是否有可用的属性父值,如果没有,它将在计算中使用临时父值
下面的代码应该达到你想要的-抱歉,我还没有能够测试它,但它应该是正确的-如果有任何问题需要编辑,请让我知道

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

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

  • 在每个模型上设置临时父值
  • 调用calculate时,它将检查是否有可用的属性父值,如果没有,它将在计算中使用临时父值
下面的代码应该达到你想要的-抱歉,我还没有能够测试它,但它应该是正确的-如果有任何问题需要编辑,请让我知道

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

我认为您可以使用下面显示的mixin
PropertyOverrideMixin
执行所需的操作,如果某些属性值不可用,那么它将查找前缀为
temp\uz
的相同属性。这将允许您提供在无法查找不动产值时可以使用的临时值

下面是mixin、一些示例模型和一个单元测试,以展示这是如何工作的。希望这能适应你的问题!最后值得一提的是,这里的属性可以与普通对象属性互换,并且仍然可以正常工作

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)

我认为您可以使用下面显示的mixin
PropertyOverrideMixin
执行所需的操作,如果某些属性值不可用,那么它将查找前缀为
temp\uz
的相同属性。这将允许您提供在无法查找不动产值时可以使用的临时值

下面是mixin、一些示例模型和一个单元测试,以展示这是如何工作的。希望这能适应你的问题!最后值得一提的是,这里的属性可以与普通对象属性互换,并且仍然可以正常工作

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)

这并不能完全实现他们所期望的,您必须对每个需要此行为的模型进行此更改,他们正在寻找一种可重用的方法。这并不能完全实现他们所期望的,您必须对每个需要此行为的模型进行此更改,他们正在寻找一种可重用的方法来实现这一点。这比属性设置器更枯燥,但问题是需要这些可重写字段的每个模型往往都有不同的字段。我认为这种方法支持这一点,因为您可以在每个模型上使用不同的
def parent\u value(self)
,这意味着您可以为每个模型自定义父值查找。不,您误解了。我的意思是,有时需要重写的是
parent\u值
,有时它的
另一个\u parent\u值
,或者它既是
parent\u值
又是
另一个\u parent\u值
。啊,我明白了,如果有两个或多个属性需要这种行为,这将不起作用。在这种情况下,我认为您需要使用一些允许您动态拦截所有属性查找并在那里实现此自定义行为的工具。我认为代理的Java实现可以很好地做到这一点,但是我不知道Python是否有类似的功能。祝你好运这比属性设置器更枯燥,但问题是需要这些可重写字段的每个模型往往有不同的字段。我认为这种方法支持这一点,因为您可以在每个模型上有不同的
def parent_value(self)
实现,这意味着您可以为每个模型自定义父值查找。不,您误解了。我的意思是有时候它是