Контекст
В 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))