在我的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
查询集。
问题
这很好用,但是我很讨厌两次编写相同的业务逻辑。我觉得我在这里没有“正确的方法”。 我认为我应该确保在代码级别上,无论我使用属性还是查询集,它始终返回相同的结果。 在这种特定情况下,业务逻辑是两个小数之间的简单乘法,因此应该可以,但是在我的代码中还会有其他情况,情况会更加复杂。
您看到一种改进我的代码的方法吗?谢谢。
答案 0 :(得分:1)
这个想法类似于"hybrid attributes" from SQLAlchemy的asked 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),则某些重复的代码可能并不是最坏的选择。