@@ -48,8 +48,7 @@ def __init__(
4848
4949 def contribute_to_class (self , cls , name , ** kwargs ):
5050 super ().contribute_to_class (cls , name , private_only = True , ** kwargs )
51- # GenericForeignKey is its own descriptor.
52- setattr (cls , self .attname , self )
51+ setattr (cls , self .attname , GenericForeignKeyDescriptor (self ))
5352
5453 def get_attname_column (self ):
5554 attname , column = super ().get_attname_column ()
@@ -161,11 +160,19 @@ def get_content_type(self, obj=None, id=None, using=None, model=None):
161160 # This should never happen. I love comments like this, don't you?
162161 raise Exception ("Impossible arguments to GFK.get_content_type!" )
163162
163+
164+ class GenericForeignKeyDescriptor :
165+ def __init__ (self , field ):
166+ self .field = field
167+
168+ def is_cached (self , instance ):
169+ return self .field .is_cached (instance )
170+
164171 def get_prefetch_querysets (self , instances , querysets = None ):
165172 custom_queryset_dict = {}
166173 if querysets is not None :
167174 for queryset in querysets :
168- ct_id = self .get_content_type (
175+ ct_id = self .field . get_content_type (
169176 model = queryset .query .model , using = queryset .db
170177 ).pk
171178 if ct_id in custom_queryset_dict :
@@ -179,12 +186,12 @@ def get_prefetch_querysets(self, instances, querysets=None):
179186 fk_dict = defaultdict (set )
180187 # We need one instance for each group in order to get the right db:
181188 instance_dict = {}
182- ct_attname = self .model ._meta .get_field (self .ct_field ).attname
189+ ct_attname = self .field . model ._meta .get_field (self . field .ct_field ).attname
183190 for instance in instances :
184191 # We avoid looking for values if either ct_id or fkey value is None
185192 ct_id = getattr (instance , ct_attname )
186193 if ct_id is not None :
187- fk_val = getattr (instance , self .fk_field )
194+ fk_val = getattr (instance , self .field . fk_field )
188195 if fk_val is not None :
189196 fk_dict [ct_id ].add (fk_val )
190197 instance_dict [ct_id ] = instance
@@ -196,7 +203,7 @@ def get_prefetch_querysets(self, instances, querysets=None):
196203 ret_val .extend (custom_queryset_dict [ct_id ].filter (pk__in = fkeys ))
197204 else :
198205 instance = instance_dict [ct_id ]
199- ct = self .get_content_type (id = ct_id , using = instance ._state .db )
206+ ct = self .field . get_content_type (id = ct_id , using = instance ._state .db )
200207 ret_val .extend (ct .get_all_objects_for_this_type (pk__in = fkeys ))
201208
202209 # For doing the join in Python, we have to match both the FK val and
@@ -207,17 +214,17 @@ def gfk_key(obj):
207214 if ct_id is None :
208215 return None
209216 else :
210- model = self .get_content_type (
217+ model = self .field . get_content_type (
211218 id = ct_id , using = obj ._state .db
212219 ).model_class ()
213- return str (getattr (obj , self .fk_field )), model
220+ return str (getattr (obj , self .field . fk_field )), model
214221
215222 return (
216223 ret_val ,
217224 lambda obj : (obj ._meta .pk .value_to_string (obj ), obj .__class__ ),
218225 gfk_key ,
219226 True ,
220- self .name ,
227+ self .field . name ,
221228 False ,
222229 )
223230
@@ -229,43 +236,44 @@ def __get__(self, instance, cls=None):
229236 # reload the same ContentType over and over (#5570). Instead, get the
230237 # content type ID here, and later when the actual instance is needed,
231238 # use ContentType.objects.get_for_id(), which has a global cache.
232- f = self .model ._meta .get_field (self .ct_field )
239+ f = self .field . model ._meta .get_field (self . field .ct_field )
233240 ct_id = getattr (instance , f .attname , None )
234- pk_val = getattr (instance , self .fk_field )
241+ pk_val = getattr (instance , self .field . fk_field )
235242
236- rel_obj = self .get_cached_value (instance , default = None )
237- if rel_obj is None and self .is_cached (instance ):
243+ rel_obj = self .field . get_cached_value (instance , default = None )
244+ if rel_obj is None and self .field . is_cached (instance ):
238245 return rel_obj
239246 if rel_obj is not None :
240247 ct_match = (
241- ct_id == self .get_content_type (obj = rel_obj , using = instance ._state .db ).id
248+ ct_id
249+ == self .field .get_content_type (obj = rel_obj , using = instance ._state .db ).id
242250 )
243251 pk_match = ct_match and rel_obj ._meta .pk .to_python (pk_val ) == rel_obj .pk
244252 if pk_match :
245253 return rel_obj
246254 else :
247255 rel_obj = None
248256 if ct_id is not None :
249- ct = self .get_content_type (id = ct_id , using = instance ._state .db )
257+ ct = self .field . get_content_type (id = ct_id , using = instance ._state .db )
250258 try :
251259 rel_obj = ct .get_object_for_this_type (
252260 using = instance ._state .db , pk = pk_val
253261 )
254262 except ObjectDoesNotExist :
255263 pass
256- self .set_cached_value (instance , rel_obj )
264+ self .field . set_cached_value (instance , rel_obj )
257265 return rel_obj
258266
259267 def __set__ (self , instance , value ):
260268 ct = None
261269 fk = None
262270 if value is not None :
263- ct = self .get_content_type (obj = value )
271+ ct = self .field . get_content_type (obj = value )
264272 fk = value .pk
265273
266- setattr (instance , self .ct_field , ct )
267- setattr (instance , self .fk_field , fk )
268- self .set_cached_value (instance , value )
274+ setattr (instance , self .field . ct_field , ct )
275+ setattr (instance , self .field . fk_field , fk )
276+ self .field . set_cached_value (instance , value )
269277
270278
271279class GenericRel (ForeignObjectRel ):
0 commit comments