1
0
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:
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,
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(

View File

@ -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]

View File

@ -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

View File

@ -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.

View File

@ -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"),

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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__)

View 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(

View File

@ -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),

View File

@ -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).

View File

@ -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"]),

View File

@ -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"})

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.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

View File

@ -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()