【问题标题】:Django: DRY code with property and queryset that have the exact same role?Django:具有完全相同角色的属性和查询集的干代码?
【发布时间】:2019-04-15 10:37:33
【问题描述】:

在我的 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 查询集。

问题

这很好用,但我很恼火两次编写相同的业务逻辑。我有一种感觉,我在这里做的不是“正确的方式”。 我认为我应该在代码级别确保无论我使用属性还是查询集,它总是返回相同的结果。 在这种特定情况下,业务逻辑是两个小数之间的简单乘法,所以应该没问题,但我的代码中会有其他情况更复杂。

您发现改进我的代码的方法了吗?谢谢。

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

    这个想法类似于"hybrid attributes" from SQLAlchemy 一直是asked about before - 我对来自该线程链的任何答案都不太满意(例如将此计算值存储在表上的额外字段中,并且总是确保保持更新)。

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

    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()
    

    然后您可以在属性和查询集都调用的函数中使用此自定义函数。如果您确实开始需要非常复杂的函数作为数据库操作和 Python,那么一些代码重复可能不是最糟糕的权衡。

    【讨论】:

    • 谢谢,非常有趣的答案。我不确定是否使用第一个解决方案,因为它只抽象乘法,而不是整个业务逻辑。我的意思是,即使使用multiplication 函数,我仍然可以将hist_price_hist_price 中的错误字段相乘。
    猜你喜欢
    • 1970-01-01
    • 2021-05-22
    • 2018-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 2010-12-25
    • 2015-01-24
    相关资源
    最近更新 更多