Ответ 1
Django использует метакласс (django.db.models.base.ModelBase
) для настройки создания классов моделей. Для каждого объекта, определенного как атрибут класса в модели (user
- тот, о котором мы здесь заботимся), Django сначала ищет, определяет ли он метод contribute_to_class
. Если метод определен, Django вызывает его, позволяя объекту настраивать класс модели по мере его создания. Если объект не определяет contribute_to_class
, он просто присваивается классу с помощью setattr
.
Так как ForeignKey
- поле модели Django, оно определяет contribute_to_class
. Когда metaclass ModelBase
вызывает ForeignKey.contribute_to_class
, значение, присвоенное ModelClass.user
, является экземпляром django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor
.
ReverseSingleRelatedObjectDescriptor
- это объект, который реализует протокол описаний Python , чтобы настроить, что происходит, когда экземпляр класса доступен как атрибут другого класса. В этом случае дескриптор используется для ленивой загрузки и возвращает экземпляр связанной модели из базы данных при первом обращении к ней.
# 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>
Метод ReverseSingleRelatedObjectDescriptor.__get__
вызывается каждый раз, когда к экземпляру модели обращается атрибут user
, но он достаточно интеллектуальный, чтобы только один раз искать связанный объект, а затем возвращать кешированную версию при последующих вызовах.