1
0
mirror of https://github.com/django/django.git synced 2025-07-01 08:19:19 +00:00

Deprecated QuerySet.extra() except for the tables option.

This commit is contained in:
Simon Charette 2023-03-24 00:19:40 -04:00
parent 5d539daa56
commit 9a83c66561
No known key found for this signature in database
18 changed files with 451 additions and 411 deletions

View File

@ -20,10 +20,17 @@ from django.db import (
router, router,
transaction, transaction,
) )
from django.db.models import AutoField, DateField, DateTimeField, Field, sql from django.db.models import (
AutoField,
BooleanField,
DateField,
DateTimeField,
Field,
sql,
)
from django.db.models.constants import LOOKUP_SEP, OnConflict from django.db.models.constants import LOOKUP_SEP, OnConflict
from django.db.models.deletion import Collector from django.db.models.deletion import Collector
from django.db.models.expressions import Case, F, Value, When from django.db.models.expressions import Case, Col, F, OrderBy, RawSQL, Value, When
from django.db.models.functions import Cast, Trunc from django.db.models.functions import Cast, Trunc
from django.db.models.query_utils import FilteredRelation, 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
@ -203,9 +210,7 @@ class ValuesIterable(BaseIterable):
if query.selected: if query.selected:
names = list(query.selected) names = list(query.selected)
else: else:
# extra(select=...) cols are always at the start of the row.
names = [ names = [
*query.extra_select,
*query.values_select, *query.values_select,
*query.annotation_select, *query.annotation_select,
] ]
@ -246,7 +251,6 @@ class NamedValuesListIterable(ValuesListIterable):
else: else:
query = queryset.query query = queryset.query
names = [ names = [
*query.extra_select,
*query.values_select, *query.values_select,
*query.annotation_select, *query.annotation_select,
] ]
@ -1711,7 +1715,68 @@ class QuerySet(AltersData):
if self.query.is_sliced: if self.query.is_sliced:
raise TypeError("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) if tables:
clone.query.add_extra_tables(tables)
if select and (not clone.query.values_select or clone.query.values_select_all):
warnings.warn(
"extra(select) usage is deprecated, use annotate() with RawSQL "
"instead.",
category=RemovedInDjango60Warning,
stacklevel=2,
)
# XXX: This is mising the logic to always place extras at the begining
# of the select clause.
if select_params:
param_iter = iter(select_params)
else:
param_iter = iter([])
for name, entry in select.items():
self.query.check_alias(name)
entry = str(entry)
entry_params = []
pos = entry.find("%s")
while pos != -1:
if pos == 0 or entry[pos - 1] != "%":
entry_params.append(next(param_iter))
pos = entry.find("%s", pos + 2)
clone.query.add_annotation(RawSQL(entry, entry_params), name)
if where:
warnings.warn(
"extra(where) usage is deprecated, use filter() with RawSQL instead.",
category=RemovedInDjango60Warning,
stacklevel=2,
)
clone = clone.filter(
RawSQL(
" AND ".join(f"({w})" for w in where), params or (), BooleanField()
)
)
if order_by:
warnings.warn(
"extra(order_by) usage is deprecated, use order_by() instead.",
category=RemovedInDjango60Warning,
stacklevel=2,
)
order_by_exprs = []
for order_sql in order_by:
descending = False
if order_sql.startswith("-"):
order_sql = order_sql[1:]
descending = True
if "." in order_sql:
alias, column = order_sql.split(".", 1)
target = Field(db_column=column)
target.set_attributes_from_name(name=None)
order_expr = Col(alias, target)
else:
try:
clone.query.names_to_path([order_sql], self.model._meta)
except exceptions.FieldError:
order_expr = RawSQL(order_sql, ())
else:
order_expr = F(order_sql)
order_by_exprs.append(OrderBy(order_expr, descending))
clone = clone.order_by(*order_by_exprs)
return clone return clone
def reverse(self): def reverse(self):
@ -1778,7 +1843,7 @@ class QuerySet(AltersData):
""" """
if isinstance(self, EmptyQuerySet): if isinstance(self, EmptyQuerySet):
return True return True
if self.query.extra_order_by or self.query.order_by: if self.query.order_by:
return True return True
elif ( elif (
self.query.default_ordering self.query.default_ordering
@ -1930,7 +1995,6 @@ class QuerySet(AltersData):
"""Check that two QuerySet classes may be merged.""" """Check that two QuerySet classes may be merged."""
if self._fields is not None and ( if self._fields is not None and (
set(self.query.values_select) != set(other.query.values_select) set(self.query.values_select) != set(other.query.values_select)
or set(self.query.extra_select) != set(other.query.extra_select)
or set(self.query.annotation_select) != set(other.query.annotation_select) or set(self.query.annotation_select) != set(other.query.annotation_select)
): ):
raise TypeError( raise TypeError(

View File

@ -258,20 +258,11 @@ class SQLCompiler:
if cols: if cols:
klass_info = { klass_info = {
"model": self.query.model, "model": self.query.model,
"select_fields": list( "select_fields": list(range(0, len(cols))),
range(
len(self.query.extra_select),
len(self.query.extra_select) + len(cols),
)
),
} }
selected = [] selected = []
if self.query.selected is None: if self.query.selected is None:
selected = [ selected = [
*(
(alias, RawSQL(*args))
for alias, args in self.query.extra_select.items()
),
*((None, col) for col in cols), *((None, col) for col in cols),
*self.query.annotation_select.items(), *self.query.annotation_select.items(),
] ]
@ -329,9 +320,7 @@ class SQLCompiler:
return ret, klass_info, annotations return ret, klass_info, annotations
def _order_by_pairs(self): def _order_by_pairs(self):
if self.query.extra_order_by: if not self.query.default_ordering:
ordering = self.query.extra_order_by
elif not self.query.default_ordering:
ordering = self.query.order_by ordering = self.query.order_by
elif self.query.order_by: elif self.query.order_by:
ordering = self.query.order_by ordering = self.query.order_by
@ -428,35 +417,6 @@ class SQLCompiler:
yield OrderBy(expr, descending=descending), False yield OrderBy(expr, descending=descending), False
continue continue
if "." in field:
# This came in through an extra(order_by=...) addition. Pass it
# on verbatim.
table, col = col.split(".", 1)
yield (
OrderBy(
RawSQL(
"%s.%s" % (self.quote_name_unless_alias(table), col), []
),
descending=descending,
),
False,
)
continue
if self.query.extra and col in self.query.extra:
if col in self.query.extra_select:
yield (
OrderBy(
Ref(col, RawSQL(*self.query.extra[col])),
descending=descending,
),
True,
)
else:
yield (
OrderBy(RawSQL(*self.query.extra[col]), descending=descending),
False,
)
else: else:
if self.query.combinator and self.select: if self.query.combinator and self.select:
# Don't use the first model's field because other # Don't use the first model's field because other
@ -550,13 +510,8 @@ class SQLCompiler:
""" """
if name in self.quote_cache: if name in self.quote_cache:
return self.quote_cache[name] return self.quote_cache[name]
if ( if (name in self.query.alias_map and name not in self.query.table_map) or (
(name in self.query.alias_map and name not in self.query.table_map) self.query.external_aliases.get(name) and name not in self.query.table_map
or name in self.query.extra_select
or (
self.query.external_aliases.get(name)
and name not in self.query.table_map
)
): ):
self.quote_cache[name] = name self.quote_cache[name] = name
return name return name
@ -2044,7 +1999,6 @@ class SQLUpdateCompiler(SQLCompiler):
query = self.query.chain(klass=Query) query = self.query.chain(klass=Query)
query.select_related = False query.select_related = False
query.clear_ordering(force=True) query.clear_ordering(force=True)
query.extra = {}
query.select = [] query.select = []
meta = query.get_meta() meta = query.get_meta()
fields = [meta.pk.name] fields = [meta.pk.name]

View File

@ -27,7 +27,6 @@ from django.db.models.expressions import (
Exists, Exists,
F, F,
OuterRef, OuterRef,
RawSQL,
Ref, Ref,
ResolvedOuterRef, ResolvedOuterRef,
Value, Value,
@ -41,7 +40,7 @@ from django.db.models.query_utils import (
) )
from django.db.models.sql.constants import INNER, LOUTER, ORDER_DIR, SINGLE from django.db.models.sql.constants import INNER, LOUTER, ORDER_DIR, SINGLE
from django.db.models.sql.datastructures import BaseTable, Empty, Join, MultiJoin from django.db.models.sql.datastructures import BaseTable, Empty, Join, MultiJoin
from django.db.models.sql.where import AND, OR, ExtraWhere, NothingNode, WhereNode from django.db.models.sql.where import AND, OR, NothingNode, WhereNode
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.regex_helper import _lazy_re_compile from django.utils.regex_helper import _lazy_re_compile
from django.utils.tree import Node from django.utils.tree import Node
@ -153,7 +152,6 @@ class RawQuery:
# Mirror some properties of a normal query so that # Mirror some properties of a normal query so that
# the compiler can be used to process results. # the compiler can be used to process results.
self.low_mark, self.high_mark = 0, None # Used for offset/limit self.low_mark, self.high_mark = 0, None # Used for offset/limit
self.extra_select = {}
self.annotation_select = {} self.annotation_select = {}
def chain(self, using): def chain(self, using):
@ -263,8 +261,9 @@ class Query(BaseExpression):
# Arbitrary limit for select_related to prevents infinite recursion. # Arbitrary limit for select_related to prevents infinite recursion.
max_depth = 5 max_depth = 5
# Holds the selects defined by a call to values() or values_list() # Holds the selects defined by a call to values() or values_list()
# excluding annotation_select and extra_select. # excluding annotation_select.
values_select = () values_select = ()
values_select_all = None
selected = None selected = None
# SQL annotation-related attributes. # SQL annotation-related attributes.
@ -276,13 +275,9 @@ class Query(BaseExpression):
combinator_all = False combinator_all = False
combined_queries = () combined_queries = ()
# These are for extensions. The contents are more or less appended verbatim # This is for extensions. The contents are more or less appended verbatim
# to the appropriate clause. # to the appropriate clause.
extra_select_mask = None
_extra_select_cache = None
extra_tables = () extra_tables = ()
extra_order_by = ()
# A tuple that is a set of model field names and either True, if these are # A tuple that is a set of model field names and either True, if these are
# the fields to defer, or False if these are the only fields to load. # the fields to defer, or False if these are the only fields to load.
@ -312,9 +307,6 @@ class Query(BaseExpression):
self.where = WhereNode() self.where = WhereNode()
# Maps alias -> Annotation Expression. # Maps alias -> Annotation Expression.
self.annotations = {} self.annotations = {}
# These are for extensions. The contents are more or less appended
# verbatim to the appropriate clause.
self.extra = {} # Maps col_alias -> (col_sql, params).
self._filtered_relations = {} self._filtered_relations = {}
@ -401,11 +393,6 @@ class Query(BaseExpression):
# It will get re-populated in the cloned queryset the next time it's # It will get re-populated in the cloned queryset the next time it's
# used. # used.
obj._annotation_select_cache = None obj._annotation_select_cache = None
obj.extra = self.extra.copy()
if self.extra_select_mask is not None:
obj.extra_select_mask = self.extra_select_mask.copy()
if self._extra_select_cache is not None:
obj._extra_select_cache = self._extra_select_cache.copy()
if self.select_related is not False: if self.select_related is not False:
# Use deepcopy because select_related stores fields in nested # Use deepcopy because select_related stores fields in nested
# dicts. # dicts.
@ -596,7 +583,6 @@ class Query(BaseExpression):
self.select = () self.select = ()
self.selected = None self.selected = None
self.default_cols = False self.default_cols = False
self.extra = {}
if self.annotations: if self.annotations:
# Inline reference to existing annotations and mask them as # Inline reference to existing annotations and mask them as
# they are unnecessary given only the summarized aggregations # they are unnecessary given only the summarized aggregations
@ -767,35 +753,17 @@ class Query(BaseExpression):
w.relabel_aliases(change_map) w.relabel_aliases(change_map)
self.where.add(w, connector) self.where.add(w, connector)
# Selection columns and extra extensions are those provided by 'rhs'. # Selection columns are those provided by 'rhs'.
if rhs.select: if rhs.select:
self.set_select([col.relabeled_clone(change_map) for col in rhs.select]) self.set_select([col.relabeled_clone(change_map) for col in rhs.select])
else: else:
self.select = () self.select = ()
if connector == OR:
# It would be nice to be able to handle this, but the queries don't
# really make sense (or return consistent value sets). Not worth
# the extra complexity when you can write a real query instead.
if self.extra and rhs.extra:
raise ValueError(
"When merging querysets using 'or', you cannot have "
"extra(select=...) on both sides."
)
self.extra.update(rhs.extra)
extra_select_mask = set()
if self.extra_select_mask is not None:
extra_select_mask.update(self.extra_select_mask)
if rhs.extra_select_mask is not None:
extra_select_mask.update(rhs.extra_select_mask)
if extra_select_mask:
self.set_extra_mask(extra_select_mask)
self.extra_tables += rhs.extra_tables self.extra_tables += rhs.extra_tables
# Ordering uses the 'rhs' ordering, unless it has none, in which case # Ordering uses the 'rhs' ordering, unless it has none, in which case
# the current ordering is used. # the current ordering is used.
self.order_by = rhs.order_by or self.order_by self.order_by = rhs.order_by or self.order_by
self.extra_order_by = rhs.extra_order_by or self.extra_order_by
def _get_defer_select_mask(self, opts, mask, select_mask=None): def _get_defer_select_mask(self, opts, mask, select_mask=None):
if select_mask is None: if select_mask is None:
@ -2162,19 +2130,19 @@ class Query(BaseExpression):
self.select = () self.select = ()
self.default_cols = False self.default_cols = False
self.select_related = False self.select_related = False
self.set_extra_mask(())
self.set_annotation_mask(()) self.set_annotation_mask(())
self.selected = None self.selected = None
def clear_select_fields(self): def clear_select_fields(self):
""" """
Clear the list of fields to select (but not extra_select columns). Clear the list of fields to select.
Some queryset types completely replace any existing list of select Some queryset types completely replace any existing list of select
columns. columns.
""" """
self.select = () self.select = ()
self.values_select = () self.values_select = ()
self.selected = None self.selected = None
self.values_select_all = None
def add_select_col(self, col, name): def add_select_col(self, col, name):
self.select += (col,) self.select += (col,)
@ -2228,7 +2196,6 @@ class Query(BaseExpression):
names = sorted( names = sorted(
[ [
*get_field_names_from_opts(opts), *get_field_names_from_opts(opts),
*self.extra,
*self.annotation_select, *self.annotation_select,
*self._filtered_relations, *self._filtered_relations,
] ]
@ -2255,8 +2222,6 @@ class Query(BaseExpression):
item = item.removeprefix("-") item = item.removeprefix("-")
if item in self.annotations: if item in self.annotations:
continue continue
if self.extra and item in self.extra:
continue
# names_to_path() validates the lookup. A descriptive # names_to_path() validates the lookup. A descriptive
# FieldError will be raise if it's not. # FieldError will be raise if it's not.
self.names_to_path(item.split(LOOKUP_SEP), self.model._meta) self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
@ -2286,7 +2251,6 @@ class Query(BaseExpression):
): ):
return return
self.order_by = () self.order_by = ()
self.extra_order_by = ()
if clear_default: if clear_default:
self.default_ordering = False self.default_ordering = False
@ -2339,38 +2303,8 @@ class Query(BaseExpression):
d = d.setdefault(part, {}) d = d.setdefault(part, {})
self.select_related = field_dict self.select_related = field_dict
def add_extra(self, select, select_params, where, params, tables, order_by): def add_extra_tables(self, tables):
"""
Add data to the various extra_* attributes for user-created additions
to the query.
"""
if select:
# We need to pair any placeholder markers in the 'select'
# dictionary with their parameters in 'select_params' so that
# subsequent updates to the select dictionary also adjust the
# parameters appropriately.
select_pairs = {}
if select_params:
param_iter = iter(select_params)
else:
param_iter = iter([])
for name, entry in select.items():
self.check_alias(name)
entry = str(entry)
entry_params = []
pos = entry.find("%s")
while pos != -1:
if pos == 0 or entry[pos - 1] != "%":
entry_params.append(next(param_iter))
pos = entry.find("%s", pos + 2)
select_pairs[name] = (entry, entry_params)
self.extra.update(select_pairs)
if where or params:
self.where.add(ExtraWhere(where, params), AND)
if tables:
self.extra_tables += tuple(tables) self.extra_tables += tuple(tables)
if order_by:
self.extra_order_by = order_by
def clear_deferred_loading(self): def clear_deferred_loading(self):
"""Remove any fields from the deferred loading set.""" """Remove any fields from the deferred loading set."""
@ -2448,17 +2382,6 @@ class Query(BaseExpression):
if self.annotation_select_mask is not None: if self.annotation_select_mask is not None:
self.set_annotation_mask(self.annotation_select_mask.union(names)) self.set_annotation_mask(self.annotation_select_mask.union(names))
def set_extra_mask(self, names):
"""
Set the mask of extra select items that will be returned by SELECT.
Don't remove them from the Query since they might be used later.
"""
if names is None:
self.extra_select_mask = None
else:
self.extra_select_mask = set(names)
self._extra_select_cache = None
@property @property
def has_select_fields(self): def has_select_fields(self):
return self.selected is not None return self.selected is not None
@ -2473,20 +2396,16 @@ class Query(BaseExpression):
for field in fields: for field in fields:
self.check_alias(field) self.check_alias(field)
field_names = [] field_names = []
extra_names = []
annotation_names = [] annotation_names = []
if not self.extra and not self.annotations: if not self.annotations:
# Shortcut - if there are no extra or annotations, then # Shortcut - if there are no annotations, then
# the values() clause must be just field names. # the values() clause must be just field names.
field_names = list(fields) field_names = list(fields)
selected = dict(zip(fields, range(len(fields)))) selected = dict(zip(fields, range(len(fields))))
else: else:
self.default_cols = False self.default_cols = False
for f in fields: for f in fields:
if extra := self.extra_select.get(f): if f in self.annotation_select:
extra_names.append(f)
selected[f] = RawSQL(*extra)
elif f in self.annotation_select:
annotation_names.append(f) annotation_names.append(f)
selected[f] = f selected[f] = f
elif f in self.annotations: elif f in self.annotations:
@ -2502,7 +2421,6 @@ class Query(BaseExpression):
self.names_to_path(f.split(LOOKUP_SEP), self.model._meta) self.names_to_path(f.split(LOOKUP_SEP), self.model._meta)
selected[f] = len(field_names) selected[f] = len(field_names)
field_names.append(f) field_names.append(f)
self.set_extra_mask(extra_names)
self.set_annotation_mask(annotation_names) self.set_annotation_mask(annotation_names)
else: else:
field_names = [f.attname for f in self.model._meta.concrete_fields] field_names = [f.attname for f in self.model._meta.concrete_fields]
@ -2528,6 +2446,7 @@ class Query(BaseExpression):
self.group_by = tuple(group_by) self.group_by = tuple(group_by)
self.values_select = tuple(field_names) self.values_select = tuple(field_names)
self.values_select_all = not fields
self.add_fields(field_names, True) self.add_fields(field_names, True)
self.selected = selected if fields else None self.selected = selected if fields else None
@ -2551,20 +2470,6 @@ class Query(BaseExpression):
else: else:
return self.annotations return self.annotations
@property
def extra_select(self):
if self._extra_select_cache is not None:
return self._extra_select_cache
if not self.extra:
return {}
elif self.extra_select_mask is not None:
self._extra_select_cache = {
k: v for k, v in self.extra.items() if k in self.extra_select_mask
}
return self._extra_select_cache
else:
return self.extra
def trim_start(self, names_with_path): def trim_start(self, names_with_path):
""" """
Trim joins from the start of the join path. The candidates for trim Trim joins from the start of the join path. The candidates for trim

View File

@ -331,20 +331,6 @@ class NothingNode:
raise EmptyResultSet raise EmptyResultSet
class ExtraWhere:
# The contents are a black box - assume no aggregates or windows are used.
contains_aggregate = False
contains_over_clause = False
def __init__(self, sqls, params):
self.sqls = sqls
self.params = params
def as_sql(self, compiler=None, connection=None):
sqls = ["(%s)" % sql for sql in self.sqls]
return " AND ".join(sqls), list(self.params or ())
class SubqueryConstraint: class SubqueryConstraint:
# Even if aggregates or windows would be used in a subquery, # Even if aggregates or windows would be used in a subquery,
# the outer query isn't interested about those. # the outer query isn't interested about those.

View File

@ -44,10 +44,10 @@ from django.db.models.functions import (
TruncDate, TruncDate,
TruncHour, TruncHour,
) )
from django.test import TestCase from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
from django.test.testcases import skipUnlessDBFeature
from django.test.utils import Approximate, CaptureQueriesContext from django.test.utils import Approximate, CaptureQueriesContext
from django.utils import timezone from django.utils import timezone
from django.utils.deprecation import RemovedInDjango60Warning
from .models import Author, Book, Publisher, Store from .models import Author, Book, Publisher, Store
@ -2139,6 +2139,8 @@ class AggregateTestCase(TestCase):
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
Author.objects.aggregate(**{crafted_alias: Avg("age")}) Author.objects.aggregate(**{crafted_alias: Avg("age")})
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_exists_extra_where_with_aggregate(self): def test_exists_extra_where_with_aggregate(self):
qs = Book.objects.annotate( qs = Book.objects.annotate(
count=Count("id"), count=Count("id"),

View File

@ -25,8 +25,9 @@ from django.db.models import (
When, When,
) )
from django.db.models.functions import Cast, Concat from django.db.models.functions import Cast, Concat
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
from django.test.utils import Approximate from django.test.utils import Approximate
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Alfa, Alfa,
@ -238,6 +239,8 @@ class AggregationTests(TestCase):
qs2 = books.filter(id__in=list(qs)) qs2 = books.filter(id__in=list(qs))
self.assertEqual(list(qs1), list(qs2)) self.assertEqual(list(qs1), list(qs2))
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
@skipUnlessDBFeature("supports_subqueries_in_group_by") @skipUnlessDBFeature("supports_subqueries_in_group_by")
def test_annotate_with_extra(self): def test_annotate_with_extra(self):
""" """
@ -287,14 +290,18 @@ class AggregationTests(TestCase):
{"pages__sum": 3703, "pages__avg": Approximate(617.166, places=2)}, {"pages__sum": 3703, "pages__avg": Approximate(617.166, places=2)},
) )
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
# Aggregate overrides extra selected column # Aggregate overrides extra selected column
self.assertEqual( self.assertEqual(
Book.objects.extra(select={"price_per_page": "price / pages"}).aggregate( Book.objects.extra(
Sum("pages") select={"price_per_page": "price / pages"}
), ).aggregate(Sum("pages")),
{"pages__sum": 3703}, {"pages__sum": 3703},
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_annotation(self): def test_annotation(self):
# Annotations get combined with extra select clauses # Annotations get combined with extra select clauses
obj = ( obj = (
@ -879,6 +886,8 @@ class AggregationTests(TestCase):
# Regression for #10132 - If the values() clause only mentioned extra # Regression for #10132 - If the values() clause only mentioned extra
# (select=) columns, those columns are used for grouping # (select=) columns, those columns are used for grouping
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
qs = ( qs = (
Book.objects.extra(select={"pub": "publisher_id"}) Book.objects.extra(select={"pub": "publisher_id"})
.values("pub") .values("pub")
@ -1058,6 +1067,8 @@ class AggregationTests(TestCase):
# Regression for #10290 - extra selects with parameters can be used for # Regression for #10290 - extra selects with parameters can be used for
# grouping. # grouping.
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
qs = ( qs = (
Book.objects.annotate(mean_auth_age=Avg("authors__age")) Book.objects.annotate(mean_auth_age=Avg("authors__age"))
.extra(select={"sheets": "(pages + %s) / %s"}, select_params=[1, 2]) .extra(select={"sheets": "(pages + %s) / %s"}, select_params=[1, 2])

View File

@ -37,8 +37,9 @@ from django.db.models.functions import (
Trim, Trim,
) )
from django.db.models.sql.query import get_field_names_from_opts from django.db.models.sql.query import get_field_names_from_opts
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
from django.test.utils import register_lookup from django.test.utils import register_lookup
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Author, Author,
@ -722,10 +723,9 @@ class NonAggregateAnnotationTestCase(TestCase):
Columns are aligned in the correct order for resolve_columns. This test Columns are aligned in the correct order for resolve_columns. This test
will fail on MySQL if column ordering is out. Column fields should be will fail on MySQL if column ordering is out. Column fields should be
aligned as: aligned as:
1. extra_select 1. model_fields
2. model_fields 2. annotation_fields
3. annotation_fields 3. model_related_fields
4. model_related_fields
""" """
store = Store.objects.first() store = Store.objects.first()
Employee.objects.create( Employee.objects.create(
@ -747,6 +747,8 @@ class NonAggregateAnnotationTestCase(TestCase):
salary=Decimal(40000.00), salary=Decimal(40000.00),
) )
# random_value annotation can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
qs = ( qs = (
Employee.objects.extra(select={"random_value": "42"}) Employee.objects.extra(select={"random_value": "42"})
.select_related("store") .select_related("store")
@ -797,6 +799,8 @@ class NonAggregateAnnotationTestCase(TestCase):
salary=Decimal(40000.00), salary=Decimal(40000.00),
) )
# random_value annotation can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
qs = ( qs = (
Employee.objects.extra(select={"random_value": "42"}) Employee.objects.extra(select={"random_value": "42"})
.select_related("store") .select_related("store")

View File

@ -455,6 +455,8 @@ class ModelTest(TestCase):
s = {a10, a11, a12} s = {a10, a11, a12}
self.assertIn(Article.objects.get(headline="Article 11"), s) self.assertIn(Article.objects.get(headline="Article 11"), s)
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_method_select_argument_with_dashes_and_values(self): def test_extra_method_select_argument_with_dashes_and_values(self):
# The 'select' argument to extra() supports names with dashes in # The 'select' argument to extra() supports names with dashes in
# them, as long as you use values(). # them, as long as you use values().
@ -483,6 +485,8 @@ class ModelTest(TestCase):
], ],
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_method_select_argument_with_dashes(self): def test_extra_method_select_argument_with_dashes(self):
# If you use 'select' with extra() and names containing dashes on a # If you use 'select' with extra() and names containing dashes on a
# query that's *not* a values() query, those extra 'select' values # query that's *not* a values() query, those extra 'select' values

View File

@ -1,5 +1,6 @@
from django.core.exceptions import FieldDoesNotExist, FieldError from django.core.exceptions import FieldDoesNotExist, FieldError
from django.test import SimpleTestCase, TestCase from django.test import SimpleTestCase, TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
BigChild, BigChild,
@ -82,6 +83,8 @@ class DeferTests(AssertionMixin, TestCase):
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
Primary.objects.only(None) Primary.objects.only(None)
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_defer_extra(self): def test_defer_extra(self):
qs = Primary.objects.all() qs = Primary.objects.all()
self.assert_delayed(qs.defer("name").extra(select={"a": 1})[0], 1) self.assert_delayed(qs.defer("name").extra(select={"a": 1})[0], 1)

View File

@ -6,8 +6,10 @@ from django.test import (
SimpleTestCase, SimpleTestCase,
TestCase, TestCase,
TransactionTestCase, TransactionTestCase,
ignore_warnings,
skipUnlessDBFeature, skipUnlessDBFeature,
) )
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Award, Award,
@ -309,6 +311,8 @@ class Ticket19102Tests(TestCase):
self.assertFalse(Login.objects.filter(pk=self.l1.pk).exists()) self.assertFalse(Login.objects.filter(pk=self.l1.pk).exists())
self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists()) self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists())
# Entire block can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
@skipUnlessDBFeature("update_can_self_select") @skipUnlessDBFeature("update_can_self_select")
def test_ticket_19102_extra(self): def test_ticket_19102_extra(self):
with self.assertNumQueries(1): with self.assertNumQueries(1):

View File

@ -1,11 +1,13 @@
import datetime import datetime
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase from django.test import SimpleTestCase, TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango60Warning
from .models import Order, RevisionableModel, TestObject from .models import Order, RevisionableModel, TestObject
@ignore_warnings(category=RemovedInDjango60Warning)
class ExtraRegressTests(TestCase): class ExtraRegressTests(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -284,7 +286,7 @@ class ExtraRegressTests(TestCase):
select={"foo": "first", "bar": "second", "whiz": "third"} select={"foo": "first", "bar": "second", "whiz": "third"}
).values_list() ).values_list()
), ),
[("first", "second", "third", obj.pk, "first", "second", "third")], [(obj.pk, "first", "second", "third", "first", "second", "third")],
) )
# Extra columns after an empty values_list() are still included # Extra columns after an empty values_list() are still included
@ -294,7 +296,7 @@ class ExtraRegressTests(TestCase):
select={"foo": "first", "bar": "second", "whiz": "third"} select={"foo": "first", "bar": "second", "whiz": "third"}
) )
), ),
[("first", "second", "third", obj.pk, "first", "second", "third")], [(obj.pk, "first", "second", "third", "first", "second", "third")],
) )
# Extra columns ignored completely if not mentioned in values_list() # Extra columns ignored completely if not mentioned in values_list()
@ -468,3 +470,23 @@ class ExtraRegressTests(TestCase):
self.assertSequenceEqual( self.assertSequenceEqual(
qs.order_by("-second_extra").values_list("first"), [("a",), ("a",)] qs.order_by("-second_extra").values_list("first"), [("a",), ("a",)]
) )
class ExtraDeprecationTests(SimpleTestCase):
def test_extra_select_deprecation(self):
msg = "extra(select) usage is deprecated, use annotate() with RawSQL instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
TestObject.objects.all().extra(select={"select": "%s"}, select_params=(1,))
self.assertEqual(ctx.filename, __file__)
def test_extra_where_deprecation(self):
msg = "extra(where) usage is deprecated, use filter() with RawSQL instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
TestObject.objects.all().extra(where={"where": "foo = %s"}, params=(1,))
self.assertEqual(ctx.filename, __file__)
def test_extra_order_by_deprecation(self):
msg = "extra(order_by) usage is deprecated, use order_by() instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
TestObject.objects.all().extra(order_by={"order_by": "random()"})
self.assertEqual(ctx.filename, __file__)

View File

@ -18,8 +18,9 @@ from django.db.models import (
) )
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.db.models.lookups import Exact, IStartsWith from django.db.models.lookups import Exact, IStartsWith
from django.test import TestCase from django.test import TestCase, ignore_warnings
from django.test.testcases import skipUnlessDBFeature from django.test.testcases import skipUnlessDBFeature
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Author, Author,
@ -344,6 +345,8 @@ class FilteredRelationTests(TestCase):
], ],
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra(self): def test_extra(self):
self.assertSequenceEqual( self.assertSequenceEqual(
Author.objects.annotate( Author.objects.annotate(

View File

@ -28,8 +28,9 @@ from django.db.models.lookups import (
LessThan, LessThan,
LessThanOrEqual, LessThanOrEqual,
) )
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
from django.test.utils import isolate_apps, register_lookup from django.test.utils import isolate_apps, register_lookup
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Article, Article,
@ -379,6 +380,8 @@ class LookupTests(TestCase):
{"headline": "Article 1", "id": self.a1.id}, {"headline": "Article 1", "id": self.a1.id},
], ],
) )
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
# The values() method works with "extra" fields specified in extra(select). # The values() method works with "extra" fields specified in extra(select).
self.assertSequenceEqual( self.assertSequenceEqual(
Article.objects.extra(select={"id_plus_one": "id + 1"}).values( Article.objects.extra(select={"id_plus_one": "id + 1"}).values(
@ -500,6 +503,8 @@ class LookupTests(TestCase):
}, },
], ],
) )
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
# However, an exception FieldDoesNotExist will be thrown if you specify # However, an exception FieldDoesNotExist will be thrown if you specify
# a nonexistent field name in values() (a field that is neither in the # a nonexistent field name in values() (a field that is neither in the
# model nor in extra(select)). # model nor in extra(select)).
@ -566,6 +571,8 @@ class LookupTests(TestCase):
self.a7.id, self.a7.id,
], ],
) )
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
self.assertSequenceEqual( self.assertSequenceEqual(
Article.objects.extra(select={"id_plus_one": "id+1"}) Article.objects.extra(select={"id_plus_one": "id+1"})
.order_by("id") .order_by("id")

View File

@ -3,7 +3,7 @@ from copy import deepcopy
from django.core.exceptions import FieldError, MultipleObjectsReturned from django.core.exceptions import FieldError, MultipleObjectsReturned
from django.db import IntegrityError, models, transaction from django.db import IntegrityError, models, transaction
from django.test import TestCase from django.test import TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango60Warning from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.translation import gettext_lazy from django.utils.translation import gettext_lazy
@ -282,6 +282,8 @@ class ManyToOneTests(TestCase):
queryset.query.get_compiler(queryset.db).as_sql()[0].count("INNER JOIN"), 1 queryset.query.get_compiler(queryset.db).as_sql()[0].count("INNER JOIN"), 1
) )
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
# The automatically joined table has a predictable name. # The automatically joined table has a predictable name.
self.assertSequenceEqual( self.assertSequenceEqual(
Article.objects.filter(reporter__first_name__exact="John").extra( Article.objects.filter(reporter__first_name__exact="John").extra(
@ -570,10 +572,14 @@ class ManyToOneTests(TestCase):
reporter_fields = ", ".join(sorted(f.name for f in Reporter._meta.get_fields())) reporter_fields = ", ".join(sorted(f.name for f in Reporter._meta.get_fields()))
with self.assertRaisesMessage(FieldError, expected_message % reporter_fields): with self.assertRaisesMessage(FieldError, expected_message % reporter_fields):
Article.objects.values_list("reporter__notafield") Article.objects.values_list("reporter__notafield")
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
article_fields = ", ".join( article_fields = ", ".join(
["EXTRA"] + sorted(f.name for f in Article._meta.get_fields()) ["EXTRA"] + sorted(f.name for f in Article._meta.get_fields())
) )
with self.assertRaisesMessage(FieldError, expected_message % article_fields): with self.assertRaisesMessage(
FieldError, expected_message % article_fields
):
Article.objects.extra(select={"EXTRA": "EXTRA_SELECT"}).values_list( Article.objects.extra(select={"EXTRA": "EXTRA_SELECT"}).values_list(
"notafield" "notafield"
) )

View File

@ -14,7 +14,8 @@ from django.db.models import (
Value, Value,
) )
from django.db.models.functions import Length, Upper from django.db.models.functions import Length, Upper
from django.test import TestCase from django.test import TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Article, Article,
@ -335,6 +336,8 @@ class OrderingTests(TestCase):
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
qs.last() qs.last()
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_ordering(self): def test_extra_ordering(self):
""" """
Ordering can be based on fields included from an 'extra' clause Ordering can be based on fields included from an 'extra' clause
@ -352,6 +355,8 @@ class OrderingTests(TestCase):
attrgetter("headline"), attrgetter("headline"),
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_ordering_quoting(self): def test_extra_ordering_quoting(self):
""" """
If the extra clause uses an SQL keyword for a name, it will be If the extra clause uses an SQL keyword for a name, it will be
@ -370,6 +375,8 @@ class OrderingTests(TestCase):
attrgetter("headline"), attrgetter("headline"),
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_ordering_with_table_name(self): def test_extra_ordering_with_table_name(self):
self.assertQuerySetEqual( self.assertQuerySetEqual(
Article.objects.extra(order_by=["ordering_article.headline"]), Article.objects.extra(order_by=["ordering_article.headline"]),

View File

@ -12,7 +12,8 @@ from django.db.models import (
) )
from django.db.models.functions import Mod from django.db.models.functions import Mod
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext from django.test.utils import CaptureQueriesContext, ignore_warnings
from django.utils.deprecation import RemovedInDjango60Warning
from .models import Author, Celebrity, ExtraInfo, Number, ReservedName from .models import Author, Celebrity, ExtraInfo, Number, ReservedName
@ -280,6 +281,8 @@ class QuerySetSetOperationTests(TestCase):
) )
self.assertCountEqual(qs1.union(qs2), [(1, 0), (0, 2)]) self.assertCountEqual(qs1.union(qs2), [(1, 0), (0, 2)])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_union_with_extra_and_values_list(self): def test_union_with_extra_and_values_list(self):
qs1 = ( qs1 = (
Number.objects.filter(num=1) Number.objects.filter(num=1)
@ -408,6 +411,8 @@ class QuerySetSetOperationTests(TestCase):
[reserved_name.pk], [reserved_name.pk],
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_union_multiple_models_with_values_list_and_order_by_extra_select(self): def test_union_multiple_models_with_values_list_and_order_by_extra_select(self):
reserved_name = ReservedName.objects.create(name="rn1", order=0) reserved_name = ReservedName.objects.create(name="rn1", order=0)
qs1 = Celebrity.objects.extra(select={"extra_name": "name"}) qs1 = Celebrity.objects.extra(select={"extra_name": "name"})

View File

@ -12,7 +12,8 @@ from django.db.models.functions import ExtractYear, Length, LTrim
from django.db.models.sql.constants import LOUTER from django.db.models.sql.constants import LOUTER
from django.db.models.sql.where import AND, OR, NothingNode, WhereNode from django.db.models.sql.where import AND, OR, NothingNode, WhereNode
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext, register_lookup from django.test.utils import CaptureQueriesContext, ignore_warnings, register_lookup
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
FK1, FK1,
@ -325,6 +326,8 @@ class Queries1Tests(TestCase):
.count(), .count(),
4, 4,
) )
# Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
self.assertEqual( self.assertEqual(
( (
Item.objects.exclude(name="two") Item.objects.exclude(name="two")
@ -594,6 +597,8 @@ class Queries1Tests(TestCase):
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
Author.objects.all() | Tag.objects.all() Author.objects.all() | Tag.objects.all()
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_ticket3141(self): def test_ticket3141(self):
self.assertEqual(Author.objects.extra(select={"foo": "1"}).count(), 4) self.assertEqual(Author.objects.extra(select={"foo": "1"}).count(), 4)
self.assertEqual( self.assertEqual(
@ -750,6 +755,8 @@ class Queries1Tests(TestCase):
datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 19, 0, 0),
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_tickets_7087_12242(self): def test_tickets_7087_12242(self):
# Dates with extra select columns # Dates with extra select columns
self.assertSequenceEqual( self.assertSequenceEqual(
@ -893,9 +900,13 @@ class Queries1Tests(TestCase):
self.assertSequenceEqual(q.annotate(Count("food")), []) self.assertSequenceEqual(q.annotate(Count("food")), [])
self.assertSequenceEqual(q.order_by("meal", "food"), []) self.assertSequenceEqual(q.order_by("meal", "food"), [])
self.assertSequenceEqual(q.distinct(), []) self.assertSequenceEqual(q.distinct(), [])
# Block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
self.assertSequenceEqual(q.extra(select={"foo": "1"}), []) self.assertSequenceEqual(q.extra(select={"foo": "1"}), [])
self.assertSequenceEqual(q.reverse(), []) self.assertSequenceEqual(q.reverse(), [])
q.query.low_mark = 1 q.query.low_mark = 1
# Block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
msg = "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): with self.assertRaisesMessage(TypeError, msg):
q.extra(select={"foo": "1"}) q.extra(select={"foo": "1"})
@ -1866,8 +1877,11 @@ class Queries5Tests(TestCase):
[self.rank1, self.rank2, self.rank3], [self.rank1, self.rank2, self.rank3],
) )
# Block should be changed to use extra(tables).order_by() when
# deprecation period ends.
# Ordering of extra() pieces is possible, too and you can mix extra # Ordering of extra() pieces is possible, too and you can mix extra
# fields and model fields in the ordering. # fields and model fields in the ordering.
with ignore_warnings(category=RemovedInDjango60Warning):
self.assertSequenceEqual( self.assertSequenceEqual(
Ranking.objects.extra( Ranking.objects.extra(
tables=["django_site"], order_by=["-django_site.id", "rank"] tables=["django_site"], order_by=["-django_site.id", "rank"]
@ -1875,7 +1889,11 @@ class Queries5Tests(TestCase):
[self.rank1, self.rank2, self.rank3], [self.rank1, self.rank2, self.rank3],
) )
sql = "case when %s > 2 then 1 else 0 end" % connection.ops.quote_name("rank") # Entire block can be removed once deprecation period ends.
with ignore_warnings(category=RemovedInDjango60Warning):
sql = "case when %s > 2 then 1 else 0 end" % connection.ops.quote_name(
"rank"
)
qs = Ranking.objects.extra(select={"good": sql}) qs = Ranking.objects.extra(select={"good": sql})
self.assertEqual( self.assertEqual(
[o.good for o in qs.extra(order_by=("-good",))], [True, False, False] [o.good for o in qs.extra(order_by=("-good",))], [True, False, False]
@ -1890,6 +1908,8 @@ class Queries5Tests(TestCase):
dicts = qs.values("id", "rank").order_by("id") dicts = qs.values("id", "rank").order_by("id")
self.assertEqual([d["rank"] for d in dicts], [2, 1, 3]) self.assertEqual([d["rank"] for d in dicts], [2, 1, 3])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_ticket7256(self): def test_ticket7256(self):
# An empty values() call includes all aliases, including those from an # An empty values() call includes all aliases, including those from an
# extra() # extra()
@ -1962,6 +1982,8 @@ class Queries5Tests(TestCase):
[self.n1, self.n2], [self.n1, self.n2],
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_select_literal_percent_s(self): def test_extra_select_literal_percent_s(self):
# Allow %%s to escape select clauses # Allow %%s to escape select clauses
self.assertEqual(Note.objects.extra(select={"foo": "'%%s'"})[0].foo, "%s") self.assertEqual(Note.objects.extra(select={"foo": "'%%s'"})[0].foo, "%s")
@ -1972,6 +1994,8 @@ class Queries5Tests(TestCase):
Note.objects.extra(select={"foo": "'bar %%s'"})[0].foo, "bar %s" Note.objects.extra(select={"foo": "'bar %%s'"})[0].foo, "bar %s"
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_select_alias_sql_injection(self): def test_extra_select_alias_sql_injection(self):
crafted_alias = """injected_name" from "queries_note"; --""" crafted_alias = """injected_name" from "queries_note"; --"""
msg = ( msg = (
@ -2350,6 +2374,8 @@ class QuerysetOrderedTests(unittest.TestCase):
def test_empty_queryset(self): def test_empty_queryset(self):
self.assertIs(Annotation.objects.none().ordered, True) self.assertIs(Annotation.objects.none().ordered, True)
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_order_by_extra(self): def test_order_by_extra(self):
self.assertIs(Annotation.objects.extra(order_by=["id"]).ordered, True) self.assertIs(Annotation.objects.extra(order_by=["id"]).ordered, True)
@ -2682,6 +2708,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values_list("num", flat=True) qs = qs.values_list("num", flat=True)
self.assertSequenceEqual(qs, [72]) self.assertSequenceEqual(qs, [72])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_values(self): def test_extra_values(self):
# testing for ticket 14930 issues # testing for ticket 14930 issues
qs = Number.objects.extra( qs = Number.objects.extra(
@ -2692,6 +2720,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values("num") qs = qs.values("num")
self.assertSequenceEqual(qs, [{"num": 72}]) self.assertSequenceEqual(qs, [{"num": 72}])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_values_order_twice(self): def test_extra_values_order_twice(self):
# testing for ticket 14930 issues # testing for ticket 14930 issues
qs = Number.objects.extra( qs = Number.objects.extra(
@ -2701,6 +2731,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values("num") qs = qs.values("num")
self.assertSequenceEqual(qs, [{"num": 72}]) self.assertSequenceEqual(qs, [{"num": 72}])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_values_order_multiple(self): def test_extra_values_order_multiple(self):
# Postgres doesn't allow constants in order by, so check for that. # Postgres doesn't allow constants in order by, so check for that.
qs = Number.objects.extra( qs = Number.objects.extra(
@ -2714,6 +2746,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values("num") qs = qs.values("num")
self.assertSequenceEqual(qs, [{"num": 72}]) self.assertSequenceEqual(qs, [{"num": 72}])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_values_order_in_extra(self): def test_extra_values_order_in_extra(self):
# testing for ticket 14930 issues # testing for ticket 14930 issues
qs = Number.objects.extra( qs = Number.objects.extra(
@ -2722,6 +2756,8 @@ class ValuesQuerysetTests(TestCase):
) )
qs = qs.values("num") qs = qs.values("num")
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_select_params_values_order_in_extra(self): def test_extra_select_params_values_order_in_extra(self):
# testing for 23259 issue # testing for 23259 issue
qs = Number.objects.extra( qs = Number.objects.extra(
@ -2733,6 +2769,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values("num") qs = qs.values("num")
self.assertSequenceEqual(qs, [{"num": 72}]) self.assertSequenceEqual(qs, [{"num": 72}])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_multiple_select_params_values_order_by(self): def test_extra_multiple_select_params_values_order_by(self):
# testing for 23259 issue # testing for 23259 issue
qs = Number.objects.extra( qs = Number.objects.extra(
@ -2744,6 +2782,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values("num") qs = qs.values("num")
self.assertSequenceEqual(qs, []) self.assertSequenceEqual(qs, [])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_extra_values_list(self): def test_extra_values_list(self):
# testing for ticket 14930 issues # testing for ticket 14930 issues
qs = Number.objects.extra(select={"value_plus_one": "num+1"}) qs = Number.objects.extra(select={"value_plus_one": "num+1"})
@ -2751,6 +2791,8 @@ class ValuesQuerysetTests(TestCase):
qs = qs.values_list("num") qs = qs.values_list("num")
self.assertSequenceEqual(qs, [(72,)]) self.assertSequenceEqual(qs, [(72,)])
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_flat_extra_values_list(self): def test_flat_extra_values_list(self):
# testing for ticket 14930 issues # testing for ticket 14930 issues
qs = Number.objects.extra(select={"value_plus_one": "num+1"}) qs = Number.objects.extra(select={"value_plus_one": "num+1"})
@ -2772,6 +2814,8 @@ class ValuesQuerysetTests(TestCase):
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
Number.objects.values_list("num", flat=True, named=True) Number.objects.values_list("num", flat=True, named=True)
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_named_values_list_bad_field_name(self): def test_named_values_list_bad_field_name(self):
msg = "Type names and field names must be valid identifiers: '1'" msg = "Type names and field names must be valid identifiers: '1'"
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
@ -2779,6 +2823,8 @@ class ValuesQuerysetTests(TestCase):
"1", named=True "1", named=True
).first() ).first()
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_named_values_list_with_fields(self): def test_named_values_list_with_fields(self):
qs = Number.objects.extra(select={"num2": "num+1"}).annotate(Count("id")) qs = Number.objects.extra(select={"num2": "num+1"}).annotate(Count("id"))
values = qs.values_list("num", "num2", named=True).first() values = qs.values_list("num", "num2", named=True).first()
@ -2787,13 +2833,15 @@ class ValuesQuerysetTests(TestCase):
self.assertEqual(values.num, 72) self.assertEqual(values.num, 72)
self.assertEqual(values.num2, 73) self.assertEqual(values.num2, 73)
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_named_values_list_without_fields(self): def test_named_values_list_without_fields(self):
qs = Number.objects.extra(select={"num2": "num+1"}).annotate(Count("id")) qs = Number.objects.extra(select={"num2": "num+1"}).annotate(Count("id"))
values = qs.values_list(named=True).first() values = qs.values_list(named=True).first()
self.assertEqual(type(values).__name__, "Row") self.assertEqual(type(values).__name__, "Row")
self.assertEqual( self.assertEqual(
values._fields, values._fields,
("num2", "id", "num", "other_num", "another_num", "id__count"), ("id", "num", "other_num", "another_num", "num2", "id__count"),
) )
self.assertEqual(values.num, 72) self.assertEqual(values.num, 72)
self.assertEqual(values.num2, 73) self.assertEqual(values.num2, 73)
@ -2980,6 +3028,8 @@ class WeirdQuerysetSlicingTests(TestCase):
self.assertQuerySetEqual(Article.objects.values_list()[n:n], []) self.assertQuerySetEqual(Article.objects.values_list()[n:n], [])
# Entire testcase can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
class EscapingTests(TestCase): class EscapingTests(TestCase):
def test_ticket_7302(self): def test_ticket_7302(self):
# Reserved names are appropriately escaped # Reserved names are appropriately escaped

View File

@ -1,5 +1,6 @@
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.test import SimpleTestCase, TestCase from django.test import SimpleTestCase, TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango60Warning
from .models import ( from .models import (
Bookmark, Bookmark,
@ -119,6 +120,8 @@ class SelectRelatedTests(TestCase):
sorted(orders), ["Agaricales", "Diptera", "Fabales", "Primates"] sorted(orders), ["Agaricales", "Diptera", "Fabales", "Primates"]
) )
# Entire test can be removed once deprecation period ends.
@ignore_warnings(category=RemovedInDjango60Warning)
def test_select_related_with_extra(self): def test_select_related_with_extra(self):
s = ( s = (
Species.objects.all() Species.objects.all()