Определите, является ли атрибут `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))

Ответы

Ответ 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(), который заменит все это вмешательство с внутренними классами.