mirror of
https://github.com/django/django.git
synced 2025-03-09 17:02:43 +00:00
Deprecated QuerySet.extra() except for the tables option.
This commit is contained in:
parent
5d539daa56
commit
9a83c66561
@ -20,10 +20,17 @@ from django.db import (
|
||||
router,
|
||||
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.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.query_utils import FilteredRelation, Q
|
||||
from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
|
||||
@ -203,9 +210,7 @@ class ValuesIterable(BaseIterable):
|
||||
if query.selected:
|
||||
names = list(query.selected)
|
||||
else:
|
||||
# extra(select=...) cols are always at the start of the row.
|
||||
names = [
|
||||
*query.extra_select,
|
||||
*query.values_select,
|
||||
*query.annotation_select,
|
||||
]
|
||||
@ -246,7 +251,6 @@ class NamedValuesListIterable(ValuesListIterable):
|
||||
else:
|
||||
query = queryset.query
|
||||
names = [
|
||||
*query.extra_select,
|
||||
*query.values_select,
|
||||
*query.annotation_select,
|
||||
]
|
||||
@ -1711,7 +1715,68 @@ class QuerySet(AltersData):
|
||||
if self.query.is_sliced:
|
||||
raise TypeError("Cannot change a query once a slice has been taken.")
|
||||
clone = self._chain()
|
||||
clone.query.add_extra(select, select_params, where, params, tables, order_by)
|
||||
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
|
||||
|
||||
def reverse(self):
|
||||
@ -1778,7 +1843,7 @@ class QuerySet(AltersData):
|
||||
"""
|
||||
if isinstance(self, EmptyQuerySet):
|
||||
return True
|
||||
if self.query.extra_order_by or self.query.order_by:
|
||||
if self.query.order_by:
|
||||
return True
|
||||
elif (
|
||||
self.query.default_ordering
|
||||
@ -1930,7 +1995,6 @@ class QuerySet(AltersData):
|
||||
"""Check that two QuerySet classes may be merged."""
|
||||
if self._fields is not None and (
|
||||
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)
|
||||
):
|
||||
raise TypeError(
|
||||
|
@ -258,20 +258,11 @@ class SQLCompiler:
|
||||
if cols:
|
||||
klass_info = {
|
||||
"model": self.query.model,
|
||||
"select_fields": list(
|
||||
range(
|
||||
len(self.query.extra_select),
|
||||
len(self.query.extra_select) + len(cols),
|
||||
)
|
||||
),
|
||||
"select_fields": list(range(0, len(cols))),
|
||||
}
|
||||
selected = []
|
||||
if self.query.selected is None:
|
||||
selected = [
|
||||
*(
|
||||
(alias, RawSQL(*args))
|
||||
for alias, args in self.query.extra_select.items()
|
||||
),
|
||||
*((None, col) for col in cols),
|
||||
*self.query.annotation_select.items(),
|
||||
]
|
||||
@ -329,9 +320,7 @@ class SQLCompiler:
|
||||
return ret, klass_info, annotations
|
||||
|
||||
def _order_by_pairs(self):
|
||||
if self.query.extra_order_by:
|
||||
ordering = self.query.extra_order_by
|
||||
elif not self.query.default_ordering:
|
||||
if not self.query.default_ordering:
|
||||
ordering = self.query.order_by
|
||||
elif self.query.order_by:
|
||||
ordering = self.query.order_by
|
||||
@ -428,35 +417,6 @@ class SQLCompiler:
|
||||
yield OrderBy(expr, descending=descending), False
|
||||
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:
|
||||
if self.query.combinator and self.select:
|
||||
# Don't use the first model's field because other
|
||||
@ -550,13 +510,8 @@ class SQLCompiler:
|
||||
"""
|
||||
if name in self.quote_cache:
|
||||
return self.quote_cache[name]
|
||||
if (
|
||||
(name in self.query.alias_map 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
|
||||
)
|
||||
if (name in self.query.alias_map and name not in self.query.table_map) or (
|
||||
self.query.external_aliases.get(name) and name not in self.query.table_map
|
||||
):
|
||||
self.quote_cache[name] = name
|
||||
return name
|
||||
@ -2044,7 +1999,6 @@ class SQLUpdateCompiler(SQLCompiler):
|
||||
query = self.query.chain(klass=Query)
|
||||
query.select_related = False
|
||||
query.clear_ordering(force=True)
|
||||
query.extra = {}
|
||||
query.select = []
|
||||
meta = query.get_meta()
|
||||
fields = [meta.pk.name]
|
||||
|
@ -27,7 +27,6 @@ from django.db.models.expressions import (
|
||||
Exists,
|
||||
F,
|
||||
OuterRef,
|
||||
RawSQL,
|
||||
Ref,
|
||||
ResolvedOuterRef,
|
||||
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.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.regex_helper import _lazy_re_compile
|
||||
from django.utils.tree import Node
|
||||
@ -153,7 +152,6 @@ class RawQuery:
|
||||
# Mirror some properties of a normal query so that
|
||||
# the compiler can be used to process results.
|
||||
self.low_mark, self.high_mark = 0, None # Used for offset/limit
|
||||
self.extra_select = {}
|
||||
self.annotation_select = {}
|
||||
|
||||
def chain(self, using):
|
||||
@ -263,8 +261,9 @@ class Query(BaseExpression):
|
||||
# Arbitrary limit for select_related to prevents infinite recursion.
|
||||
max_depth = 5
|
||||
# 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_all = None
|
||||
selected = None
|
||||
|
||||
# SQL annotation-related attributes.
|
||||
@ -276,13 +275,9 @@ class Query(BaseExpression):
|
||||
combinator_all = False
|
||||
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.
|
||||
extra_select_mask = None
|
||||
_extra_select_cache = None
|
||||
|
||||
extra_tables = ()
|
||||
extra_order_by = ()
|
||||
|
||||
# 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.
|
||||
@ -312,9 +307,6 @@ class Query(BaseExpression):
|
||||
self.where = WhereNode()
|
||||
# Maps alias -> Annotation Expression.
|
||||
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 = {}
|
||||
|
||||
@ -401,11 +393,6 @@ class Query(BaseExpression):
|
||||
# It will get re-populated in the cloned queryset the next time it's
|
||||
# used.
|
||||
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:
|
||||
# Use deepcopy because select_related stores fields in nested
|
||||
# dicts.
|
||||
@ -596,7 +583,6 @@ class Query(BaseExpression):
|
||||
self.select = ()
|
||||
self.selected = None
|
||||
self.default_cols = False
|
||||
self.extra = {}
|
||||
if self.annotations:
|
||||
# Inline reference to existing annotations and mask them as
|
||||
# they are unnecessary given only the summarized aggregations
|
||||
@ -767,35 +753,17 @@ class Query(BaseExpression):
|
||||
w.relabel_aliases(change_map)
|
||||
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:
|
||||
self.set_select([col.relabeled_clone(change_map) for col in rhs.select])
|
||||
else:
|
||||
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
|
||||
|
||||
# Ordering uses the 'rhs' ordering, unless it has none, in which case
|
||||
# the current ordering is used.
|
||||
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):
|
||||
if select_mask is None:
|
||||
@ -2162,19 +2130,19 @@ class Query(BaseExpression):
|
||||
self.select = ()
|
||||
self.default_cols = False
|
||||
self.select_related = False
|
||||
self.set_extra_mask(())
|
||||
self.set_annotation_mask(())
|
||||
self.selected = None
|
||||
|
||||
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
|
||||
columns.
|
||||
"""
|
||||
self.select = ()
|
||||
self.values_select = ()
|
||||
self.selected = None
|
||||
self.values_select_all = None
|
||||
|
||||
def add_select_col(self, col, name):
|
||||
self.select += (col,)
|
||||
@ -2228,7 +2196,6 @@ class Query(BaseExpression):
|
||||
names = sorted(
|
||||
[
|
||||
*get_field_names_from_opts(opts),
|
||||
*self.extra,
|
||||
*self.annotation_select,
|
||||
*self._filtered_relations,
|
||||
]
|
||||
@ -2255,8 +2222,6 @@ class Query(BaseExpression):
|
||||
item = item.removeprefix("-")
|
||||
if item in self.annotations:
|
||||
continue
|
||||
if self.extra and item in self.extra:
|
||||
continue
|
||||
# names_to_path() validates the lookup. A descriptive
|
||||
# FieldError will be raise if it's not.
|
||||
self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
|
||||
@ -2286,7 +2251,6 @@ class Query(BaseExpression):
|
||||
):
|
||||
return
|
||||
self.order_by = ()
|
||||
self.extra_order_by = ()
|
||||
if clear_default:
|
||||
self.default_ordering = False
|
||||
|
||||
@ -2339,38 +2303,8 @@ class Query(BaseExpression):
|
||||
d = d.setdefault(part, {})
|
||||
self.select_related = field_dict
|
||||
|
||||
def add_extra(self, select, select_params, where, params, tables, order_by):
|
||||
"""
|
||||
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)
|
||||
if order_by:
|
||||
self.extra_order_by = order_by
|
||||
def add_extra_tables(self, tables):
|
||||
self.extra_tables += tuple(tables)
|
||||
|
||||
def clear_deferred_loading(self):
|
||||
"""Remove any fields from the deferred loading set."""
|
||||
@ -2448,17 +2382,6 @@ class Query(BaseExpression):
|
||||
if self.annotation_select_mask is not None:
|
||||
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
|
||||
def has_select_fields(self):
|
||||
return self.selected is not None
|
||||
@ -2473,20 +2396,16 @@ class Query(BaseExpression):
|
||||
for field in fields:
|
||||
self.check_alias(field)
|
||||
field_names = []
|
||||
extra_names = []
|
||||
annotation_names = []
|
||||
if not self.extra and not self.annotations:
|
||||
# Shortcut - if there are no extra or annotations, then
|
||||
if not self.annotations:
|
||||
# Shortcut - if there are no annotations, then
|
||||
# the values() clause must be just field names.
|
||||
field_names = list(fields)
|
||||
selected = dict(zip(fields, range(len(fields))))
|
||||
else:
|
||||
self.default_cols = False
|
||||
for f in fields:
|
||||
if extra := self.extra_select.get(f):
|
||||
extra_names.append(f)
|
||||
selected[f] = RawSQL(*extra)
|
||||
elif f in self.annotation_select:
|
||||
if f in self.annotation_select:
|
||||
annotation_names.append(f)
|
||||
selected[f] = f
|
||||
elif f in self.annotations:
|
||||
@ -2502,7 +2421,6 @@ class Query(BaseExpression):
|
||||
self.names_to_path(f.split(LOOKUP_SEP), self.model._meta)
|
||||
selected[f] = len(field_names)
|
||||
field_names.append(f)
|
||||
self.set_extra_mask(extra_names)
|
||||
self.set_annotation_mask(annotation_names)
|
||||
else:
|
||||
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.values_select = tuple(field_names)
|
||||
self.values_select_all = not fields
|
||||
self.add_fields(field_names, True)
|
||||
self.selected = selected if fields else None
|
||||
|
||||
@ -2551,20 +2470,6 @@ class Query(BaseExpression):
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
Trim joins from the start of the join path. The candidates for trim
|
||||
|
@ -331,20 +331,6 @@ class NothingNode:
|
||||
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:
|
||||
# Even if aggregates or windows would be used in a subquery,
|
||||
# the outer query isn't interested about those.
|
||||
|
@ -44,10 +44,10 @@ from django.db.models.functions import (
|
||||
TruncDate,
|
||||
TruncHour,
|
||||
)
|
||||
from django.test import TestCase
|
||||
from django.test.testcases import skipUnlessDBFeature
|
||||
from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
|
||||
from django.test.utils import Approximate, CaptureQueriesContext
|
||||
from django.utils import timezone
|
||||
from django.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
from .models import Author, Book, Publisher, Store
|
||||
|
||||
@ -2139,6 +2139,8 @@ class AggregateTestCase(TestCase):
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
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):
|
||||
qs = Book.objects.annotate(
|
||||
count=Count("id"),
|
||||
|
@ -25,8 +25,9 @@ from django.db.models import (
|
||||
When,
|
||||
)
|
||||
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.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
from .models import (
|
||||
Alfa,
|
||||
@ -238,6 +239,8 @@ class AggregationTests(TestCase):
|
||||
qs2 = books.filter(id__in=list(qs))
|
||||
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")
|
||||
def test_annotate_with_extra(self):
|
||||
"""
|
||||
@ -287,14 +290,18 @@ class AggregationTests(TestCase):
|
||||
{"pages__sum": 3703, "pages__avg": Approximate(617.166, places=2)},
|
||||
)
|
||||
|
||||
# Aggregate overrides extra selected column
|
||||
self.assertEqual(
|
||||
Book.objects.extra(select={"price_per_page": "price / pages"}).aggregate(
|
||||
Sum("pages")
|
||||
),
|
||||
{"pages__sum": 3703},
|
||||
)
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
# Aggregate overrides extra selected column
|
||||
self.assertEqual(
|
||||
Book.objects.extra(
|
||||
select={"price_per_page": "price / pages"}
|
||||
).aggregate(Sum("pages")),
|
||||
{"pages__sum": 3703},
|
||||
)
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_annotation(self):
|
||||
# Annotations get combined with extra select clauses
|
||||
obj = (
|
||||
@ -879,37 +886,39 @@ class AggregationTests(TestCase):
|
||||
|
||||
# Regression for #10132 - If the values() clause only mentioned extra
|
||||
# (select=) columns, those columns are used for grouping
|
||||
qs = (
|
||||
Book.objects.extra(select={"pub": "publisher_id"})
|
||||
.values("pub")
|
||||
.annotate(Count("id"))
|
||||
.order_by("pub")
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
qs,
|
||||
[
|
||||
{"pub": self.p1.id, "id__count": 2},
|
||||
{"pub": self.p2.id, "id__count": 1},
|
||||
{"pub": self.p3.id, "id__count": 2},
|
||||
{"pub": self.p4.id, "id__count": 1},
|
||||
],
|
||||
)
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
qs = (
|
||||
Book.objects.extra(select={"pub": "publisher_id"})
|
||||
.values("pub")
|
||||
.annotate(Count("id"))
|
||||
.order_by("pub")
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
qs,
|
||||
[
|
||||
{"pub": self.p1.id, "id__count": 2},
|
||||
{"pub": self.p2.id, "id__count": 1},
|
||||
{"pub": self.p3.id, "id__count": 2},
|
||||
{"pub": self.p4.id, "id__count": 1},
|
||||
],
|
||||
)
|
||||
|
||||
qs = (
|
||||
Book.objects.extra(select={"pub": "publisher_id", "foo": "pages"})
|
||||
.values("pub")
|
||||
.annotate(Count("id"))
|
||||
.order_by("pub")
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
qs,
|
||||
[
|
||||
{"pub": self.p1.id, "id__count": 2},
|
||||
{"pub": self.p2.id, "id__count": 1},
|
||||
{"pub": self.p3.id, "id__count": 2},
|
||||
{"pub": self.p4.id, "id__count": 1},
|
||||
],
|
||||
)
|
||||
qs = (
|
||||
Book.objects.extra(select={"pub": "publisher_id", "foo": "pages"})
|
||||
.values("pub")
|
||||
.annotate(Count("id"))
|
||||
.order_by("pub")
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
qs,
|
||||
[
|
||||
{"pub": self.p1.id, "id__count": 2},
|
||||
{"pub": self.p2.id, "id__count": 1},
|
||||
{"pub": self.p3.id, "id__count": 2},
|
||||
{"pub": self.p4.id, "id__count": 1},
|
||||
],
|
||||
)
|
||||
|
||||
# Regression for #10182 - Queries with aggregate calls are correctly
|
||||
# realiased when used in a subquery
|
||||
@ -1058,15 +1067,17 @@ class AggregationTests(TestCase):
|
||||
|
||||
# Regression for #10290 - extra selects with parameters can be used for
|
||||
# grouping.
|
||||
qs = (
|
||||
Book.objects.annotate(mean_auth_age=Avg("authors__age"))
|
||||
.extra(select={"sheets": "(pages + %s) / %s"}, select_params=[1, 2])
|
||||
.order_by("sheets")
|
||||
.values("sheets")
|
||||
)
|
||||
self.assertQuerySetEqual(
|
||||
qs, [150, 175, 224, 264, 473, 566], lambda b: int(b["sheets"])
|
||||
)
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
qs = (
|
||||
Book.objects.annotate(mean_auth_age=Avg("authors__age"))
|
||||
.extra(select={"sheets": "(pages + %s) / %s"}, select_params=[1, 2])
|
||||
.order_by("sheets")
|
||||
.values("sheets")
|
||||
)
|
||||
self.assertQuerySetEqual(
|
||||
qs, [150, 175, 224, 264, 473, 566], lambda b: int(b["sheets"])
|
||||
)
|
||||
|
||||
# Regression for 10425 - annotations don't get in the way of a count()
|
||||
# clause
|
||||
|
@ -37,8 +37,9 @@ from django.db.models.functions import (
|
||||
Trim,
|
||||
)
|
||||
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.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
from .models import (
|
||||
Author,
|
||||
@ -722,10 +723,9 @@ class NonAggregateAnnotationTestCase(TestCase):
|
||||
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
|
||||
aligned as:
|
||||
1. extra_select
|
||||
2. model_fields
|
||||
3. annotation_fields
|
||||
4. model_related_fields
|
||||
1. model_fields
|
||||
2. annotation_fields
|
||||
3. model_related_fields
|
||||
"""
|
||||
store = Store.objects.first()
|
||||
Employee.objects.create(
|
||||
@ -747,13 +747,15 @@ class NonAggregateAnnotationTestCase(TestCase):
|
||||
salary=Decimal(40000.00),
|
||||
)
|
||||
|
||||
qs = (
|
||||
Employee.objects.extra(select={"random_value": "42"})
|
||||
.select_related("store")
|
||||
.annotate(
|
||||
annotated_value=Value(17),
|
||||
# random_value annotation can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
qs = (
|
||||
Employee.objects.extra(select={"random_value": "42"})
|
||||
.select_related("store")
|
||||
.annotate(
|
||||
annotated_value=Value(17),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
rows = [
|
||||
(1, "Max", True, 42, "Paine", 23, Decimal(50000.00), store.name, 17),
|
||||
@ -797,13 +799,15 @@ class NonAggregateAnnotationTestCase(TestCase):
|
||||
salary=Decimal(40000.00),
|
||||
)
|
||||
|
||||
qs = (
|
||||
Employee.objects.extra(select={"random_value": "42"})
|
||||
.select_related("store")
|
||||
.annotate(
|
||||
annotated_value=Value(17),
|
||||
# random_value annotation can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
qs = (
|
||||
Employee.objects.extra(select={"random_value": "42"})
|
||||
.select_related("store")
|
||||
.annotate(
|
||||
annotated_value=Value(17),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
rows = [
|
||||
(1, "Max", True, 42, "Paine", 23, Decimal(50000.00), store.name, 17),
|
||||
|
@ -455,6 +455,8 @@ class ModelTest(TestCase):
|
||||
s = {a10, a11, a12}
|
||||
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):
|
||||
# The 'select' argument to extra() supports names with dashes in
|
||||
# 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):
|
||||
# If you use 'select' with extra() and names containing dashes on a
|
||||
# query that's *not* a values() query, those extra 'select' values
|
||||
|
@ -1,5 +1,6 @@
|
||||
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 (
|
||||
BigChild,
|
||||
@ -82,6 +83,8 @@ class DeferTests(AssertionMixin, TestCase):
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
Primary.objects.only(None)
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_defer_extra(self):
|
||||
qs = Primary.objects.all()
|
||||
self.assert_delayed(qs.defer("name").extra(select={"a": 1})[0], 1)
|
||||
|
@ -6,8 +6,10 @@ from django.test import (
|
||||
SimpleTestCase,
|
||||
TestCase,
|
||||
TransactionTestCase,
|
||||
ignore_warnings,
|
||||
skipUnlessDBFeature,
|
||||
)
|
||||
from django.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
from .models import (
|
||||
Award,
|
||||
@ -309,6 +311,8 @@ class Ticket19102Tests(TestCase):
|
||||
self.assertFalse(Login.objects.filter(pk=self.l1.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")
|
||||
def test_ticket_19102_extra(self):
|
||||
with self.assertNumQueries(1):
|
||||
|
@ -1,11 +1,13 @@
|
||||
import datetime
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
class ExtraRegressTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@ -284,7 +286,7 @@ class ExtraRegressTests(TestCase):
|
||||
select={"foo": "first", "bar": "second", "whiz": "third"}
|
||||
).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
|
||||
@ -294,7 +296,7 @@ class ExtraRegressTests(TestCase):
|
||||
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()
|
||||
@ -468,3 +470,23 @@ class ExtraRegressTests(TestCase):
|
||||
self.assertSequenceEqual(
|
||||
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__)
|
||||
|
@ -18,8 +18,9 @@ from django.db.models import (
|
||||
)
|
||||
from django.db.models.functions import Concat
|
||||
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.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
from .models import (
|
||||
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):
|
||||
self.assertSequenceEqual(
|
||||
Author.objects.annotate(
|
||||
|
@ -28,8 +28,9 @@ from django.db.models.lookups import (
|
||||
LessThan,
|
||||
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.utils.deprecation import RemovedInDjango60Warning
|
||||
|
||||
from .models import (
|
||||
Article,
|
||||
@ -379,46 +380,48 @@ class LookupTests(TestCase):
|
||||
{"headline": "Article 1", "id": self.a1.id},
|
||||
],
|
||||
)
|
||||
# The values() method works with "extra" fields specified in extra(select).
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id + 1"}).values(
|
||||
"id", "id_plus_one"
|
||||
),
|
||||
[
|
||||
{"id": self.a5.id, "id_plus_one": self.a5.id + 1},
|
||||
{"id": self.a6.id, "id_plus_one": self.a6.id + 1},
|
||||
{"id": self.a4.id, "id_plus_one": self.a4.id + 1},
|
||||
{"id": self.a2.id, "id_plus_one": self.a2.id + 1},
|
||||
{"id": self.a3.id, "id_plus_one": self.a3.id + 1},
|
||||
{"id": self.a7.id, "id_plus_one": self.a7.id + 1},
|
||||
{"id": self.a1.id, "id_plus_one": self.a1.id + 1},
|
||||
],
|
||||
)
|
||||
data = {
|
||||
"id_plus_one": "id+1",
|
||||
"id_plus_two": "id+2",
|
||||
"id_plus_three": "id+3",
|
||||
"id_plus_four": "id+4",
|
||||
"id_plus_five": "id+5",
|
||||
"id_plus_six": "id+6",
|
||||
"id_plus_seven": "id+7",
|
||||
"id_plus_eight": "id+8",
|
||||
}
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.filter(id=self.a1.id).extra(select=data).values(*data),
|
||||
[
|
||||
{
|
||||
"id_plus_one": self.a1.id + 1,
|
||||
"id_plus_two": self.a1.id + 2,
|
||||
"id_plus_three": self.a1.id + 3,
|
||||
"id_plus_four": self.a1.id + 4,
|
||||
"id_plus_five": self.a1.id + 5,
|
||||
"id_plus_six": self.a1.id + 6,
|
||||
"id_plus_seven": self.a1.id + 7,
|
||||
"id_plus_eight": self.a1.id + 8,
|
||||
}
|
||||
],
|
||||
)
|
||||
# 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).
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id + 1"}).values(
|
||||
"id", "id_plus_one"
|
||||
),
|
||||
[
|
||||
{"id": self.a5.id, "id_plus_one": self.a5.id + 1},
|
||||
{"id": self.a6.id, "id_plus_one": self.a6.id + 1},
|
||||
{"id": self.a4.id, "id_plus_one": self.a4.id + 1},
|
||||
{"id": self.a2.id, "id_plus_one": self.a2.id + 1},
|
||||
{"id": self.a3.id, "id_plus_one": self.a3.id + 1},
|
||||
{"id": self.a7.id, "id_plus_one": self.a7.id + 1},
|
||||
{"id": self.a1.id, "id_plus_one": self.a1.id + 1},
|
||||
],
|
||||
)
|
||||
data = {
|
||||
"id_plus_one": "id+1",
|
||||
"id_plus_two": "id+2",
|
||||
"id_plus_three": "id+3",
|
||||
"id_plus_four": "id+4",
|
||||
"id_plus_five": "id+5",
|
||||
"id_plus_six": "id+6",
|
||||
"id_plus_seven": "id+7",
|
||||
"id_plus_eight": "id+8",
|
||||
}
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.filter(id=self.a1.id).extra(select=data).values(*data),
|
||||
[
|
||||
{
|
||||
"id_plus_one": self.a1.id + 1,
|
||||
"id_plus_two": self.a1.id + 2,
|
||||
"id_plus_three": self.a1.id + 3,
|
||||
"id_plus_four": self.a1.id + 4,
|
||||
"id_plus_five": self.a1.id + 5,
|
||||
"id_plus_six": self.a1.id + 6,
|
||||
"id_plus_seven": self.a1.id + 7,
|
||||
"id_plus_eight": self.a1.id + 8,
|
||||
}
|
||||
],
|
||||
)
|
||||
# You can specify fields from forward and reverse relations, just like filter().
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.values("headline", "author__name"),
|
||||
@ -500,17 +503,19 @@ class LookupTests(TestCase):
|
||||
},
|
||||
],
|
||||
)
|
||||
# However, an exception FieldDoesNotExist will be thrown if you specify
|
||||
# a nonexistent field name in values() (a field that is neither in the
|
||||
# model nor in extra(select)).
|
||||
msg = (
|
||||
"Cannot resolve keyword 'id_plus_two' into field. Choices are: "
|
||||
"author, author_id, headline, id, id_plus_one, pub_date, slug, tag"
|
||||
)
|
||||
with self.assertRaisesMessage(FieldError, msg):
|
||||
Article.objects.extra(select={"id_plus_one": "id + 1"}).values(
|
||||
"id", "id_plus_two"
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
# However, an exception FieldDoesNotExist will be thrown if you specify
|
||||
# a nonexistent field name in values() (a field that is neither in the
|
||||
# model nor in extra(select)).
|
||||
msg = (
|
||||
"Cannot resolve keyword 'id_plus_two' into field. Choices are: "
|
||||
"author, author_id, headline, id, id_plus_one, pub_date, slug, tag"
|
||||
)
|
||||
with self.assertRaisesMessage(FieldError, msg):
|
||||
Article.objects.extra(select={"id_plus_one": "id + 1"}).values(
|
||||
"id", "id_plus_two"
|
||||
)
|
||||
# If you don't specify field names to values(), all are returned.
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.filter(id=self.a5.id).values(),
|
||||
@ -566,48 +571,50 @@ class LookupTests(TestCase):
|
||||
self.a7.id,
|
||||
],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id+1"})
|
||||
.order_by("id")
|
||||
.values_list("id"),
|
||||
[
|
||||
(self.a1.id,),
|
||||
(self.a2.id,),
|
||||
(self.a3.id,),
|
||||
(self.a4.id,),
|
||||
(self.a5.id,),
|
||||
(self.a6.id,),
|
||||
(self.a7.id,),
|
||||
],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id+1"})
|
||||
.order_by("id")
|
||||
.values_list("id_plus_one", "id"),
|
||||
[
|
||||
(self.a1.id + 1, self.a1.id),
|
||||
(self.a2.id + 1, self.a2.id),
|
||||
(self.a3.id + 1, self.a3.id),
|
||||
(self.a4.id + 1, self.a4.id),
|
||||
(self.a5.id + 1, self.a5.id),
|
||||
(self.a6.id + 1, self.a6.id),
|
||||
(self.a7.id + 1, self.a7.id),
|
||||
],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id+1"})
|
||||
.order_by("id")
|
||||
.values_list("id", "id_plus_one"),
|
||||
[
|
||||
(self.a1.id, self.a1.id + 1),
|
||||
(self.a2.id, self.a2.id + 1),
|
||||
(self.a3.id, self.a3.id + 1),
|
||||
(self.a4.id, self.a4.id + 1),
|
||||
(self.a5.id, self.a5.id + 1),
|
||||
(self.a6.id, self.a6.id + 1),
|
||||
(self.a7.id, self.a7.id + 1),
|
||||
],
|
||||
)
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id+1"})
|
||||
.order_by("id")
|
||||
.values_list("id"),
|
||||
[
|
||||
(self.a1.id,),
|
||||
(self.a2.id,),
|
||||
(self.a3.id,),
|
||||
(self.a4.id,),
|
||||
(self.a5.id,),
|
||||
(self.a6.id,),
|
||||
(self.a7.id,),
|
||||
],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id+1"})
|
||||
.order_by("id")
|
||||
.values_list("id_plus_one", "id"),
|
||||
[
|
||||
(self.a1.id + 1, self.a1.id),
|
||||
(self.a2.id + 1, self.a2.id),
|
||||
(self.a3.id + 1, self.a3.id),
|
||||
(self.a4.id + 1, self.a4.id),
|
||||
(self.a5.id + 1, self.a5.id),
|
||||
(self.a6.id + 1, self.a6.id),
|
||||
(self.a7.id + 1, self.a7.id),
|
||||
],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.extra(select={"id_plus_one": "id+1"})
|
||||
.order_by("id")
|
||||
.values_list("id", "id_plus_one"),
|
||||
[
|
||||
(self.a1.id, self.a1.id + 1),
|
||||
(self.a2.id, self.a2.id + 1),
|
||||
(self.a3.id, self.a3.id + 1),
|
||||
(self.a4.id, self.a4.id + 1),
|
||||
(self.a5.id, self.a5.id + 1),
|
||||
(self.a6.id, self.a6.id + 1),
|
||||
(self.a7.id, self.a7.id + 1),
|
||||
],
|
||||
)
|
||||
args = ("name", "article__headline", "article__tag__name")
|
||||
self.assertSequenceEqual(
|
||||
Author.objects.values_list(*args).order_by(*args),
|
||||
|
@ -3,7 +3,7 @@ from copy import deepcopy
|
||||
|
||||
from django.core.exceptions import FieldError, MultipleObjectsReturned
|
||||
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.translation import gettext_lazy
|
||||
|
||||
@ -282,23 +282,25 @@ class ManyToOneTests(TestCase):
|
||||
queryset.query.get_compiler(queryset.db).as_sql()[0].count("INNER JOIN"), 1
|
||||
)
|
||||
|
||||
# The automatically joined table has a predictable name.
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.filter(reporter__first_name__exact="John").extra(
|
||||
where=["many_to_one_reporter.last_name='Smith'"]
|
||||
),
|
||||
[new_article1, self.a],
|
||||
)
|
||||
# ... and should work fine with the string that comes out of
|
||||
# forms.Form.cleaned_data.
|
||||
self.assertQuerySetEqual(
|
||||
(
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
# The automatically joined table has a predictable name.
|
||||
self.assertSequenceEqual(
|
||||
Article.objects.filter(reporter__first_name__exact="John").extra(
|
||||
where=["many_to_one_reporter.last_name='%s'" % "Smith"]
|
||||
)
|
||||
),
|
||||
[new_article1, self.a],
|
||||
)
|
||||
where=["many_to_one_reporter.last_name='Smith'"]
|
||||
),
|
||||
[new_article1, self.a],
|
||||
)
|
||||
# ... and should work fine with the string that comes out of
|
||||
# forms.Form.cleaned_data.
|
||||
self.assertQuerySetEqual(
|
||||
(
|
||||
Article.objects.filter(reporter__first_name__exact="John").extra(
|
||||
where=["many_to_one_reporter.last_name='%s'" % "Smith"]
|
||||
)
|
||||
),
|
||||
[new_article1, self.a],
|
||||
)
|
||||
# Find all Articles for a Reporter.
|
||||
# Use direct ID check, pk check, and object comparison
|
||||
self.assertSequenceEqual(
|
||||
@ -570,13 +572,17 @@ class ManyToOneTests(TestCase):
|
||||
reporter_fields = ", ".join(sorted(f.name for f in Reporter._meta.get_fields()))
|
||||
with self.assertRaisesMessage(FieldError, expected_message % reporter_fields):
|
||||
Article.objects.values_list("reporter__notafield")
|
||||
article_fields = ", ".join(
|
||||
["EXTRA"] + sorted(f.name for f in Article._meta.get_fields())
|
||||
)
|
||||
with self.assertRaisesMessage(FieldError, expected_message % article_fields):
|
||||
Article.objects.extra(select={"EXTRA": "EXTRA_SELECT"}).values_list(
|
||||
"notafield"
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
article_fields = ", ".join(
|
||||
["EXTRA"] + sorted(f.name for f in Article._meta.get_fields())
|
||||
)
|
||||
with self.assertRaisesMessage(
|
||||
FieldError, expected_message % article_fields
|
||||
):
|
||||
Article.objects.extra(select={"EXTRA": "EXTRA_SELECT"}).values_list(
|
||||
"notafield"
|
||||
)
|
||||
|
||||
def test_fk_assignment_and_related_object_cache(self):
|
||||
# Tests of ForeignKey assignment and the related-object cache (see #6886).
|
||||
|
@ -14,7 +14,8 @@ from django.db.models import (
|
||||
Value,
|
||||
)
|
||||
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 (
|
||||
Article,
|
||||
@ -335,6 +336,8 @@ class OrderingTests(TestCase):
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
qs.last()
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_extra_ordering(self):
|
||||
"""
|
||||
Ordering can be based on fields included from an 'extra' clause
|
||||
@ -352,6 +355,8 @@ class OrderingTests(TestCase):
|
||||
attrgetter("headline"),
|
||||
)
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_extra_ordering_quoting(self):
|
||||
"""
|
||||
If the extra clause uses an SQL keyword for a name, it will be
|
||||
@ -370,6 +375,8 @@ class OrderingTests(TestCase):
|
||||
attrgetter("headline"),
|
||||
)
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_extra_ordering_with_table_name(self):
|
||||
self.assertQuerySetEqual(
|
||||
Article.objects.extra(order_by=["ordering_article.headline"]),
|
||||
|
@ -12,7 +12,8 @@ from django.db.models import (
|
||||
)
|
||||
from django.db.models.functions import Mod
|
||||
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
|
||||
|
||||
@ -280,6 +281,8 @@ class QuerySetSetOperationTests(TestCase):
|
||||
)
|
||||
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):
|
||||
qs1 = (
|
||||
Number.objects.filter(num=1)
|
||||
@ -408,6 +411,8 @@ class QuerySetSetOperationTests(TestCase):
|
||||
[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):
|
||||
reserved_name = ReservedName.objects.create(name="rn1", order=0)
|
||||
qs1 = Celebrity.objects.extra(select={"extra_name": "name"})
|
||||
|
@ -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.where import AND, OR, NothingNode, WhereNode
|
||||
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 (
|
||||
FK1,
|
||||
@ -325,26 +326,28 @@ class Queries1Tests(TestCase):
|
||||
.count(),
|
||||
4,
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
Item.objects.exclude(name="two")
|
||||
.extra(select={"foo": "%s"}, select_params=(1,))
|
||||
.values("creator", "name", "foo")
|
||||
.distinct()
|
||||
.count()
|
||||
),
|
||||
4,
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
Item.objects.exclude(name="two")
|
||||
.extra(select={"foo": "%s"}, select_params=(1,))
|
||||
.values("creator", "name")
|
||||
.distinct()
|
||||
.count()
|
||||
),
|
||||
4,
|
||||
)
|
||||
# Entire block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
self.assertEqual(
|
||||
(
|
||||
Item.objects.exclude(name="two")
|
||||
.extra(select={"foo": "%s"}, select_params=(1,))
|
||||
.values("creator", "name", "foo")
|
||||
.distinct()
|
||||
.count()
|
||||
),
|
||||
4,
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
Item.objects.exclude(name="two")
|
||||
.extra(select={"foo": "%s"}, select_params=(1,))
|
||||
.values("creator", "name")
|
||||
.distinct()
|
||||
.count()
|
||||
),
|
||||
4,
|
||||
)
|
||||
xx.delete()
|
||||
|
||||
def test_ticket7323(self):
|
||||
@ -594,6 +597,8 @@ class Queries1Tests(TestCase):
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
Author.objects.all() | Tag.objects.all()
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_ticket3141(self):
|
||||
self.assertEqual(Author.objects.extra(select={"foo": "1"}).count(), 4)
|
||||
self.assertEqual(
|
||||
@ -750,6 +755,8 @@ class Queries1Tests(TestCase):
|
||||
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):
|
||||
# Dates with extra select columns
|
||||
self.assertSequenceEqual(
|
||||
@ -893,12 +900,16 @@ class Queries1Tests(TestCase):
|
||||
self.assertSequenceEqual(q.annotate(Count("food")), [])
|
||||
self.assertSequenceEqual(q.order_by("meal", "food"), [])
|
||||
self.assertSequenceEqual(q.distinct(), [])
|
||||
self.assertSequenceEqual(q.extra(select={"foo": "1"}), [])
|
||||
# Block can be removed once deprecation period ends.
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
self.assertSequenceEqual(q.extra(select={"foo": "1"}), [])
|
||||
self.assertSequenceEqual(q.reverse(), [])
|
||||
q.query.low_mark = 1
|
||||
msg = "Cannot change a query once a slice has been taken."
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
q.extra(select={"foo": "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."
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
q.extra(select={"foo": "1"})
|
||||
self.assertSequenceEqual(q.defer("meal"), [])
|
||||
self.assertSequenceEqual(q.only("meal"), [])
|
||||
|
||||
@ -1866,30 +1877,39 @@ class Queries5Tests(TestCase):
|
||||
[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
|
||||
# fields and model fields in the ordering.
|
||||
self.assertSequenceEqual(
|
||||
Ranking.objects.extra(
|
||||
tables=["django_site"], order_by=["-django_site.id", "rank"]
|
||||
),
|
||||
[self.rank1, self.rank2, self.rank3],
|
||||
)
|
||||
with ignore_warnings(category=RemovedInDjango60Warning):
|
||||
self.assertSequenceEqual(
|
||||
Ranking.objects.extra(
|
||||
tables=["django_site"], order_by=["-django_site.id", "rank"]
|
||||
),
|
||||
[self.rank1, self.rank2, self.rank3],
|
||||
)
|
||||
|
||||
sql = "case when %s > 2 then 1 else 0 end" % connection.ops.quote_name("rank")
|
||||
qs = Ranking.objects.extra(select={"good": sql})
|
||||
self.assertEqual(
|
||||
[o.good for o in qs.extra(order_by=("-good",))], [True, False, False]
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
qs.extra(order_by=("-good", "id")),
|
||||
[self.rank3, self.rank2, self.rank1],
|
||||
)
|
||||
# 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})
|
||||
self.assertEqual(
|
||||
[o.good for o in qs.extra(order_by=("-good",))], [True, False, False]
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
qs.extra(order_by=("-good", "id")),
|
||||
[self.rank3, self.rank2, self.rank1],
|
||||
)
|
||||
|
||||
# Despite having some extra aliases in the query, we can still omit
|
||||
# them in a values() query.
|
||||
dicts = qs.values("id", "rank").order_by("id")
|
||||
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):
|
||||
# An empty values() call includes all aliases, including those from an
|
||||
# extra()
|
||||
@ -1962,6 +1982,8 @@ class Queries5Tests(TestCase):
|
||||
[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):
|
||||
# Allow %%s to escape select clauses
|
||||
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"
|
||||
)
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_extra_select_alias_sql_injection(self):
|
||||
crafted_alias = """injected_name" from "queries_note"; --"""
|
||||
msg = (
|
||||
@ -2350,6 +2374,8 @@ class QuerysetOrderedTests(unittest.TestCase):
|
||||
def test_empty_queryset(self):
|
||||
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):
|
||||
self.assertIs(Annotation.objects.extra(order_by=["id"]).ordered, True)
|
||||
|
||||
@ -2682,6 +2708,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values_list("num", flat=True)
|
||||
self.assertSequenceEqual(qs, [72])
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_extra_values(self):
|
||||
# testing for ticket 14930 issues
|
||||
qs = Number.objects.extra(
|
||||
@ -2692,6 +2720,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values("num")
|
||||
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):
|
||||
# testing for ticket 14930 issues
|
||||
qs = Number.objects.extra(
|
||||
@ -2701,6 +2731,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values("num")
|
||||
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):
|
||||
# Postgres doesn't allow constants in order by, so check for that.
|
||||
qs = Number.objects.extra(
|
||||
@ -2714,6 +2746,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values("num")
|
||||
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):
|
||||
# testing for ticket 14930 issues
|
||||
qs = Number.objects.extra(
|
||||
@ -2722,6 +2756,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
)
|
||||
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):
|
||||
# testing for 23259 issue
|
||||
qs = Number.objects.extra(
|
||||
@ -2733,6 +2769,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values("num")
|
||||
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):
|
||||
# testing for 23259 issue
|
||||
qs = Number.objects.extra(
|
||||
@ -2744,6 +2782,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values("num")
|
||||
self.assertSequenceEqual(qs, [])
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_extra_values_list(self):
|
||||
# testing for ticket 14930 issues
|
||||
qs = Number.objects.extra(select={"value_plus_one": "num+1"})
|
||||
@ -2751,6 +2791,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
qs = qs.values_list("num")
|
||||
self.assertSequenceEqual(qs, [(72,)])
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_flat_extra_values_list(self):
|
||||
# testing for ticket 14930 issues
|
||||
qs = Number.objects.extra(select={"value_plus_one": "num+1"})
|
||||
@ -2772,6 +2814,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
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):
|
||||
msg = "Type names and field names must be valid identifiers: '1'"
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
@ -2779,6 +2823,8 @@ class ValuesQuerysetTests(TestCase):
|
||||
"1", named=True
|
||||
).first()
|
||||
|
||||
# Entire test can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
def test_named_values_list_with_fields(self):
|
||||
qs = Number.objects.extra(select={"num2": "num+1"}).annotate(Count("id"))
|
||||
values = qs.values_list("num", "num2", named=True).first()
|
||||
@ -2787,13 +2833,15 @@ class ValuesQuerysetTests(TestCase):
|
||||
self.assertEqual(values.num, 72)
|
||||
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):
|
||||
qs = Number.objects.extra(select={"num2": "num+1"}).annotate(Count("id"))
|
||||
values = qs.values_list(named=True).first()
|
||||
self.assertEqual(type(values).__name__, "Row")
|
||||
self.assertEqual(
|
||||
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.num2, 73)
|
||||
@ -2980,6 +3028,8 @@ class WeirdQuerysetSlicingTests(TestCase):
|
||||
self.assertQuerySetEqual(Article.objects.values_list()[n:n], [])
|
||||
|
||||
|
||||
# Entire testcase can be removed once deprecation period ends.
|
||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
||||
class EscapingTests(TestCase):
|
||||
def test_ticket_7302(self):
|
||||
# Reserved names are appropriately escaped
|
||||
|
@ -1,5 +1,6 @@
|
||||
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 (
|
||||
Bookmark,
|
||||
@ -119,6 +120,8 @@ class SelectRelatedTests(TestCase):
|
||||
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):
|
||||
s = (
|
||||
Species.objects.all()
|
||||
|
Loading…
x
Reference in New Issue
Block a user