Skip to content

Commit b70d239

Browse files
committed
Changed how annotations are temporarily renamed
If only a subset of annotations is renamed before calling the base class, and then renamed back using add new key/remove old key, then the ordering of annotations is changed (annotations are stored in a OrderedDict internally). This will cause issues if ordering is important (e.g., in a union).
1 parent e69ee68 commit b70d239

File tree

2 files changed

+25
-13
lines changed

2 files changed

+25
-13
lines changed

psqlextra/query.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections import OrderedDict
12
from itertools import chain
23
from typing import Dict, Iterable, List, Optional, Tuple, Union
34

@@ -32,22 +33,19 @@ def annotate(self, **annotations):
3233
Normally, the annotate function doesn't allow you to use the
3334
name of an existing field on the model as the alias name. This
3435
version of the function does allow that.
35-
"""
36-
37-
fields = {field.name: field for field in self.model._meta.get_fields()}
3836
39-
# temporarily rename the fields that have the same
40-
# name as a field name, we'll rename them back after
41-
# the function in the base class ran
42-
new_annotations = {}
37+
This is done by temporarily renaming the fields in order to avoid the
38+
check for conflicts that the base class does.
39+
We rename all fields instead of the ones that already exist because
40+
the annotations are stored in an OrderedDict. Renaming only the
41+
conflicts will mess up the order.
42+
"""
43+
new_annotations = OrderedDict()
4344
renames = {}
4445
for name, value in annotations.items():
45-
if name in fields:
46-
new_name = "%s_new" % name
47-
new_annotations[new_name] = value
48-
renames[new_name] = name
49-
else:
50-
new_annotations[name] = value
46+
new_name = "%s_new" % name
47+
new_annotations[new_name] = value
48+
renames[new_name] = name
5149

5250
# run the base class's annotate function
5351
result = super().annotate(**new_annotations)

tests/test_query.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ def test_query_annotate_rename_chain():
6161
assert obj[0]["value"] == 23
6262

6363

64+
def test_query_annotate_rename_order():
65+
"""Tests whether annotation order is preserved after a rename."""
66+
67+
model = get_fake_model(
68+
{
69+
"name": models.CharField(max_length=10),
70+
"value": models.IntegerField(),
71+
}
72+
)
73+
74+
qs = model.objects.annotate(value=F("value"), value_2=F("value"))
75+
assert list(qs.query.annotations.keys()) == ["value", "value_2"]
76+
77+
6478
def test_query_hstore_value_update_f_ref():
6579
"""Tests whether F(..) expressions can be used in hstore values when
6680
performing update queries."""

0 commit comments

Comments
 (0)