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

Другие вопросы по тегам