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:
parent
6f5dbe9dbe
commit
ba9a2b7544
@ -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
|
||||||
|
@ -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, \
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user