确定属性是不是是 django 中的“DeferredAttribute”

Posted

技术标签:

【中文标题】确定属性是不是是 django 中的“DeferredAttribute”【英文标题】:Determine if an attribute is a `DeferredAttribute` in django确定属性是否是 django 中的“DeferredAttribute” 【发布时间】:2015-03-29 03:16:40 【问题描述】:

背景


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

该错误已本地化为在扩展缓存机器的CachingMixin 的模型上调用only()。它会导致深度递归,偶尔会破坏堆栈,但会创建巨大的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)。在这种情况下,selfMyModel 的一个实例,具有延迟和非延迟属性的混合:

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

Cache Machine 在ForeignKey 关系中进行双向失效。它通过遍历 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 中的外键并访问它们的值时出现的问题,Cahine Machine 引入了无意的递归。延迟属性上的getattr(self, f.attname) 会导致提取Model,该CachingMixin 已应用并具有延迟属性。这将重新开始整个缓存过程。

问题


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

如果我只有一个Model 实例的句柄,其中包含延迟和非延迟属性,有没有办法确定属性是否为DeferredAttribute 没有访问是吗?

    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))

【问题讨论】:

【参考方案1】:

以下是检查字段是否被延迟的方法:

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

【讨论】:

使用 DeferredAttribute 的方法在 Django 1.7.3 上似乎对我不起作用 instance.__class__.__dict__ 从来没有常规字段的键。 @CraynicCai,不知道为什么它不适合你。这种方法被 Django 使用了很长时间。这是在 Django 1.7.3 github.com/django/django/blob/1.7.3/django/db/models/… 请注意,这不再正确。由于 1.10 每个 类上的具体字段都是DeferredAttribute,并且上面的代码将不起作用(加载字段时,会为该字段设置实例属性)。从 1.8 开始,您应该改用 Model.get_deferred_fields()【参考方案2】:

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

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) 是原始类的 newly created 代理模型。 DeferredAttribute 首先检查实例的local dict。如果不存在,它将从数据库中加载值。此方法绕过DeferredAttribute 对象描述符,因此如果该值不存在,则不会加载该值。

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

【讨论】:

以上是关于确定属性是不是是 django 中的“DeferredAttribute”的主要内容,如果未能解决你的问题,请参考以下文章

如何确定一个想法是不是应该被视为关系数据库中的表或属性?

django 模型-----定义模型

自定义 Django 小部件/字段:对象没有属性“attrs”

django基础知识之定义模型:

Django中模型

如何以编程方式确定用户是不是从 iOS 中的 Adwords 转换而来,或者是不是可以将此数据转换为用户属性?