Django 中的自定义管理器破坏了prefetch_related缓存



如果我在没有自定义管理器的情况下执行以下所有操作,则一切按预期工作:

class Content(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Meta:
app_label = 'game'
class Requirement(models.Model):
content = models.ForeignKey(Content, on_delete=models.CASCADE, related_name = 'requirements')
value = models.IntegerField(default=0)
def __str__(self):
return "{} requires value {}".format(self.content,self.value)
class Meta:
app_label = 'game'
def testPrefetchOrig():
contents = Content.objects.filter(name__startswith = 'a').prefetch_related('requirements')
for c in contents:
for r in c.requirements.all():
print r
logging.warning(r)
logging.warning("Done with query")

这会预取数据一次,并且永远不会再次预取:

DEBUG:django.db.backends:(0.001) SELECT "game_content"."id", "game_content"."name", "game_content"."deleted" FROM "game_content" WHERE "game_content"."name"::text LIKE 'a%'; args=(u'a%',)
DEBUG:django.db.backends:(0.001) SELECT "game_requirement"."id", "game_requirement"."content_id", "game_requirement"."value", "game_requirement"."deleted" FROM "game_requirement" WHERE "game_requirement"."content_id" IN (5, 6); args=(5, 6)
alphabet requires value 5
WARNING:root:alphabet requires value 5
alphabet requires value 3
WARNING:root:alphabet requires value 3
albatross requires value 1
WARNING:root:albatross requires value 1
albatross requires value 0
WARNING:root:albatross requires value 0
WARNING:root:Done with query

但是,我想使用自定义管理器来处理通过设置已删除标志来"删除"的条目的过滤。

class DeletedItemsQuerySet(models.query.QuerySet):
def get(self, *args, **kwargs):
kwargs['deleted']=False
return models.query.QuerySet.get(self, *args, **kwargs)
def all(self):
return self.filterNoDeleted()
def filterNoDeleted(self, *args, **kwargs):
kwargs['deleted']=False
return models.query.QuerySet.filter(self, *args, **kwargs)
def getDeleted(self, *args, **kwargs):
return models.query.QuerySet.get(self, *args, **kwargs)
def filterDeleted(self, *args, **kwargs):
return models.query.QuerySet.filter(self, *args, **kwargs)
class DeletedItemsManager(models.Manager.from_queryset(DeletedItemsQuerySet)):
def all(self):
return super(models.Manager,self).all().filterNoDeleted()

然后我们修改我们的模型以使用它:

class Content(models.Model):
name = models.CharField(max_length=200)
objects = DeletedItemsManager()
deleted = models.BooleanField(default=False)
def __str__(self):
return self.name
class Meta:
app_label = 'game'
class Requirement(models.Model):
content = models.ForeignKey(Content, on_delete=models.CASCADE, related_name = 'requirements')
value = models.IntegerField(default=0)
deleted = models.BooleanField(default=False)
objects = DeletedItemsManager()
def __str__(self):
return "{} requires value {}".format(self.content,self.value)
class Meta:
app_label = 'game'
def testPrefetchOrig():
contents = Content.objects.filter(name__startswith = 'a').prefetch_related('requirements')
for c in contents:
for r in c.requirements.all():
print r
logging.warning(r)
logging.warning("Done with query")

这会预取数据,但仍会查询它:

DEBUG:django.db.backends:(0.001) SELECT "game_content"."id", "game_content"."name", "game_content"."deleted" FROM "game_content" WHERE "game_content"."name"::text LIKE 'a%'; args=(u'a%',)
DEBUG:django.db.backends:(0.001) SELECT "game_requirement"."id", "game_requirement"."content_id", "game_requirement"."value", "game_requirement"."deleted" FROM "game_requirement" WHERE "game_requirement"."content_id" IN (5, 6); args=(5, 6)
DEBUG:django.db.backends:(0.000) SELECT "game_requirement"."id", "game_requirement"."content_id", "game_requirement"."value", "game_requirement"."deleted" FROM "game_requirement" WHERE ("game_requirement"."content_id" = 5 AND "game_requirement"."deleted" = false); args=(5, False)
alphabet requires value 5
WARNING:root:alphabet requires value 5
alphabet requires value 3
WARNING:root:alphabet requires value 3
DEBUG:django.db.backends:(0.001) SELECT "game_requirement"."id", "game_requirement"."content_id", "game_requirement"."value", "game_requirement"."deleted" FROM "game_requirement" WHERE ("game_requirement"."content_id" = 6 AND "game_requirement"."deleted" = false); args=(6, False)
albatross requires value 1
WARNING:root:albatross requires value 1
albatross requires value 0
WARNING:root:albatross requires value 0
WARNING:root:Done with query

如何使用自定义管理器,但仍prefetch_related工作?

def testPrefetchOrig():
contents = Content.objects.filter(name__startswith = 'a').prefetch_related(Prefetch('requirements', queryset=Requirement.objects.filterNoDeleted(),to_attr='undeletedRequirements'))
for c in contents:
for r in c.undeletedRequirements:
print r
print "Done with query"

好的。 DeletedItemsManager 在每个 all(( 方法调用中添加了一个额外的 filterNoDeleted(( 方法调用。 该筛选器调用会销毁缓存,如 https://docs.djangoproject.com/en/1.11/ref/models/querysets/中的注释

但是,我确实需要过滤掉已删除的相关对象。

我应该能够在需求表中选择所有必需的行,尽管我不确定如何在查询集中设置正确的字段以反映这些结果。 但是,如果我只想阅读结果,我可能会将其变成字典。

有没有人有更优雅的解决方案?

注意:有一个对 Django 进行filtered_relation添加的拉取请求,但这还不适用于prefetch_related。 也许在未来,Django 会支持这个用例。

最新更新