1- from django .core .exceptions import ValidationError
1+ import operator
2+
3+ from django .core .exceptions import FieldDoesNotExist , ValidationError
24from django .db import models
5+ from django .db .models import ExpressionWrapper , F , Max , Sum
36from django .test import SimpleTestCase , TestCase
47from django .test .utils import isolate_apps
58
1316 Data ,
1417 Holder ,
1518)
19+ from .utils import truncate_ms
1620
1721
1822class MethodTests (SimpleTestCase ):
@@ -38,10 +42,6 @@ def test_validate(self):
3842
3943
4044class ModelTests (TestCase ):
41- def truncate_ms (self , value ):
42- """Truncate microseconds to milliseconds as supported by MongoDB."""
43- return value .replace (microsecond = (value .microsecond // 1000 ) * 1000 )
44-
4545 def test_save_load (self ):
4646 Holder .objects .create (data = Data (integer = "5" ))
4747 obj = Holder .objects .get ()
@@ -64,12 +64,12 @@ def test_save_load_null(self):
6464 def test_pre_save (self ):
6565 """Field.pre_save() is called on embedded model fields."""
6666 obj = Holder .objects .create (data = Data ())
67- auto_now = self . truncate_ms (obj .data .auto_now )
68- auto_now_add = self . truncate_ms (obj .data .auto_now_add )
67+ auto_now = truncate_ms (obj .data .auto_now )
68+ auto_now_add = truncate_ms (obj .data .auto_now_add )
6969 self .assertEqual (auto_now , auto_now_add )
7070 # save() updates auto_now but not auto_now_add.
7171 obj .save ()
72- self .assertEqual (self . truncate_ms (obj .data .auto_now_add ), auto_now_add )
72+ self .assertEqual (truncate_ms (obj .data .auto_now_add ), auto_now_add )
7373 auto_now_two = obj .data .auto_now
7474 self .assertGreater (auto_now_two , obj .data .auto_now_add )
7575 # And again, save() updates auto_now but not auto_now_add.
@@ -99,13 +99,89 @@ def test_gt(self):
9999 def test_gte (self ):
100100 self .assertCountEqual (Holder .objects .filter (data__integer__gte = 3 ), self .objs [3 :])
101101
102+ def test_order_by_embedded_field (self ):
103+ qs = Holder .objects .filter (data__integer__gt = 3 ).order_by ("-data__integer" )
104+ self .assertSequenceEqual (qs , list (reversed (self .objs [4 :])))
105+
106+ def test_order_and_group_by_embedded_field (self ):
107+ # Create and sort test data by `data__integer`.
108+ expected_objs = sorted (
109+ (Holder .objects .create (data = Data (integer = x )) for x in range (6 )),
110+ key = lambda x : x .data .integer ,
111+ )
112+ # Group by `data__integer + 5` and get the latest `data__auto_now`
113+ # datetime.
114+ qs = (
115+ Holder .objects .annotate (
116+ group = ExpressionWrapper (F ("data__integer" ) + 5 , output_field = models .IntegerField ()),
117+ )
118+ .values ("group" )
119+ .annotate (max_auto_now = Max ("data__auto_now" ))
120+ .order_by ("data__integer" )
121+ )
122+ # Each unique `data__integer` is correctly grouped and annotated.
123+ self .assertSequenceEqual (
124+ [{** e , "max_auto_now" : e ["max_auto_now" ]} for e in qs ],
125+ [
126+ {"group" : e .data .integer + 5 , "max_auto_now" : truncate_ms (e .data .auto_now )}
127+ for e in expected_objs
128+ ],
129+ )
130+
131+ def test_order_and_group_by_embedded_field_annotation (self ):
132+ # Create repeated `data__integer` values.
133+ [Holder .objects .create (data = Data (integer = x )) for x in range (6 )]
134+ # Group by `data__integer` and compute the sum of occurrences.
135+ qs = (
136+ Holder .objects .values ("data__integer" )
137+ .annotate (sum = Sum ("data__integer" ))
138+ .order_by ("sum" )
139+ )
140+ # The sum is twice the integer values since each appears twice.
141+ self .assertQuerySetEqual (qs , [0 , 2 , 4 , 6 , 8 , 10 ], operator .itemgetter ("sum" ))
142+
102143 def test_nested (self ):
103144 obj = Book .objects .create (
104145 author = Author (name = "Shakespeare" , age = 55 , address = Address (city = "NYC" , state = "NY" ))
105146 )
106147 self .assertCountEqual (Book .objects .filter (author__address__city = "NYC" ), [obj ])
107148
108149
150+ class InvalidLookupTests (SimpleTestCase ):
151+ def test_invalid_field (self ):
152+ msg = "Author has no field named 'first_name'"
153+ with self .assertRaisesMessage (FieldDoesNotExist , msg ):
154+ Book .objects .filter (author__first_name = "Bob" )
155+
156+ def test_invalid_field_nested (self ):
157+ msg = "Address has no field named 'floor'"
158+ with self .assertRaisesMessage (FieldDoesNotExist , msg ):
159+ Book .objects .filter (author__address__floor = "NYC" )
160+
161+ def test_invalid_lookup (self ):
162+ msg = "Unsupported lookup 'foo' for CharField 'city'."
163+ with self .assertRaisesMessage (FieldDoesNotExist , msg ):
164+ Book .objects .filter (author__address__city__foo = "NYC" )
165+
166+ def test_invalid_lookup_with_suggestions (self ):
167+ msg = (
168+ "Unsupported lookup '{lookup}' for CharField 'name', "
169+ "perhaps you meant {suggested_lookups}?"
170+ )
171+ with self .assertRaisesMessage (
172+ FieldDoesNotExist , msg .format (lookup = "exactly" , suggested_lookups = "exact or iexact" )
173+ ):
174+ Book .objects .filter (author__name__exactly = "NYC" )
175+ with self .assertRaisesMessage (
176+ FieldDoesNotExist , msg .format (lookup = "gti" , suggested_lookups = "gt or gte" )
177+ ):
178+ Book .objects .filter (author__name__gti = "NYC" )
179+ with self .assertRaisesMessage (
180+ FieldDoesNotExist , msg .format (lookup = "is_null" , suggested_lookups = "isnull" )
181+ ):
182+ Book .objects .filter (author__name__is_null = "NYC" )
183+
184+
109185@isolate_apps ("model_fields_" )
110186class CheckTests (SimpleTestCase ):
111187 def test_no_relational_fields (self ):
0 commit comments