2010-08-30 4 views
23

Sie haben versucht, dies jetzt für ein paar Stunden herauszufinden und haben nichts erreicht.Wie gelingt es Djangos ORM, fremde Objekte zu holen, wenn sie darauf zugreifen?

class other(models.Model): 
    user = models.ForeignKey(User) 


others = other.objects.all() 
o = others[0] 

An diesem Punkt die ORM nicht für das o.user Objekt gefragt, aber wenn ich etwas tun, was das Objekt berührt, es lädt es aus der Datenbank.

type(o.user) 

wird eine Last von der Datenbank führen.

Was ich verstehen möchte ist, wie sie diese Magie tun. Was ist der pythonische Elfenstaub, der dafür verantwortlich ist? Ja, ich habe mir die Quelle angesehen, ich bin ratlos.

Antwort

52

Django verwendet eine metaclass (django.db.models.base.ModelBase), um die Erstellung von Modellklassen anzupassen. Für jedes Objekt, das als Klassenattribut für das Modell definiert ist (user ist das, was uns hier interessiert), sucht Django zuerst, ob es eine contribute_to_class Methode definiert. Wenn die Methode definiert ist, ruft Django sie auf und ermöglicht dem Objekt, die Modellklasse während der Erstellung anzupassen. Wenn das Objekt contribute_to_class nicht definiert, wird es einfach der Klasse zugewiesen, die setattr verwendet.

Da ForeignKey ein Django-Modellfeld ist, definiert es contribute_to_class. Wenn die Metaklasse aufruft, ist der Wert ModelClass.user eine Instanz von django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor.

ReverseSingleRelatedObjectDescriptor ist ein Objekt, das Pythons descriptor protocol implementiert, um anzupassen, was passiert, wenn auf eine Instanz der Klasse als Attribut einer anderen Klasse zugegriffen wird. In diesem Fall wird der Deskriptor für lazily load verwendet und gibt die zugehörige Modellinstanz beim ersten Zugriff aus der Datenbank zurück.

# make a user and an instance of our model 
>>> user = User(username="example") 
>>> my_instance = MyModel(user=user) 

# user is a ReverseSingleRelatedObjectDescriptor 
>>> MyModel.user 
<django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor object> 

# user hasn't been loaded, yet 
>>> my_instance._user_cache 
AttributeError: 'MyModel' object has no attribute '_user_cache' 

# ReverseSingleRelatedObjectDescriptor.__get__ loads the user 
>>> my_instance.user 
<User: example> 

# now the user is cached and won't be looked up again 
>>> my_instance._user_cache 
<User: example> 

Die ReverseSingleRelatedObjectDescriptor.__get__ Methode wird jedes Mal, wenn die user Attribut auf dem Modellinstanz zugegriffen wird, aber es ist intelligent genug, um nur einmal das zugehörige Objekt zu suchen und dann eine Cache-Version auf nachfolgende Anrufe zurückzukehren.

+1

+1. Ich mag die Erklärung. –

+0

Danke, genau das wollte ich lernen. – boatcoder

+0

Gibt es eine einfache Möglichkeit, den Cache das zugehörige Objekt zu erzwingen? Ich gooogled django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor und fand diesen Beitrag ... – yegle

1

Dies wird nicht erklären wie genau Django geht darüber, aber was Sie sehen, ist Lazy Loading in Aktion. Lazy Loading ist ein weithin bekanntes Designmuster, das die Initialisierung von Objekten bis zu dem Punkt, an dem sie benötigt werden, auf verschiebt. In Ihrem Fall wird entweder o = others[0] oder type(o.user) ausgeführt. Diese Wikipedia article kann Ihnen einige Einblicke in den Prozess geben.

+0

Ja, ich verstehe das Warum, ich suchte nach der Erklärung, wie sie das geschafft haben. – boatcoder

0

Eigenschaften können verwendet werden, um dieses Verhalten zu implementieren. Grundsätzlich wird Ihre Klassendefinition eine Klasse ähnlich die folgenden erzeugen:

class other(models.Model): 
    def _get_user(self): 
     ## o.users being accessed 
     return User.objects.get(other_id=self.id) 

    def _set_user(self, v): 
     ## ... 

    user = property(_get_user, _set_user) 

Die Abfrage auf Benutzer wird nicht ausgeführt, bis Sie die .user einer ‚anderen‘ Instanz zugreifen.

Verwandte Themen