Определите, является ли атрибут `DeferredAttribute` в django
Контекст
В 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
. Он делает это, перебирая все поля в Model
и сохраняя ряд указателей в кеше, которые указывают на объекты, которые нуждаются в недействительности, когда объект, о котором идет речь, недействителен.
Использование only()
в ORM Django выполняет некоторую магию метапрограммирования, которая переопределяет необработанные атрибуты с реализацией Django DeferredAttribute
. При нормальных обстоятельствах доступ к favorite_color
будет вызывать DeferredAttribute.__get__
(https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146) и извлекать атрибут либо из кеша результатов, либо из источник данных. Он делает это, выбирая необработанное представление рассматриваемого Model
и вызывая на нем другой запрос only()
.
Это проблема при переходе по внешним ключам в Model
и доступе к их значениям, Cachine 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 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
Ответ 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)
представляет собой недавно созданную модель прокси для исходного класса. A DeferredAttribute
сначала проверяет локальный dict экземпляра. Если этого не существует, он будет загружать значение из базы данных. Этот метод обходит дескриптор объекта DeferredAttribute
, поэтому значение не будет загружено, если оно не существует.
Это работает в Django 1.4 и 1.7 и, предположительно, в версиях между ними. Обратите внимание, что Django 1.8 в свое время представит метод get_deferred_fields()
, который заменит все это вмешательство с внутренними классами.