避免django Q表达式和as python代码之间的代码重复

lyr7nygr  于 2023-10-21  发布在  Go
关注(0)|答案(2)|浏览(81)

我有一个非常复杂的模型

ALLOWED_STATES = {1,2,3}
class X(models.Model):
    a = models.BooleanField()
    b = models.ForeignKey('Y', on_delete=model.CASCADE)
    c = models.IntegerField()
    d = models.IntegerField()
    @property
    def can_delete(self)
        # this goes on for 6 and clause
        return self.a and self.b.c and self.c in ALLOWED_STATES and self.d != 5 and ..

我在annotate()调用和filter()中也需要这个属性

#in one endpoint
qs = X.objects.filter(...).annoate(can_delete=ExpressionWrapper(Q(a=True, b__c=True, c__in=ALLOWED_STATES,...) & ~Q(d=5), output_field=models.BooleanField())

我想知道是否有一种方法可以将同一属性的这些形式统一为一个,而无需在获取行后在python中调用can_delete。这两个表单已经成为一个维护性问题,因为PM一直在更改can_delete的定义。

gv8xihay

gv8xihay1#

如果在计算can_delete()时使用Q对象会怎么样?
例如,将can_delete()更改为:

@property
    def can_delete(self)
        return X.objects \
            .annotate(can_delete_qs=ExpressionWrapper(Q(a=True, b__c=True, c__in=ALLOWED_STATES,...) & ~Q(d=5), output_field=models.BooleanField()) \
            .get(id=self.id) \
            .can_delete_qs

警告:未经测试的代码。
这样做的代价是,现在每次调用can_delete()都需要调用一次数据库,即使内存中有对象。然而,由于您已经有了一种使用annotate为整个查询集计算this的方法,因此您每次只会为单个对象调用this,这并不坏。
这样做的好处是,您可以在两个地方使用相同的Q表达式。

m1m5dgzv

m1m5dgzv2#

您可以检查对象的属性内的can_delete注解:

def can_delete(self)
     if hasattr( self, 'can_delete_qs'):  # was this object annotated?
         return self.can_delete_qs        # if so return the annotation value
     # this goes on for 6 and clause
     return self.a and self.b.c and ...   # as before

如果前面的查询已经获得了信息,这将避免不必要的DB调用。
但是要注意,当您引用obj.can_delete时,引起注解的信息可能已经在DB中更改了。如果这是可能的,并且删除数据库中现在评估为can_delete_qs = False的内容的后果是严重的,那么您不应该缓存此信息,而是每次都使用数据库查询重新评估它。
为了维护性而不是效率,请删除Python评估,并使用Nico奥德尔的建议替换其余属性。此外,在一个地方(DRY)定义Q对象,并将其导入到任何需要的地方

CAN_DELETE_Q = Q(a=True, b__c=True, c__in=ALLOWED_STATES,...) & ~Q(d=5)

from wherever import CAN_DELETE_Q

...
    .annotate(can_delete_qs=ExpressionWrapper( CAN_DELETE_Q) ...

我不知道的是,你是否可以在Django完全初始化数据库之前定义一个Q表达式。如果没有,你需要一个动态导入或一个惰性评估来延迟评估,直到Django启动并运行之后。

相关问题