Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #7270 -- Added the ability to follow reverse OneToOneFields in …

…select_related(). Thanks to George Vilches, Ben Davis, and Alex Gaynor for their work on various stages of this patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12307 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 58cd220f51d5e294cb9e67c12a6e9d08523e282f 1 parent 8e8d4b5
Russell Keith-Magee authored January 27, 2010
4  django/db/models/fields/related.py
@@ -189,7 +189,7 @@ class SingleRelatedObjectDescriptor(object):
@@ -319,7 +319,7 @@ def __set__(self, instance, value):
74  django/db/models/query.py
@@ -1116,6 +1116,29 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
1116 1116
     """
1117 1117
     Helper function that recursively returns an object with the specified
1118 1118
     related attributes already populated.
  1119
+
  1120
+    This method may be called recursively to populate deep select_related()
  1121
+    clauses.
  1122
+
  1123
+    Arguments:
  1124
+     * klass - the class to retrieve (and instantiate)
  1125
+     * row - the row of data returned by the database cursor
  1126
+     * index_start - the index of the row at which data for this
  1127
+       object is known to start
  1128
+     * max_depth - the maximum depth to which a select_related()
  1129
+       relationship should be explored.
  1130
+     * cur_depth - the current depth in the select_related() tree.
  1131
+       Used in recursive calls to determin if we should dig deeper.
  1132
+     * requested - A dictionary describing the select_related() tree
  1133
+       that is to be retrieved. keys are field names; values are
  1134
+       dictionaries describing the keys on that related object that
  1135
+       are themselves to be select_related().
  1136
+     * offset - the number of additional fields that are known to
  1137
+       exist in `row` for `klass`. This usually means the number of
  1138
+       annotated results on `klass`.
  1139
+     * only_load - if the query has had only() or defer() applied,
  1140
+       this is the list of field names that will be returned. If None,
  1141
+       the full field list for `klass` can be assumed.
1119 1142
     """
1120 1143
     if max_depth and requested is None and cur_depth > max_depth:
1121 1144
         # We've recursed deeply enough; stop now.
@@ -1127,14 +1150,18 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
1127 1150
         # Handle deferred fields.
1128 1151
         skip = set()
1129 1152
         init_list = []
1130  
-        pk_val = row[index_start + klass._meta.pk_index()]
  1153
+        # Build the list of fields that *haven't* been requested
1131 1154
         for field in klass._meta.fields:
1132 1155
             if field.name not in load_fields:
1133 1156
                 skip.add(field.name)
1134 1157
             else:
1135 1158
                 init_list.append(field.attname)
  1159
+        # Retrieve all the requested fields
1136 1160
         field_count = len(init_list)
1137 1161
         fields = row[index_start : index_start + field_count]
  1162
+        # If all the select_related columns are None, then the related
  1163
+        # object must be non-existent - set the relation to None.
  1164
+        # Otherwise, construct the related object.
1138 1165
         if fields == (None,) * field_count:
1139 1166
             obj = None
1140 1167
         elif skip:
@@ -1143,14 +1170,20 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
1143 1170
         else:
1144 1171
             obj = klass(*fields)
1145 1172
     else:
  1173
+        # Load all fields on klass
1146 1174
         field_count = len(klass._meta.fields)
1147 1175
         fields = row[index_start : index_start + field_count]
  1176
+        # If all the select_related columns are None, then the related
  1177
+        # object must be non-existent - set the relation to None.
  1178
+        # Otherwise, construct the related object.
1148 1179
         if fields == (None,) * field_count:
1149 1180
             obj = None
1150 1181
         else:
1151 1182
             obj = klass(*fields)
1152 1183
 
1153 1184
     index_end = index_start + field_count + offset
  1185
+    # Iterate over each related object, populating any
  1186
+    # select_related() fields
1154 1187
     for f in klass._meta.fields:
1155 1188
         if not select_related_descend(f, restricted, requested):
1156 1189
             continue
@@ -1158,12 +1191,51 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
1158 1191
             next = requested[f.name]
1159 1192
         else:
1160 1193
             next = None
  1194
+        # Recursively retrieve the data for the related object
1161 1195
         cached_row = get_cached_row(f.rel.to, row, index_end, max_depth,
1162 1196
                 cur_depth+1, next)
  1197
+        # If the recursive descent found an object, populate the
  1198
+        # descriptor caches relevant to the object
1163 1199
         if cached_row:
1164 1200
             rel_obj, index_end = cached_row
1165 1201
             if obj is not None:
  1202
+                # If the base object exists, populate the
  1203
+                # descriptor cache
1166 1204
                 setattr(obj, f.get_cache_name(), rel_obj)
  1205
+            if f.unique:
  1206
+                # If the field is unique, populate the
  1207
+                # reverse descriptor cache on the related object
  1208
+                setattr(rel_obj, f.related.get_cache_name(), obj)
  1209
+
  1210
+    # Now do the same, but for reverse related objects.
  1211
+    # Only handle the restricted case - i.e., don't do a depth
  1212
+    # descent into reverse relations unless explicitly requested
  1213
+    if restricted:
  1214
+        related_fields = [
  1215
+            (o.field, o.model)
  1216
+            for o in klass._meta.get_all_related_objects()
  1217
+            if o.field.unique
  1218
+        ]
  1219
+        for f, model in related_fields:
  1220
+            if not select_related_descend(f, restricted, requested, reverse=True):
  1221
+                continue
  1222
+            next = requested[f.related_query_name()]
  1223
+            # Recursively retrieve the data for the related object
  1224
+            cached_row = get_cached_row(model, row, index_end, max_depth,
  1225
+                cur_depth+1, next)
  1226
+            # If the recursive descent found an object, populate the
  1227
+            # descriptor caches relevant to the object
  1228
+            if cached_row:
  1229
+                rel_obj, index_end = cached_row
  1230
+                if obj is not None:
  1231
+                    # If the field is unique, populate the
  1232
+                    # reverse descriptor cache
  1233
+                    setattr(obj, f.related.get_cache_name(), rel_obj)
  1234
+                if rel_obj is not None:
  1235
+                    # If the related object exists, populate
  1236
+                    # the descriptor cache.
  1237
+                    setattr(rel_obj, f.get_cache_name(), obj)
  1238
+
1167 1239
     return obj, index_end
1168 1240
 
1169 1241
 def delete_objects(seen_objs, using):
18  django/db/models/query_utils.py
@@ -197,19 +197,29 @@ def __set__(self, instance, value):
197 197
         """
198 198
         instance.__dict__[self.field_name] = value
199 199
 
200  
-def select_related_descend(field, restricted, requested):
  200
+def select_related_descend(field, restricted, requested, reverse=False):
201 201
     """
202 202
     Returns True if this field should be used to descend deeper for
203 203
     select_related() purposes. Used by both the query construction code
204 204
     (sql.query.fill_related_selections()) and the model instance creation code
205 205
     (query.get_cached_row()).
  206
+
  207
+    Arguments:
  208
+     * field - the field to be checked
  209
+     * restricted - a boolean field, indicating if the field list has been
  210
+       manually restricted using a requested clause)
  211
+     * requested - The select_related() dictionary.
  212
+     * reverse - boolean, True if we are checking a reverse select related
206 213
     """
207 214
     if not field.rel:
208 215
         return False
209  
-    if field.rel.parent_link:
210  
-        return False
211  
-    if restricted and field.name not in requested:
  216
+    if field.rel.parent_link and not reverse:
212 217
         return False
  218
+    if restricted:
  219
+        if reverse and field.related_query_name() not in requested:
  220
+            return False
  221
+        if not reverse and field.name not in requested:
  222
+            return False
213 223
     if not restricted and field.null:
214 224
         return False
215 225
     return True
3  django/db/models/related.py
@@ -45,3 +45,6 @@ def get_accessor_name(self):
68  django/db/models/sql/compiler.py
@@ -520,7 +520,7 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
520 520
 
521 521
         # Setup for the case when only particular related fields should be
522 522
         # included in the related selection.
523  
-        if requested is None and restricted is not False:
  523
+        if requested is None:
524 524
             if isinstance(self.query.select_related, dict):
525 525
                 requested = self.query.select_related
526 526
                 restricted = True
@@ -600,6 +600,72 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
600 600
             self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
601 601
                     used, next, restricted, new_nullable, dupe_set, avoid)
602 602
 
  603
+        if restricted:
  604
+            related_fields = [
  605
+                (o.field, o.model)
  606
+                for o in opts.get_all_related_objects()
  607
+                if o.field.unique
  608
+            ]
  609
+            for f, model in related_fields:
  610
+                if not select_related_descend(f, restricted, requested, reverse=True):
  611
+                    continue
  612
+                # The "avoid" set is aliases we want to avoid just for this
  613
+                # particular branch of the recursion. They aren't permanently
  614
+                # forbidden from reuse in the related selection tables (which is
  615
+                # what "used" specifies).
  616
+                avoid = avoid_set.copy()
  617
+                dupe_set = orig_dupe_set.copy()
  618
+                table = model._meta.db_table
  619
+
  620
+                int_opts = opts
  621
+                alias = root_alias
  622
+                alias_chain = []
  623
+                chain = opts.get_base_chain(f.rel.to)
  624
+                if chain is not None:
  625
+                    for int_model in chain:
  626
+                        # Proxy model have elements in base chain
  627
+                        # with no parents, assign the new options
  628
+                        # object and skip to the next base in that
  629
+                        # case
  630
+                        if not int_opts.parents[int_model]:
  631
+                            int_opts = int_model._meta
  632
+                            continue
  633
+                        lhs_col = int_opts.parents[int_model].column
  634
+                        dedupe = lhs_col in opts.duplicate_targets
  635
+                        if dedupe:
  636
+                            avoid.update(self.query.dupe_avoidance.get(id(opts), lhs_col),
  637
+                                ())
  638
+                            dupe_set.add((opts, lhs_col))
  639
+                        int_opts = int_model._meta
  640
+                        alias = self.query.join(
  641
+                            (alias, int_opts.db_table, lhs_col, int_opts.pk.column),
  642
+                            exclusions=used, promote=True, reuse=used
  643
+                        )
  644
+                        alias_chain.append(alias)
  645
+                        for dupe_opts, dupe_col in dupe_set:
  646
+                            self.query.update_dupe_avoidance(dupe_opts, dupe_col, alias)
  647
+                    dedupe = f.column in opts.duplicate_targets
  648
+                    if dupe_set or dedupe:
  649
+                        avoid.update(self.query.dupe_avoidance.get((id(opts), f.column), ()))
  650
+                        if dedupe:
  651
+                            dupe_set.add((opts, f.column))
  652
+                alias = self.query.join(
  653
+                    (alias, table, f.rel.get_related_field().column, f.column),
  654
+                    exclusions=used.union(avoid),
  655
+                    promote=True
  656
+                )
  657
+                used.add(alias)
  658
+                columns, aliases = self.get_default_columns(start_alias=alias,
  659
+                    opts=model._meta, as_pairs=True)
  660
+                self.query.related_select_cols.extend(columns)
  661
+                self.query.related_select_fields.extend(model._meta.fields)
  662
+
  663
+                next = requested.get(f.related_query_name(), {})
  664
+                new_nullable = f.null or None
  665
+
  666
+                self.fill_related_selections(model._meta, table, cur_depth+1,
  667
+                    used, next, restricted, new_nullable)
  668
+
603 669
     def deferred_to_columns(self):
604 670
         """
605 671
         Converts the self.deferred_loading data structure to mapping of table
24  docs/ref/models/querysets.txt
@@ -619,17 +619,29 @@ This is also valid::
619 619
 
620 620
 ...and would also pull in the ``building`` relation.
621 621
 
622  
-You can only refer to ``ForeignKey`` relations in the list of fields passed to
623  
-``select_related``. You *can* refer to foreign keys that have ``null=True``
624  
-(unlike the default ``select_related()`` call). It's an error to use both a
625  
-list of fields and the ``depth`` parameter in the same ``select_related()``
626  
-call, since they are conflicting options.
  622
+You can refer to any ``ForeignKey`` or ``OneToOneField`` relation in
  623
+the list of fields passed to ``select_related``. Ths includes foreign
  624
+keys that have ``null=True`` (unlike the default ``select_related()``
  625
+call). It's an error to use both a list of fields and the ``depth``
  626
+parameter in the same ``select_related()`` call, since they are
  627
+conflicting options.
627 628
 
628 629
 .. versionadded:: 1.0
629 630
 
630 631
 Both the ``depth`` argument and the ability to specify field names in the call
631 632
 to ``select_related()`` are new in Django version 1.0.
632 633
 
  634
+.. versionchanged:: 1.2
  635
+
  636
+You can also refer to the reverse direction of a ``OneToOneFields`` in
  637
+the list of fields passed to ``select_related`` -- that is, you can traverse
  638
+a ``OneToOneField`` back to the object on which the field is defined. Instead
  639
+of specifying the field name, use the ``related_name`` for the field on the
  640
+related object.
  641
+
  642
+``OneToOneFields`` will not be traversed in the reverse direction if you
  643
+are performing a depth-based ``select_related``.
  644
+
633 645
 .. _queryset-extra:
634 646
 
635 647
 ``extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)``
@@ -1335,7 +1347,7 @@ extract two field values, where only one is expected::
1335 1347
         entries = Entry.objects.filter(blog__in=list(values))
1336 1348
 
1337 1349
     Note the ``list()`` call around the Blog ``QuerySet`` to force execution of
1338  
-    the first query. Without it, a nested query would be executed, because 
  1350
+    the first query. Without it, a nested query would be executed, because
1339 1351
     :ref:`querysets-are-lazy`.
1340 1352
 
1341 1353
 gt
0  tests/regressiontests/select_related_onetoone/__init__.py
No changes.
46  tests/regressiontests/select_related_onetoone/models.py
... ...
@@ -0,0 +1,46 @@
83  tests/regressiontests/select_related_onetoone/tests.py
... ...
@@ -0,0 +1,83 @@

0 notes on commit 58cd220

Please sign in to comment.
Something went wrong with that request. Please try again.