Fixed #30988 -- Deprecated the InvalidQuery exception.
It was barely documented without pointers at its defining location and was abused to prevent misuse of the QuerySet field deferring feature.
This commit is contained in:
parent
cbe4d6203f
commit
11e327a3ff
|
@ -21,7 +21,7 @@ from django.db.models.deletion import Collector
|
||||||
from django.db.models.expressions import Case, Expression, F, Value, When
|
from django.db.models.expressions import Case, Expression, F, Value, When
|
||||||
from django.db.models.fields import AutoField
|
from django.db.models.fields import AutoField
|
||||||
from django.db.models.functions import Cast, Trunc
|
from django.db.models.functions import Cast, Trunc
|
||||||
from django.db.models.query_utils import FilteredRelation, InvalidQuery, Q
|
from django.db.models.query_utils import FilteredRelation, Q
|
||||||
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
|
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
|
||||||
from django.db.utils import NotSupportedError
|
from django.db.utils import NotSupportedError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -1455,7 +1455,9 @@ class RawQuerySet:
|
||||||
try:
|
try:
|
||||||
model_init_names, model_init_pos, annotation_fields = self.resolve_model_init_order()
|
model_init_names, model_init_pos, annotation_fields = self.resolve_model_init_order()
|
||||||
if self.model._meta.pk.attname not in model_init_names:
|
if self.model._meta.pk.attname not in model_init_names:
|
||||||
raise InvalidQuery('Raw query must include the primary key')
|
raise exceptions.FieldDoesNotExist(
|
||||||
|
'Raw query must include the primary key'
|
||||||
|
)
|
||||||
model_cls = self.model
|
model_cls = self.model
|
||||||
fields = [self.model_fields.get(c) for c in self.columns]
|
fields = [self.model_fields.get(c) for c in self.columns]
|
||||||
converters = compiler.get_converters([
|
converters = compiler.get_converters([
|
||||||
|
|
|
@ -8,10 +8,13 @@ circular import difficulties.
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
import warnings
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldDoesNotExist, FieldError
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.utils import tree
|
from django.utils import tree
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
# PathInfo is used when converting lookups (fk__somecol). The contents
|
# PathInfo is used when converting lookups (fk__somecol). The contents
|
||||||
# describe the relation in Model terms (model Options and Fields for both
|
# describe the relation in Model terms (model Options and Fields for both
|
||||||
|
@ -19,8 +22,29 @@ from django.utils import tree
|
||||||
PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct filtered_relation')
|
PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct filtered_relation')
|
||||||
|
|
||||||
|
|
||||||
class InvalidQuery(Exception):
|
class InvalidQueryType(type):
|
||||||
"""The query passed to raw() isn't a safe query to use with raw()."""
|
@property
|
||||||
|
def _subclasses(self):
|
||||||
|
return (FieldDoesNotExist, FieldError)
|
||||||
|
|
||||||
|
def __warn(self):
|
||||||
|
warnings.warn(
|
||||||
|
'The InvalidQuery exception class is deprecated. Use '
|
||||||
|
'FieldDoesNotExist or FieldError instead.',
|
||||||
|
category=RemovedInDjango40Warning,
|
||||||
|
stacklevel=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __instancecheck__(self, instance):
|
||||||
|
self.__warn()
|
||||||
|
return isinstance(instance, self._subclasses) or super().__instancecheck__(instance)
|
||||||
|
|
||||||
|
def __subclasscheck__(self, subclass):
|
||||||
|
self.__warn()
|
||||||
|
return issubclass(subclass, self._subclasses) or super().__subclasscheck__(subclass)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidQuery(Exception, metaclass=InvalidQueryType):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,10 +257,11 @@ def select_related_descend(field, restricted, requested, load_fields, reverse=Fa
|
||||||
if load_fields:
|
if load_fields:
|
||||||
if field.attname not in load_fields:
|
if field.attname not in load_fields:
|
||||||
if restricted and field.name in requested:
|
if restricted and field.name in requested:
|
||||||
raise InvalidQuery("Field %s.%s cannot be both deferred"
|
msg = (
|
||||||
" and traversed using select_related"
|
'Field %s.%s cannot be both deferred and traversed using '
|
||||||
" at the same time." %
|
'select_related at the same time.'
|
||||||
(field.model._meta.object_name, field.name))
|
) % (field.model._meta.object_name, field.name)
|
||||||
|
raise FieldError(msg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ details on these changes.
|
||||||
* The undocumented usage of the :lookup:`isnull` lookup with non-boolean values
|
* The undocumented usage of the :lookup:`isnull` lookup with non-boolean values
|
||||||
as the right-hand side will no longer be allowed.
|
as the right-hand side will no longer be allowed.
|
||||||
|
|
||||||
|
* The ``django.db.models.query_utils.InvalidQuery`` exception class will be
|
||||||
|
removed.
|
||||||
|
|
||||||
See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more
|
See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more
|
||||||
details on these changes.
|
details on these changes.
|
||||||
|
|
||||||
|
|
|
@ -330,6 +330,11 @@ Miscellaneous
|
||||||
* The undocumented usage of the :lookup:`isnull` lookup with non-boolean values
|
* The undocumented usage of the :lookup:`isnull` lookup with non-boolean values
|
||||||
as the right-hand side is deprecated, use ``True`` or ``False`` instead.
|
as the right-hand side is deprecated, use ``True`` or ``False`` instead.
|
||||||
|
|
||||||
|
* The barely documented ``django.db.models.query_utils.InvalidQuery`` exception
|
||||||
|
class is deprecated in favor of
|
||||||
|
:class:`~django.core.exceptions.FieldDoesNotExist` and
|
||||||
|
:class:`~django.core.exceptions.FieldError`.
|
||||||
|
|
||||||
.. _removed-features-3.1:
|
.. _removed-features-3.1:
|
||||||
|
|
||||||
Features removed in 3.1
|
Features removed in 3.1
|
||||||
|
|
|
@ -170,8 +170,9 @@ last names were both retrieved on demand when they were printed.
|
||||||
|
|
||||||
There is only one field that you can't leave out - the primary key
|
There is only one field that you can't leave out - the primary key
|
||||||
field. Django uses the primary key to identify model instances, so it
|
field. Django uses the primary key to identify model instances, so it
|
||||||
must always be included in a raw query. An ``InvalidQuery`` exception
|
must always be included in a raw query. A
|
||||||
will be raised if you forget to include the primary key.
|
:class:`~django.core.exceptions.FieldDoesNotExist` exception will be raised if
|
||||||
|
you forget to include the primary key.
|
||||||
|
|
||||||
Adding annotations
|
Adding annotations
|
||||||
------------------
|
------------------
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.db.models.query_utils import InvalidQuery
|
from django.core.exceptions import FieldError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
|
@ -113,7 +113,7 @@ class DeferTests(AssertionMixin, TestCase):
|
||||||
'Field Primary.related cannot be both deferred and traversed '
|
'Field Primary.related cannot be both deferred and traversed '
|
||||||
'using select_related at the same time.'
|
'using select_related at the same time.'
|
||||||
)
|
)
|
||||||
with self.assertRaisesMessage(InvalidQuery, msg):
|
with self.assertRaisesMessage(FieldError, msg):
|
||||||
Primary.objects.defer("related").select_related("related")[0]
|
Primary.objects.defer("related").select_related("related")[0]
|
||||||
|
|
||||||
def test_only_select_related_raises_invalid_query(self):
|
def test_only_select_related_raises_invalid_query(self):
|
||||||
|
@ -121,7 +121,7 @@ class DeferTests(AssertionMixin, TestCase):
|
||||||
'Field Primary.related cannot be both deferred and traversed using '
|
'Field Primary.related cannot be both deferred and traversed using '
|
||||||
'select_related at the same time.'
|
'select_related at the same time.'
|
||||||
)
|
)
|
||||||
with self.assertRaisesMessage(InvalidQuery, msg):
|
with self.assertRaisesMessage(FieldError, msg):
|
||||||
Primary.objects.only("name").select_related("related")[0]
|
Primary.objects.only("name").select_related("related")[0]
|
||||||
|
|
||||||
def test_defer_foreign_keys_are_deferred_and_not_traversed(self):
|
def test_defer_foreign_keys_are_deferred_and_not_traversed(self):
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldDoesNotExist, FieldError
|
||||||
|
from django.db.models.query_utils import InvalidQuery
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidQueryTests(SimpleTestCase):
|
||||||
|
@contextmanager
|
||||||
|
def assert_warns(self):
|
||||||
|
msg = (
|
||||||
|
'The InvalidQuery exception class is deprecated. Use '
|
||||||
|
'FieldDoesNotExist or FieldError instead.'
|
||||||
|
)
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango40Warning, msg):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def test_type(self):
|
||||||
|
self.assertIsInstance(InvalidQuery(), InvalidQuery)
|
||||||
|
|
||||||
|
def test_isinstance(self):
|
||||||
|
for exception in (FieldError, FieldDoesNotExist):
|
||||||
|
with self.assert_warns(), self.subTest(exception.__name__):
|
||||||
|
self.assertIsInstance(exception(), InvalidQuery)
|
||||||
|
|
||||||
|
def test_issubclass(self):
|
||||||
|
for exception in (FieldError, FieldDoesNotExist, InvalidQuery):
|
||||||
|
with self.assert_warns(), self.subTest(exception.__name__):
|
||||||
|
self.assertIs(issubclass(exception, InvalidQuery), True)
|
|
@ -1,8 +1,8 @@
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db.models.query import RawQuerySet
|
from django.db.models.query import RawQuerySet
|
||||||
from django.db.models.query_utils import InvalidQuery
|
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
|
@ -235,7 +235,8 @@ class RawQueryTests(TestCase):
|
||||||
|
|
||||||
def test_missing_fields_without_PK(self):
|
def test_missing_fields_without_PK(self):
|
||||||
query = "SELECT first_name, dob FROM raw_query_author"
|
query = "SELECT first_name, dob FROM raw_query_author"
|
||||||
with self.assertRaisesMessage(InvalidQuery, 'Raw query must include the primary key'):
|
msg = 'Raw query must include the primary key'
|
||||||
|
with self.assertRaisesMessage(FieldDoesNotExist, msg):
|
||||||
list(Author.objects.raw(query))
|
list(Author.objects.raw(query))
|
||||||
|
|
||||||
def test_annotations(self):
|
def test_annotations(self):
|
||||||
|
|
Loading…
Reference in New Issue