1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Refs #32508 -- Raised TypeError instead of using "assert" on unsupported operations for sliced querysets.

This commit is contained in:
Mariusz Felisiak 2021-03-10 09:16:28 +01:00 committed by GitHub
parent 6f5dbe9dbe
commit ba9a2b7544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 60 additions and 30 deletions

View File

@ -654,9 +654,6 @@ class QuerySet:
"earliest() and latest() require either fields as positional " "earliest() and latest() require either fields as positional "
"arguments or 'get_latest_by' in the model's Meta." "arguments or 'get_latest_by' in the model's Meta."
) )
assert not self.query.is_sliced, \
"Cannot change a query once a slice has been taken."
obj = self._chain() obj = self._chain()
obj.query.set_limits(high=1) obj.query.set_limits(high=1)
obj.query.clear_ordering(force_empty=True) obj.query.clear_ordering(force_empty=True)
@ -664,9 +661,13 @@ class QuerySet:
return obj.get() return obj.get()
def earliest(self, *fields): def earliest(self, *fields):
if self.query.is_sliced:
raise TypeError('Cannot change a query once a slice has been taken.')
return self._earliest(*fields) return self._earliest(*fields)
def latest(self, *fields): def latest(self, *fields):
if self.query.is_sliced:
raise TypeError('Cannot change a query once a slice has been taken.')
return self.reverse()._earliest(*fields) return self.reverse()._earliest(*fields)
def first(self): def first(self):
@ -684,8 +685,8 @@ class QuerySet:
Return a dictionary mapping each of the given IDs to the object with 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. that ID. If `id_list` isn't provided, evaluate the entire QuerySet.
""" """
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot use 'limit' or 'offset' with in_bulk" raise TypeError("Cannot use 'limit' or 'offset' with in_bulk().")
opts = self.model._meta opts = self.model._meta
unique_fields = [ unique_fields = [
constraint.fields[0] constraint.fields[0]
@ -721,9 +722,8 @@ class QuerySet:
def delete(self): def delete(self):
"""Delete the records in the current QuerySet.""" """Delete the records in the current QuerySet."""
self._not_support_combined_queries('delete') self._not_support_combined_queries('delete')
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot use 'limit' or 'offset' with delete." raise TypeError("Cannot use 'limit' or 'offset' with delete().")
if self.query.distinct or self.query.distinct_fields: if self.query.distinct or self.query.distinct_fields:
raise TypeError('Cannot call delete() after .distinct().') raise TypeError('Cannot call delete() after .distinct().')
if self._fields is not None: if self._fields is not None:
@ -772,8 +772,8 @@ class QuerySet:
fields to the appropriate values. fields to the appropriate values.
""" """
self._not_support_combined_queries('update') self._not_support_combined_queries('update')
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot update a query once a slice has been taken." raise TypeError('Cannot update a query once a slice has been taken.')
self._for_write = True self._for_write = True
query = self.query.chain(sql.UpdateQuery) query = self.query.chain(sql.UpdateQuery)
query.add_update_values(kwargs) query.add_update_values(kwargs)
@ -792,8 +792,8 @@ class QuerySet:
code (it requires too much poking around at model internals to be code (it requires too much poking around at model internals to be
useful at that level). useful at that level).
""" """
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot update a query once a slice has been taken." raise TypeError('Cannot update a query once a slice has been taken.')
query = self.query.chain(sql.UpdateQuery) query = self.query.chain(sql.UpdateQuery)
query.add_update_fields(values) query.add_update_fields(values)
# Clear any annotations so that they won't be present in subqueries. # Clear any annotations so that they won't be present in subqueries.
@ -970,10 +970,8 @@ class QuerySet:
return self._filter_or_exclude(True, args, kwargs) return self._filter_or_exclude(True, args, kwargs)
def _filter_or_exclude(self, negate, args, kwargs): def _filter_or_exclude(self, negate, args, kwargs):
if args or kwargs: if (args or kwargs) and self.query.is_sliced:
assert not self.query.is_sliced, \ raise TypeError('Cannot filter a query once a slice has been taken.')
"Cannot filter a query once a slice has been taken."
clone = self._chain() clone = self._chain()
if self._defer_next_filter: if self._defer_next_filter:
self._defer_next_filter = False self._defer_next_filter = False
@ -1163,8 +1161,8 @@ class QuerySet:
def order_by(self, *field_names): def order_by(self, *field_names):
"""Return a new QuerySet instance with the ordering changed.""" """Return a new QuerySet instance with the ordering changed."""
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot reorder a query once a slice has been taken." raise TypeError('Cannot reorder a query once a slice has been taken.')
obj = self._chain() obj = self._chain()
obj.query.clear_ordering(force_empty=False) obj.query.clear_ordering(force_empty=False)
obj.query.add_ordering(*field_names) obj.query.add_ordering(*field_names)
@ -1175,8 +1173,8 @@ class QuerySet:
Return a new QuerySet instance that will select only distinct results. Return a new QuerySet instance that will select only distinct results.
""" """
self._not_support_combined_queries('distinct') self._not_support_combined_queries('distinct')
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot create distinct fields once a slice has been taken." raise TypeError('Cannot create distinct fields once a slice has been taken.')
obj = self._chain() obj = self._chain()
obj.query.add_distinct_fields(*field_names) obj.query.add_distinct_fields(*field_names)
return obj return obj
@ -1185,8 +1183,8 @@ class QuerySet:
order_by=None, select_params=None): order_by=None, select_params=None):
"""Add extra SQL fragments to the query.""" """Add extra SQL fragments to the query."""
self._not_support_combined_queries('extra') self._not_support_combined_queries('extra')
assert not self.query.is_sliced, \ if self.query.is_sliced:
"Cannot change a query once a slice has been taken" raise TypeError('Cannot change a query once a slice has been taken.')
clone = self._chain() clone = self._chain()
clone.query.add_extra(select, select_params, where, params, tables, order_by) clone.query.add_extra(select, select_params, where, params, tables, order_by)
return clone return clone

View File

@ -574,8 +574,8 @@ class Query(BaseExpression):
""" """
assert self.model == rhs.model, \ assert self.model == rhs.model, \
"Cannot combine queries on two different base models." "Cannot combine queries on two different base models."
assert not self.is_sliced, \ if self.is_sliced:
"Cannot combine queries once a slice has been taken." raise TypeError('Cannot combine queries once a slice has been taken.')
assert self.distinct == rhs.distinct, \ assert self.distinct == rhs.distinct, \
"Cannot combine a unique query with a non-unique query." "Cannot combine a unique query with a non-unique query."
assert self.distinct_fields == rhs.distinct_fields, \ assert self.distinct_fields == rhs.distinct_fields, \

View File

@ -318,6 +318,9 @@ Miscellaneous
rather than via the recommended ``AdminSite.urls`` property, or rather than via the recommended ``AdminSite.urls`` property, or
``AdminSite.get_urls()`` method. ``AdminSite.get_urls()`` method.
* Unsupported operations on a sliced queryset now raise ``TypeError`` instead
of ``AssertionError``.
.. _deprecated-features-4.0: .. _deprecated-features-4.0:
Features deprecated in 4.0 Features deprecated in 4.0

View File

@ -275,6 +275,10 @@ class OnDeleteTests(TestCase):
class DeletionTests(TestCase): class DeletionTests(TestCase):
def test_sliced_queryset(self):
msg = "Cannot use 'limit' or 'offset' with delete()."
with self.assertRaisesMessage(TypeError, msg):
M.objects.all()[0:5].delete()
def test_m2m(self): def test_m2m(self):
m = M.objects.create() m = M.objects.create()

View File

@ -98,6 +98,11 @@ class DistinctOnTests(TestCase):
c2 = c1.distinct('pk') c2 = c1.distinct('pk')
self.assertNotIn('OUTER JOIN', str(c2.query)) self.assertNotIn('OUTER JOIN', str(c2.query))
def test_sliced_queryset(self):
msg = 'Cannot create distinct fields once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Staff.objects.all()[0:5].distinct('name')
def test_transform(self): def test_transform(self):
new_name = self.t1.name.upper() new_name = self.t1.name.upper()
self.assertNotEqual(self.t1.name, new_name) self.assertNotEqual(self.t1.name, new_name)

View File

@ -81,6 +81,11 @@ class EarliestOrLatestTests(TestCase):
Article.objects.model._meta.get_latest_by = ('pub_date', 'expire_date') Article.objects.model._meta.get_latest_by = ('pub_date', 'expire_date')
self.assertEqual(Article.objects.filter(pub_date=datetime(2005, 7, 28)).earliest(), a4) self.assertEqual(Article.objects.filter(pub_date=datetime(2005, 7, 28)).earliest(), a4)
def test_earliest_sliced_queryset(self):
msg = 'Cannot change a query once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[0:5].earliest()
def test_latest(self): def test_latest(self):
# Because no Articles exist yet, latest() raises ArticleDoesNotExist. # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
with self.assertRaises(Article.DoesNotExist): with self.assertRaises(Article.DoesNotExist):
@ -143,6 +148,11 @@ class EarliestOrLatestTests(TestCase):
Article.objects.model._meta.get_latest_by = ('pub_date', 'expire_date') Article.objects.model._meta.get_latest_by = ('pub_date', 'expire_date')
self.assertEqual(Article.objects.filter(pub_date=datetime(2005, 7, 27)).latest(), a3) self.assertEqual(Article.objects.filter(pub_date=datetime(2005, 7, 27)).latest(), a3)
def test_latest_sliced_queryset(self):
msg = 'Cannot change a query once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[0:5].latest()
def test_latest_manual(self): def test_latest_manual(self):
# You can still use latest() with a model that doesn't have # You can still use latest() with a model that doesn't have
# "get_latest_by" set -- just pass in the field name manually. # "get_latest_by" set -- just pass in the field name manually.

View File

@ -244,6 +244,11 @@ class LookupTests(TestCase):
with self.assertRaisesMessage(ValueError, msg % field_name): with self.assertRaisesMessage(ValueError, msg % field_name):
Model.objects.in_bulk(field_name=field_name) Model.objects.in_bulk(field_name=field_name)
def test_in_bulk_sliced_queryset(self):
msg = "Cannot use 'limit' or 'offset' with in_bulk()."
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[0:5].in_bulk([self.a1.id, self.a2.id])
def test_values(self): def test_values(self):
# values() returns a list of dictionaries instead of object instances -- # values() returns a list of dictionaries instead of object instances --
# and you can specify which fields you want to retrieve. # and you can specify which fields you want to retrieve.

View File

@ -700,7 +700,8 @@ class Queries1Tests(TestCase):
) )
self.assertQuerysetEqual(q.reverse(), []) self.assertQuerysetEqual(q.reverse(), [])
q.query.low_mark = 1 q.query.low_mark = 1
with self.assertRaisesMessage(AssertionError, 'Cannot change a query once a slice has been taken'): msg = 'Cannot change a query once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
q.extra(select={'foo': "1"}) q.extra(select={'foo': "1"})
self.assertQuerysetEqual(q.defer('meal'), []) self.assertQuerysetEqual(q.defer('meal'), [])
self.assertQuerysetEqual(q.only('meal'), []) self.assertQuerysetEqual(q.only('meal'), [])
@ -2359,15 +2360,18 @@ class QuerySetSupportsPythonIdioms(TestCase):
) )
def test_slicing_cannot_filter_queryset_once_sliced(self): def test_slicing_cannot_filter_queryset_once_sliced(self):
with self.assertRaisesMessage(AssertionError, "Cannot filter a query once a slice has been taken."): msg = 'Cannot filter a query once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[0:5].filter(id=1) Article.objects.all()[0:5].filter(id=1)
def test_slicing_cannot_reorder_queryset_once_sliced(self): def test_slicing_cannot_reorder_queryset_once_sliced(self):
with self.assertRaisesMessage(AssertionError, "Cannot reorder a query once a slice has been taken."): msg = 'Cannot reorder a query once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[0:5].order_by('id') Article.objects.all()[0:5].order_by('id')
def test_slicing_cannot_combine_queries_once_sliced(self): def test_slicing_cannot_combine_queries_once_sliced(self):
with self.assertRaisesMessage(AssertionError, "Cannot combine queries once a slice has been taken."): msg = 'Cannot combine queries once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[0:1] & Article.objects.all()[4:5] Article.objects.all()[0:1] & Article.objects.all()[4:5]
def test_slicing_negative_indexing_not_supported_for_single_element(self): def test_slicing_negative_indexing_not_supported_for_single_element(self):
@ -2417,7 +2421,8 @@ class WeirdQuerysetSlicingTests(TestCase):
self.assertQuerysetEqual(Article.objects.all()[0:0], []) self.assertQuerysetEqual(Article.objects.all()[0:0], [])
self.assertQuerysetEqual(Article.objects.all()[0:0][:10], []) self.assertQuerysetEqual(Article.objects.all()[0:0][:10], [])
self.assertEqual(Article.objects.all()[:0].count(), 0) self.assertEqual(Article.objects.all()[:0].count(), 0)
with self.assertRaisesMessage(TypeError, 'Cannot reverse a query once a slice has been taken.'): msg = 'Cannot change a query once a slice has been taken.'
with self.assertRaisesMessage(TypeError, msg):
Article.objects.all()[:0].latest('created') Article.objects.all()[:0].latest('created')
def test_empty_resultset_sql(self): def test_empty_resultset_sql(self):

View File

@ -130,7 +130,7 @@ class AdvancedTests(TestCase):
""" """
method = DataPoint.objects.all()[:2].update method = DataPoint.objects.all()[:2].update
msg = 'Cannot update a query once a slice has been taken.' msg = 'Cannot update a query once a slice has been taken.'
with self.assertRaisesMessage(AssertionError, msg): with self.assertRaisesMessage(TypeError, msg):
method(another_value='another thing') method(another_value='another thing')
def test_update_respects_to_field(self): def test_update_respects_to_field(self):