Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Improvements to [8608] to fix an infinite loop (for exclude(generic_r…

…elation)).

Also comes with approximately 67% less stupidity in the table joins for
filtering on generic relations.

Fixed #5937, hopefully for good, this time.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8644 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 4cd03ef5d985a76236d47c4fd9c041531722e6c0 1 parent 1abfb1d
Malcolm Tredinnick authored August 28, 2008
7  django/contrib/contenttypes/generic.py
@@ -157,15 +157,18 @@ def db_type(self):
157 157
         # same db_type as well.
158 158
         return None
159 159
 
160  
-    def extra_filters(self, pieces, pos):
  160
+    def extra_filters(self, pieces, pos, negate):
161 161
         """
162 162
         Return an extra filter to the queryset so that the results are filtered
163 163
         on the appropriate content type.
164 164
         """
  165
+        if negate:
  166
+            return []
165 167
         ContentType = get_model("contenttypes", "contenttype")
166 168
         content_type = ContentType.objects.get_for_model(self.model)
167 169
         prefix = "__".join(pieces[:pos + 1])
168  
-        return "%s__%s" % (prefix, self.content_type_field_name), content_type
  170
+        return [("%s__%s" % (prefix, self.content_type_field_name),
  171
+            content_type)]
169 172
 
170 173
 class ReverseGenericRelatedObjectsDescriptor(object):
171 174
     """
39  django/db/models/sql/query.py
@@ -1058,9 +1058,11 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
1058 1058
 
1059 1059
         try:
1060 1060
             field, target, opts, join_list, last, extra_filters = self.setup_joins(
1061  
-                    parts, opts, alias, True, allow_many, can_reuse=can_reuse)
  1061
+                    parts, opts, alias, True, allow_many, can_reuse=can_reuse,
  1062
+                    negate=negate, process_extras=process_extras)
1062 1063
         except MultiJoin, e:
1063  
-            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]))
  1064
+            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
  1065
+                    can_reuse)
1064 1066
             return
1065 1067
         final = len(join_list)
1066 1068
         penultimate = last.pop()
@@ -1196,7 +1198,8 @@ def add_q(self, q_object, used_aliases=None):
1196 1198
             self.used_aliases = used_aliases
1197 1199
 
1198 1200
     def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
1199  
-            allow_explicit_fk=False, can_reuse=None):
  1201
+            allow_explicit_fk=False, can_reuse=None, negate=False,
  1202
+            process_extras=True):
1200 1203
         """
1201 1204
         Compute the necessary table joins for the passage through the fields
1202 1205
         given in 'names'. 'opts' is the Options class for the current model
@@ -1205,7 +1208,10 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
1205 1208
         many-to-one joins will always create a new alias (necessary for
1206 1209
         disjunctive filters). If can_reuse is not None, it's a list of aliases
1207 1210
         that can be reused in these joins (nothing else can be reused in this
1208  
-        case).
  1211
+        case). Finally, 'negate' is used in the same sense as for add_filter()
  1212
+        -- it indicates an exclude() filter, or something similar. It is only
  1213
+        passed in here so that it can be passed to a field's extra_filter() for
  1214
+        customised behaviour.
1209 1215
 
1210 1216
         Returns the final field involved in the join, the target database
1211 1217
         column (used for any 'where' constraint), the final 'opts' value and the
@@ -1271,8 +1277,8 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
1271 1277
                 exclusions.update(self.dupe_avoidance.get((id(opts), dupe_col),
1272 1278
                         ()))
1273 1279
 
1274  
-            if hasattr(field, 'extra_filters'):
1275  
-                extra_filters.append(field.extra_filters(names, pos))
  1280
+            if process_extras and hasattr(field, 'extra_filters'):
  1281
+                extra_filters.extend(field.extra_filters(names, pos, negate))
1276 1282
             if direct:
1277 1283
                 if m2m:
1278 1284
                     # Many-to-many field defined on the current model.
@@ -1295,10 +1301,15 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
1295 1301
                     int_alias = self.join((alias, table1, from_col1, to_col1),
1296 1302
                             dupe_multis, exclusions, nullable=True,
1297 1303
                             reuse=can_reuse)
1298  
-                    alias = self.join((int_alias, table2, from_col2, to_col2),
1299  
-                            dupe_multis, exclusions, nullable=True,
1300  
-                            reuse=can_reuse)
1301  
-                    joins.extend([int_alias, alias])
  1304
+                    if int_alias == table2 and from_col2 == to_col2:
  1305
+                        joins.append(int_alias)
  1306
+                        alias = int_alias
  1307
+                    else:
  1308
+                        alias = self.join(
  1309
+                                (int_alias, table2, from_col2, to_col2),
  1310
+                                dupe_multis, exclusions, nullable=True,
  1311
+                                reuse=can_reuse)
  1312
+                        joins.extend([int_alias, alias])
1302 1313
                 elif field.rel:
1303 1314
                     # One-to-one or many-to-one field
1304 1315
                     if cached_data:
@@ -1391,7 +1402,7 @@ def update_dupe_avoidance(self, opts, col, alias):
1391 1402
             except KeyError:
1392 1403
                 self.dupe_avoidance[ident, name] = set([alias])
1393 1404
 
1394  
-    def split_exclude(self, filter_expr, prefix):
  1405
+    def split_exclude(self, filter_expr, prefix, can_reuse):
1395 1406
         """
1396 1407
         When doing an exclude against any kind of N-to-many relation, we need
1397 1408
         to use a subquery. This method constructs the nested query, given the
@@ -1399,10 +1410,11 @@ def split_exclude(self, filter_expr, prefix):
1399 1410
         N-to-many relation field.
1400 1411
         """
1401 1412
         query = Query(self.model, self.connection)
1402  
-        query.add_filter(filter_expr)
  1413
+        query.add_filter(filter_expr, can_reuse=can_reuse)
1403 1414
         query.set_start(prefix)
1404 1415
         query.clear_ordering(True)
1405  
-        self.add_filter(('%s__in' % prefix, query), negate=True, trim=True)
  1416
+        self.add_filter(('%s__in' % prefix, query), negate=True, trim=True,
  1417
+                can_reuse=can_reuse)
1406 1418
 
1407 1419
     def set_limits(self, low=None, high=None):
1408 1420
         """
@@ -1614,6 +1626,7 @@ def set_start(self, start):
1614 1626
         alias = self.get_initial_alias()
1615 1627
         field, col, opts, joins, last, extra = self.setup_joins(
1616 1628
                 start.split(LOOKUP_SEP), opts, alias, False)
  1629
+        self.unref_alias(alias)
1617 1630
         alias = joins[last[-1]]
1618 1631
         self.select = [(alias, self.alias_map[alias][RHS_JOIN_COL])]
1619 1632
         self.select_fields = [field]
4  tests/modeltests/generic_relations/models.py
@@ -131,8 +131,12 @@ def __unicode__(self):
131 131
 [<TaggedItem: clearish>]
132 132
 
133 133
 # Queries across generic relations respect the content types. Even though there are two TaggedItems with a tag of "fatty", this query only pulls out the one with the content type related to Animals.
  134
+>>> Animal.objects.order_by('common_name')
  135
+[<Animal: Lion>, <Animal: Platypus>]
134 136
 >>> Animal.objects.filter(tags__tag='fatty')
135 137
 [<Animal: Platypus>]
  138
+>>> Animal.objects.exclude(tags__tag='fatty')
  139
+[<Animal: Lion>]
136 140
 
137 141
 # If you delete an object with an explicit Generic relation, the related
138 142
 # objects are deleted when the source object is deleted.

0 notes on commit 4cd03ef

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