Django:具有完全相同角色的具有属性和查询集的DRY代码?

时间:2019-04-15 10:37:33

标签: python django django-models

在我的Django代码中,对于OrderedArticle个对象,我需要计算一个hist_price,它是2个字段的乘积:hist_unit_price * quantity

我做这件事的第一个方法是一个简单的属性:

class OrderedArticle(Model):
    @property
    def hist_price(self):
        return self.hist_unit_price * self.quantity

然后,我意识到当我需要对这些价格进行大量计算时,出于性能原因,我无法使用此属性,而是必须在数据库级别计算hist_price。这就是为什么我为此编写了一个自定义查询集的原因:

class OrderOperationQuerySet(Queryset):

    @staticmethod
    def _hist_price(orderable_field):  # can be an OrderedArticle or another object here
        return ExpressionWrapper(
            F(f'{orderable_field}__hist_unit_price') * F(f'{orderable_field}__quantity'),
            output_field=DecimalField())

当前,我的代码中同时使用了hist_price属性和_hist_price查询集。

问题

这很好用,但是我很讨厌两次编写相同的业务逻辑。我觉得我在这里没有“正确的方法”。 我认为我应该确保在代码级别上,无论我使用属性还是查询集,它始终返回相同的结果。 在这种特定情况下,业务逻辑是两个小数之间的简单乘法,因此应该可以,但是在我的代码中还会有其他情况,情况会更加复杂。

您看到一种改进我的代码的方法吗?谢谢。

1 个答案:

答案 0 :(得分:1)

这个想法类似于"hybrid attributes" from SQLAlchemyasked about before -我对那个线程链的任何答案都不满意(就像将这个计算值存储在一个额外的字段上一样)表格,并始终确保对其进行更新)。

只要重载所需的运算符以接受实际值或F()对象(例如基本数学运算符),您的属性和ExpressionWrapper函数都可能会使用一些内部函数。

def multiplication(x, y):
    return x * y  # trivial here but it could be any mathematical expression really


def _hist_price(orderable_field):
    return ExpressionWrapper(
        multiplication(
            F(f"{orderable_field}__hist_unit_price"),
            F(f"{orderable_field}__quantity")
        ),
        output_field=DecimalField()
    )

@property
def hist_price(self):
    return multiplication(self.hist_unit_price, self.quantity)

如果在这些混合函数之一中它比基本的数值运算复杂,并且您希望避免重复的业务逻辑,则需要编写一个包装函子,该函子可以使用python函数为属性调用程序解析为正确的输出,以及可以对查询集调用者的F对象进行操作的函数,以维持运算符的重载。但这将导致代码进行内部检查以找出要执行的操作的代码,这可能是不直观的,因此这实际上是一种折衷方案。

在粗糙的伪代码中,这些自定义函数之一就像

def _hybrid_lower(value):
   if isinstance(value, F):  # maybe F would be sufficient or some other class higher in the hierarchy
       # https://docs.djangoproject.com/en/2.2/ref/models/expressions/#func-expressions
       return Func(value, function='LOWER')
   else:
       return value.lower()

,然后可以在属性和queryset都调用的函数中使用此自定义函数。如果确实开始需要真正复杂的功能(例如数据库操作和Python),则某些重复的代码可能并不是最坏的选择。