Определите, является ли атрибут `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). В этом случае self
это пример MyModel
со смесью отложенных и отложенных атрибутов:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if isinstance(f, models.ForeignKey))
Cache Machine выполняет двунаправленную аннулирование через ForeignKey
отношения. Это делается путем зацикливания всех полей в Model
и сохранение ряда указателей в кэше, которые указывают на объекты, которые должны быть признаны недействительными, когда рассматриваемый объект является недействительным.
Использование only()
в Django ORM работает магия метапрограммирования, которая переопределяет необработанные атрибуты с помощью Django DeferredAttribute
реализация. При нормальных обстоятельствах доступ к favorite_color
будет ссылаться DeferredAttribute.__get__
( https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py) и извлеките атрибут либо из кэша результатов, либо из источника данных. Это делается путем извлечения непредставленного представления 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's value isn't a Deferred attribute))
2 ответа
Вот как проверить, отложено ли поле:
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
Это проверит, является ли атрибут отложенным атрибутом и еще не загружен из базы данных:
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)
является вновь созданной моделью Proxy для исходного класса. DeferredAttribute
сначала проверяет локальный диктат экземпляра. Если этого не существует, он загрузит значение из базы данных. Этот метод обходит DeferredAttribute
дескриптор объекта, поэтому значение не будет загружено, если оно не существует.
Это работает в Django 1.4 и 1.7, и, вероятно, в версиях между ними. Обратите внимание, что Django 1.8 в свое время представит get_deferred_fields()
метод, который заменит все это вмешательство во внутренние классы.