From 806ba19bbff311b7d567857ae61db6ff84af4a2c Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 25 Jul 2019 20:45:55 +0200 Subject: [PATCH] Added Query.is_sliced property. Previously, we used Query.can_filter() mainly to check if a query is sliced what was confusing. --- django/db/models/query.py | 20 ++++++++++---------- django/db/models/sql/query.py | 13 ++++++++----- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 3342ef2086..ab4f3fc534 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -635,7 +635,7 @@ class QuerySet: "arguments or 'get_latest_by' in the model's Meta." ) - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot change a query once a slice has been taken." obj = self._chain() obj.query.set_limits(high=1) @@ -664,7 +664,7 @@ class QuerySet: Return a dictionary mapping each of the given IDs to the object with that ID. If `id_list` isn't provided, evaluate the entire QuerySet. """ - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot use 'limit' or 'offset' with in_bulk" if field_name != 'pk' and not self.model._meta.get_field(field_name).unique: raise ValueError("in_bulk()'s field_name must be a unique field but %r isn't." % field_name) @@ -689,7 +689,7 @@ class QuerySet: def delete(self): """Delete the records in the current QuerySet.""" - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot use 'limit' or 'offset' with delete." if self._fields is not None: @@ -731,7 +731,7 @@ class QuerySet: Update all elements in the current QuerySet, setting all the given fields to the appropriate values. """ - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.chain(sql.UpdateQuery) @@ -751,7 +751,7 @@ class QuerySet: code (it requires too much poking around at model internals to be useful at that level). """ - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot update a query once a slice has been taken." query = self.query.chain(sql.UpdateQuery) query.add_update_fields(values) @@ -903,7 +903,7 @@ class QuerySet: def _filter_or_exclude(self, negate, *args, **kwargs): if args or kwargs: - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot filter a query once a slice has been taken." clone = self._chain() @@ -1072,7 +1072,7 @@ class QuerySet: def order_by(self, *field_names): """Return a new QuerySet instance with the ordering changed.""" - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot reorder a query once a slice has been taken." obj = self._chain() obj.query.clear_ordering(force_empty=False) @@ -1083,7 +1083,7 @@ class QuerySet: """ Return a new QuerySet instance that will select only distinct results. """ - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot create distinct fields once a slice has been taken." obj = self._chain() obj.query.add_distinct_fields(*field_names) @@ -1093,7 +1093,7 @@ class QuerySet: order_by=None, select_params=None): """Add extra SQL fragments to the query.""" self._not_support_combined_queries('extra') - assert self.query.can_filter(), \ + assert not self.query.is_sliced, \ "Cannot change a query once a slice has been taken" clone = self._chain() clone.query.add_extra(select, select_params, where, params, tables, order_by) @@ -1101,7 +1101,7 @@ class QuerySet: def reverse(self): """Reverse the ordering of the QuerySet.""" - if not self.query.can_filter(): + if self.query.is_sliced: raise TypeError('Cannot reverse a query once a slice has been taken.') clone = self._chain() clone.query.standard_ordering = not clone.query.standard_ordering diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index e15e64cde4..0369e01348 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -413,7 +413,6 @@ class Query(BaseExpression): """ if not self.annotation_select: return {} - has_limit = self.low_mark != 0 or self.high_mark is not None existing_annotations = [ annotation for alias, annotation in self.annotations.items() @@ -430,7 +429,7 @@ class Query(BaseExpression): # those operations must be done in a subquery so that the query # aggregates on the limit and/or distinct results instead of applying # the distinct and limit after the aggregation. - if (isinstance(self.group_by, tuple) or has_limit or existing_annotations or + if (isinstance(self.group_by, tuple) or self.is_sliced or existing_annotations or self.distinct or self.combinator): from django.db.models.sql.subqueries import AggregateQuery outer_query = AggregateQuery(self.model) @@ -438,7 +437,7 @@ class Query(BaseExpression): inner_query.select_for_update = False inner_query.select_related = False inner_query.set_annotation_mask(self.annotation_select) - if not has_limit and not self.distinct_fields: + if not self.is_sliced and not self.distinct_fields: # Queries with distinct_fields need ordering and when a limit # is applied we must take the slice from the ordered query. # Otherwise no need for ordering. @@ -548,7 +547,7 @@ class Query(BaseExpression): """ assert self.model == rhs.model, \ "Cannot combine queries on two different base models." - assert self.can_filter(), \ + assert not self.is_sliced, \ "Cannot combine queries once a slice has been taken." assert self.distinct == rhs.distinct, \ "Cannot combine a unique query with a non-unique query." @@ -1762,6 +1761,10 @@ class Query(BaseExpression): """Clear any existing limits.""" self.low_mark, self.high_mark = 0, None + @property + def is_sliced(self): + return self.low_mark != 0 or self.high_mark is not None + def has_limit_one(self): return self.high_mark is not None and (self.high_mark - self.low_mark) == 1 @@ -1771,7 +1774,7 @@ class Query(BaseExpression): Typically, this means no limits or offsets have been put on the results. """ - return not self.low_mark and self.high_mark is None + return not self.is_sliced def clear_select_clause(self): """Remove all fields from SELECT clause."""