From 93ffa81bc59ff13c1a5c07f5bd22d003756e1da5 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 23 Jul 2019 05:04:06 -0700 Subject: [PATCH] Refs #30657 -- Made DeferredAttribute.__init__() to take a field instance instead of a field name. --- django/contrib/gis/db/models/proxy.py | 13 ++++++------- django/db/models/fields/__init__.py | 2 +- django/db/models/query_utils.py | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/django/contrib/gis/db/models/proxy.py b/django/contrib/gis/db/models/proxy.py index 282109b7254..adf2bae1e5b 100644 --- a/django/contrib/gis/db/models/proxy.py +++ b/django/contrib/gis/db/models/proxy.py @@ -14,10 +14,9 @@ class SpatialProxy(DeferredAttribute): Initialize on the given Geometry or Raster class (not an instance) and the corresponding field. """ - self._field = field self._klass = klass self._load_func = load_func or klass - super().__init__(field.attname) + super().__init__(field) def __get__(self, instance, cls=None): """ @@ -32,7 +31,7 @@ class SpatialProxy(DeferredAttribute): # Getting the value of the field. try: - geo_value = instance.__dict__[self._field.attname] + geo_value = instance.__dict__[self.field.attname] except KeyError: geo_value = super().__get__(instance, cls) @@ -44,7 +43,7 @@ class SpatialProxy(DeferredAttribute): # Otherwise, a geometry or raster object is built using the field's # contents, and the model's corresponding attribute is set. geo_obj = self._load_func(geo_value) - setattr(instance, self._field.attname, geo_obj) + setattr(instance, self.field.attname, geo_obj) return geo_obj def __set__(self, instance, value): @@ -56,7 +55,7 @@ class SpatialProxy(DeferredAttribute): To set rasters, use JSON or dict values. """ # The geographic type of the field. - gtype = self._field.geom_type + gtype = self.field.geom_type if gtype == 'RASTER' and (value is None or isinstance(value, (str, dict, self._klass))): # For raster fields, assure input is None or a string, dict, or @@ -67,7 +66,7 @@ class SpatialProxy(DeferredAttribute): # general GeometryField is used. if value.srid is None: # Assigning the field SRID if the geometry has no SRID. - value.srid = self._field.srid + value.srid = self.field.srid elif value is None or isinstance(value, (str, memoryview)): # Set geometries with None, WKT, HEX, or WKB pass @@ -76,5 +75,5 @@ class SpatialProxy(DeferredAttribute): instance.__class__.__name__, gtype, type(value))) # Setting the objects dictionary with the value, and returning. - instance.__dict__[self._field.attname] = value + instance.__dict__[self.field.attname] = value return value diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 6bd8f41a856..a16713a3978 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -738,7 +738,7 @@ class Field(RegisterLookupMixin): # if you have a classmethod and a field with the same name, then # such fields can't be deferred (we don't have a check for this). if not getattr(cls, self.attname, None): - setattr(cls, self.attname, DeferredAttribute(self.attname)) + setattr(cls, self.attname, DeferredAttribute(self)) if self.choices is not None: setattr(cls, 'get_%s_display' % self.name, partialmethod(cls._get_FIELD_display, field=self)) diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 90289d0da23..7a667814f43 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -116,8 +116,8 @@ class DeferredAttribute: A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed. """ - def __init__(self, field_name): - self.field_name = field_name + def __init__(self, field): + self.field = field def __get__(self, instance, cls=None): """ @@ -127,26 +127,26 @@ class DeferredAttribute: if instance is None: return self data = instance.__dict__ - if data.get(self.field_name, self) is self: + field_name = self.field.attname + if data.get(field_name, self) is self: # Let's see if the field is part of the parent chain. If so we # might be able to reuse the already loaded value. Refs #18343. - val = self._check_parent_chain(instance, self.field_name) + val = self._check_parent_chain(instance) if val is None: - instance.refresh_from_db(fields=[self.field_name]) - val = getattr(instance, self.field_name) - data[self.field_name] = val - return data[self.field_name] + instance.refresh_from_db(fields=[field_name]) + val = getattr(instance, field_name) + data[field_name] = val + return data[field_name] - def _check_parent_chain(self, instance, name): + def _check_parent_chain(self, instance): """ Check if the field value can be fetched from a parent field already loaded in the instance. This can be done if the to-be fetched field is a primary key field. """ opts = instance._meta - f = opts.get_field(name) - link_field = opts.get_ancestor_link(f.model) - if f.primary_key and f != link_field: + link_field = opts.get_ancestor_link(self.field.model) + if self.field.primary_key and self.field != link_field: return getattr(instance, link_field.attname) return None