【问题标题】:Custom Manager in Django Destroys Caching for prefetch_relatedDjango 中的自定义管理器破坏 prefetch_related 的缓存
【发布时间】:2017-07-11 19:40:15
【问题描述】:

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

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 工作?

【问题讨论】:

  • 等等。我有点明白发生了什么。管理器的 all() 方法包含一个过滤器,用于过滤掉已删除的对象,但该过滤器会破坏缓存。

标签: django django-models prefetch django-custom-manager


【解决方案1】:

好的。 DeletedItemsManager 在每个 all() 方法调用上添加一个额外的 filterNoDeleted() 方法调用。根据https://docs.djangoproject.com/en/1.11/ref/models/querysets/ 中的注释,该过滤器调用会破坏缓存

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

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

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

注意:有一个对 Django 的过滤关系添加的拉取请求,但这不适用于 prefetch_related。也许将来 Django 会支持这个用例。

【讨论】:

    【解决方案2】:
    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 中自动化这个过程,但如果我理解正确,我不能只向 DeletedItemsManager 添加一个方法,因为我会调用 Content 管理器的 prefetch_related,它在此示例中也使用 DeletedItemsManager,但一般来说,它可能是不同的管理器。最好不要自动执行此操作。
    猜你喜欢
    • 1970-01-01
    • 2021-12-07
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    • 2020-05-19
    相关资源
    最近更新 更多