mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #8046 -- The first filter() call on a related manager for many-to-many
fields no longer creates duplicate copies of the join table(s). Basically, this means filters on the join table (for ManyToManyField(through=...)) and complex filters in the normal (non-through) case don't produce incorrect or duplicate results. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8472 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -376,7 +376,7 @@ def create_many_related_manager(superclass, through=False): | |||||||
|                 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) |                 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) | ||||||
|  |  | ||||||
|         def get_query_set(self): |         def get_query_set(self): | ||||||
|             return superclass.get_query_set(self).filter(**(self.core_filters)) |             return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters)) | ||||||
|  |  | ||||||
|         # If the ManyToMany relation has an intermediary model,  |         # If the ManyToMany relation has an intermediary model,  | ||||||
|         # the add and remove methods do not exist. |         # the add and remove methods do not exist. | ||||||
|   | |||||||
| @@ -121,6 +121,7 @@ class QuerySet(object): | |||||||
|         self.query = query or sql.Query(self.model, connection) |         self.query = query or sql.Query(self.model, connection) | ||||||
|         self._result_cache = None |         self._result_cache = None | ||||||
|         self._iter = None |         self._iter = None | ||||||
|  |         self._sticky_filter = False | ||||||
|  |  | ||||||
|     ######################## |     ######################## | ||||||
|     # PYTHON MAGIC METHODS # |     # PYTHON MAGIC METHODS # | ||||||
| @@ -589,7 +590,10 @@ class QuerySet(object): | |||||||
|     def _clone(self, klass=None, setup=False, **kwargs): |     def _clone(self, klass=None, setup=False, **kwargs): | ||||||
|         if klass is None: |         if klass is None: | ||||||
|             klass = self.__class__ |             klass = self.__class__ | ||||||
|         c = klass(model=self.model, query=self.query.clone()) |         query = self.query.clone() | ||||||
|  |         if self._sticky_filter: | ||||||
|  |             query.filter_is_sticky = True | ||||||
|  |         c = klass(model=self.model, query=query) | ||||||
|         c.__dict__.update(kwargs) |         c.__dict__.update(kwargs) | ||||||
|         if setup and hasattr(c, '_setup_query'): |         if setup and hasattr(c, '_setup_query'): | ||||||
|             c._setup_query() |             c._setup_query() | ||||||
| @@ -607,6 +611,17 @@ class QuerySet(object): | |||||||
|             except StopIteration: |             except StopIteration: | ||||||
|                 self._iter = None |                 self._iter = None | ||||||
|  |  | ||||||
|  |     def _next_is_sticky(self): | ||||||
|  |         """ | ||||||
|  |         Indicates that the next filter call and the one following that should | ||||||
|  |         be treated as a single filter. This is only important when it comes to | ||||||
|  |         determining when to reuse tables for many-to-many filters. Required so | ||||||
|  |         that we can filter naturally on the results of related managers. | ||||||
|  |         """ | ||||||
|  |         obj = self._clone() | ||||||
|  |         obj._sticky_filter = True | ||||||
|  |         return obj | ||||||
|  |  | ||||||
|     def _merge_sanity_check(self, other): |     def _merge_sanity_check(self, other): | ||||||
|         """ |         """ | ||||||
|         Checks that we are merging two comparable QuerySet classes. By default |         Checks that we are merging two comparable QuerySet classes. By default | ||||||
|   | |||||||
| @@ -58,6 +58,8 @@ class Query(object): | |||||||
|         self.select_fields = [] |         self.select_fields = [] | ||||||
|         self.related_select_fields = [] |         self.related_select_fields = [] | ||||||
|         self.dupe_avoidance = {} |         self.dupe_avoidance = {} | ||||||
|  |         self.used_aliases = set() | ||||||
|  |         self.filter_is_sticky = False | ||||||
|  |  | ||||||
|         # SQL-related attributes |         # SQL-related attributes | ||||||
|         self.select = [] |         self.select = [] | ||||||
| @@ -78,7 +80,7 @@ class Query(object): | |||||||
|  |  | ||||||
|         # These are for extensions. The contents are more or less appended |         # These are for extensions. The contents are more or less appended | ||||||
|         # verbatim to the appropriate clause. |         # verbatim to the appropriate clause. | ||||||
|         self.extra_select = SortedDict()  # Maps col_alias -> col_sql. |         self.extra_select = SortedDict()  # Maps col_alias -> (col_sql, params). | ||||||
|         self.extra_tables = () |         self.extra_tables = () | ||||||
|         self.extra_where = () |         self.extra_where = () | ||||||
|         self.extra_params = () |         self.extra_params = () | ||||||
| @@ -185,6 +187,11 @@ class Query(object): | |||||||
|         obj.extra_where = self.extra_where |         obj.extra_where = self.extra_where | ||||||
|         obj.extra_params = self.extra_params |         obj.extra_params = self.extra_params | ||||||
|         obj.extra_order_by = self.extra_order_by |         obj.extra_order_by = self.extra_order_by | ||||||
|  |         if self.filter_is_sticky and self.used_aliases: | ||||||
|  |             obj.used_aliases = self.used_aliases.copy() | ||||||
|  |         else: | ||||||
|  |             obj.used_aliases = set() | ||||||
|  |         obj.filter_is_sticky = False | ||||||
|         obj.__dict__.update(kwargs) |         obj.__dict__.update(kwargs) | ||||||
|         if hasattr(obj, '_setup_query'): |         if hasattr(obj, '_setup_query'): | ||||||
|             obj._setup_query() |             obj._setup_query() | ||||||
| @@ -1148,12 +1155,11 @@ class Query(object): | |||||||
|         Can also be used to add anything that has an 'add_to_query()' method. |         Can also be used to add anything that has an 'add_to_query()' method. | ||||||
|         """ |         """ | ||||||
|         if used_aliases is None: |         if used_aliases is None: | ||||||
|             used_aliases = set() |             used_aliases = self.used_aliases | ||||||
|         if hasattr(q_object, 'add_to_query'): |         if hasattr(q_object, 'add_to_query'): | ||||||
|             # Complex custom objects are responsible for adding themselves. |             # Complex custom objects are responsible for adding themselves. | ||||||
|             q_object.add_to_query(self, used_aliases) |             q_object.add_to_query(self, used_aliases) | ||||||
|             return |         else: | ||||||
|  |  | ||||||
|             if self.where and q_object.connector != AND and len(q_object) > 1: |             if self.where and q_object.connector != AND and len(q_object) > 1: | ||||||
|                 self.where.start_subtree(AND) |                 self.where.start_subtree(AND) | ||||||
|                 subtree = True |                 subtree = True | ||||||
| @@ -1173,6 +1179,8 @@ class Query(object): | |||||||
|                 self.where.negate() |                 self.where.negate() | ||||||
|             if subtree: |             if subtree: | ||||||
|                 self.where.end_subtree() |                 self.where.end_subtree() | ||||||
|  |         if self.filter_is_sticky: | ||||||
|  |             self.used_aliases = used_aliases | ||||||
|  |  | ||||||
|     def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True, |     def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True, | ||||||
|             allow_explicit_fk=False, can_reuse=None): |             allow_explicit_fk=False, can_reuse=None): | ||||||
|   | |||||||
| @@ -158,4 +158,18 @@ AttributeError: Cannot use create() on a ManyToManyField which specifies an inte | |||||||
|   </object> |   </object> | ||||||
| </django-objects> | </django-objects> | ||||||
|  |  | ||||||
|  | ## Regression test for #8046: | ||||||
|  | Check that we don't involve too many copies of the intermediate table when | ||||||
|  | doing a join. | ||||||
|  |  | ||||||
|  | >>> bob = Person.objects.create(name='Bob') | ||||||
|  | >>> jim = Person.objects.create(name='Jim') | ||||||
|  | >>> rock = Group.objects.create(name='Rock') | ||||||
|  | >>> roll = Group.objects.create(name='Roll') | ||||||
|  | >>> _ = Membership.objects.create(person=bob, group=rock) | ||||||
|  | >>> _ = Membership.objects.create(person=jim, group=rock, price=50) | ||||||
|  | >>> _ = Membership.objects.create(person=bob, group=roll, price=50) | ||||||
|  | >>> rock.members.filter(membership__price=50) | ||||||
|  | [<Person: Jim>] | ||||||
|  |  | ||||||
| """} | """} | ||||||
		Reference in New Issue
	
	Block a user