Déterminer si un attribut est un `DeferredAttribute` dans django

Le Contexte


j'ai localisé un bug plutôt critique dans la machine de Cache Django qui fait que sa logique d'invalidation perd son esprit après une mise à jour de Django 1.4 à 1.7.

le bug est localisé sur les invocations de only() sur les modèles qui étendent la machine de cache CachingMixin. Il en résulte des récursions profondes qui parfois casser la pile, mais sinon créer énorme flush_lists que cache machine utilise pour bi-directionnel invalidation pour les modèles ForeignKey relations.

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()

Le Bug


le bogue se produit dans les cas suivants: lines(https://github.com/jbalogh/django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254). Dans ce cas self est une instance de MyModel avec un mélange d'attributs différés et non découragés:

    fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
                if isinstance(f, models.ForeignKey))

Cache de la Machine ne invalidation bidirectionnelle à travers ForeignKey relations. Il le fait en bouclant tous les champs dans un Model et stocker une série de pointeurs dans le cache qui pointent vers des objets qui doivent être invalidés lorsque l'objet en question est invalidé.

only() dans l'ORM de Django fait un peu de méta-programmation de la magie qui l'emporte sur le unfetched attributs avec Django DeferredAttribute mise en œuvre. Dans des circonstances normales, un accès à favorite_color invoquent DeferredAttribute.__get__(https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146) et récupérez l'attribut soit dans le cache de résultat, soit dans la source de données. Il le fait en récupérant la représentation non découragée de la Model en question et l'appel d'une autre only() requête sur elle.

C'est le problème quand boucler sur les clés étrangères dans le Model et en accédant à leurs valeurs, Cachine Machine introduit un récursion involontaire. getattr(self, f.attname) sur un attribut qui est reporté induit une extraction d'un Model qui a l' CachingMixin appliqué et a des attributs différés. Cela recommence tout le processus de mise en cache.

La Question


je voudrais ouvrir un PR pour corriger ceci et je crois que la réponse à ceci est aussi simple que sauter au-dessus des attributs différés, mais je ne suis pas sûr comment le faire parce que l'accès à l'attribut provoque le extraction de démarrage du processus.

si je n'ai qu'une poignée sur une instance de Model avec un mélange d'attributs différés et non découragés, y a-t-il un moyen de déterminer si un attribut est un DeferredAttributesans accéder?

    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))
17
demandé sur nsfyn55 2015-01-29 21:58:37

2 réponses

Voici comment vérifier si un champ est différé:

from django.db.models.query_utils import DeferredAttribute

is_deferred = isinstance(model_instance.__class__.__dict__.get(field.attname), DeferredAttribute):

extrait de:https://github.com/django/django/blob/1.9.4/django/db/models/base.py#L393

8
répondu Alexey Kuleshevich 2016-03-17 19:19:16

ceci vérifiera si l'attribut est un attribut différé et n'est pas encore chargé à partir de la base de données:

fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
                if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))

en Interne, type(self) est un nouvellement créé modèle de remplacement pour la classe d'origine. DeferredAttribute vérifie d'abord le dict local de l'instance. Si cela n'existe pas, il chargera la valeur de la base de données. Cette méthode contourne le DeferredAttribute descripteur d'objet pour que la valeur ne soit pas chargée si elle n'existe pas.

cela fonctionne dans Django 1.4 et 1.7, et probablement dans les versions intermédiaires. Notez que Django 1.8 introduira le get_deferred_fields() méthode qui supplantera toute cette ingérence avec les internes de la classe.

5
répondu knbk 2015-02-02 20:29:30