19

上下文


我在 Django Cache Machine 中发现了一个相当严重的错误,它在从 Django 1.4 升级到 1.7 后导致它的失效逻辑失去理智。

only()该错误被本地化为对扩展缓存机器的模型的调用CachingMixin。它会导致深度递归,偶尔会破坏堆栈,但否则会产生巨大flush_lists的缓存机器用于关系中模型的双向失效ForeignKey

class MyModel(CachingMixin):
    id = models.CharField(max_length=50, blank=True)
    nickname = models.CharField(max_length=50, blank=True)
    favorite_color = models.CharField(max_length=50, blank=True)
    content_owner = models.ForeignKey(OtherModel)

m = MyModel.objects.only('id').all()

错误


该错误发生在以下几行中(https://github.com/jbalogh/django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254)。在这种情况下self,是MyModel一个混合了延迟和非延迟属性的实例:

    fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
                if isinstance(f, models.ForeignKey))

缓存机器跨ForeignKey关系进行双向失效。它通过遍历 a 中的所有字段Model并在缓存中存储一​​系列指针来实现这一点,这些指针指向在相关对象失效时需要失效的对象。

在 Django ORM 中的使用only()做了一些元编程魔法,用 Django 的DeferredAttribute实现覆盖了未获取的属性。在正常情况下,访问favorite_color将调用DeferredAttribute.__get__https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146)并从结果缓存或数据源。它通过获取相关Model问题的未延迟表示并在其上调用另一个only()查询来做到这一点。

这是在循环中的外键Model并访问它们的值时出现的问题,缓存机器引入了无意的递归。getattr(self, f.attname)在延迟的属性上会导致获取Model具有已CachingMixin应用且具有延迟属性的 a。这将重新开始整个缓存过程。

问题


我想打开一个 PR 来解决这个问题,我相信这个问题的答案就像跳过延迟属性一样简单,但我不知道该怎么做,因为访问属性会导致提取过程开始。

如果我所拥有的只是Model一个混合了延迟和非延迟属性的 a 实例的句柄,有没有办法确定一个属性是否是 aDeferredAttribute 而无需访问它?

    fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
                if (isinstance(f, models.ForeignKey) and <f's value isn't a Deferred attribute))
4

2 回答 2

10

以下是如何检查字段是否被延迟:

from django.db.models.query_utils import DeferredAttribute

is_deferred = isinstance(model_instance.__class__.__dict__.get(field.attname), DeferredAttribute):

取自:https ://github.com/django/django/blob/1.9.4/django/db/models/base.py#L393

于 2015-02-02T20:15:17.430 回答
7

这将检查属性是否是延迟属性并且尚未从数据库中加载:

fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
                if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))

在内部,type(self)是为原始类新创建的代理模型。DeferredAttribute首先检查实例的本地字典。如果不存在,它将从数据库中加载值。此方法绕过DeferredAttribute对象描述符,因此如果该值不存在,则不会加载该值。

这适用于 Django 1.4 和 1.7,并且可能适用于两者之间的版本。请注意,Django 1.8 将在适当的时候引入该get_deferred_fields()方法,该方法将取代所有这些对类内部的干预。

于 2015-02-02T20:22:56.110 回答