8
8
from .. import forms
9
9
from ..query_utils import process_lhs , process_rhs
10
10
from . import EmbeddedModelField
11
- from .array import ArrayField
11
+ from .array import ArrayField , ArrayLenTransform
12
12
13
13
14
14
class EmbeddedModelArrayField (ArrayField ):
15
- ALLOWED_LOOKUPS = {
16
- "in" ,
17
- "exact" ,
18
- "iexact" ,
19
- "gt" ,
20
- "gte" ,
21
- "lt" ,
22
- "lte" ,
23
- "all" ,
24
- "contained_by" ,
25
- }
26
-
27
15
def __init__ (self , embedded_model , ** kwargs ):
28
16
if "size" in kwargs :
29
17
raise ValueError ("EmbeddedModelArrayField does not support size." )
@@ -69,18 +57,50 @@ def get_transform(self, name):
69
57
return transform
70
58
return KeyTransformFactory (name , self )
71
59
60
+ def _get_lookup (self , lookup_name ):
61
+ lookup = super ()._get_lookup (lookup_name )
62
+ if lookup is None or lookup is ArrayLenTransform :
63
+ return lookup
64
+
65
+ class EmbeddedModelArrayFieldLookups (Lookup ):
66
+ def as_mql (self , compiler , connection ):
67
+ raise ValueError (
68
+ "Cannot apply this lookup directly to EmbeddedModelArrayField. "
69
+ "Try querying one of its embedded fields instead."
70
+ )
71
+
72
+ return EmbeddedModelArrayFieldLookups
73
+
74
+
75
+ class _EmbeddedModelArrayOutputField (ArrayField ):
76
+ """
77
+ Represents the output of an EmbeddedModelArrayField when traversed in a query path.
78
+
79
+ This field is not meant to be used directly in model definitions. It exists solely to
80
+ support query output resolution; when an EmbeddedModelArrayField is accessed in a query,
81
+ the result should behave like an array of the embedded model's target type.
82
+
83
+ While it mimics ArrayField's lookups behavior, the way those lookups are resolved
84
+ follows the semantics of EmbeddedModelArrayField rather than native array behavior.
85
+ """
86
+
87
+ ALLOWED_LOOKUPS = {
88
+ "in" ,
89
+ "exact" ,
90
+ "iexact" ,
91
+ "gt" ,
92
+ "gte" ,
93
+ "lt" ,
94
+ "lte" ,
95
+ "all" ,
96
+ "contained_by" ,
97
+ }
98
+
72
99
def get_lookup (self , name ):
73
100
return super ().get_lookup (name ) if name in self .ALLOWED_LOOKUPS else None
74
101
75
102
76
103
class EmbeddedModelArrayFieldBuiltinLookup (Lookup ):
77
- def check_lhs (self ):
78
- if not isinstance (self .lhs , KeyTransform ):
79
- raise ValueError (
80
- "Cannot apply this lookup directly to EmbeddedModelArrayField. "
81
- "Try querying one of its embedded fields instead."
82
- )
83
-
84
104
def process_rhs (self , compiler , connection ):
85
105
value = self .rhs
86
106
if not self .get_db_prep_lookup_value_is_iterable :
@@ -95,111 +115,81 @@ def process_rhs(self, compiler, connection):
95
115
]
96
116
97
117
def as_mql (self , compiler , connection ):
98
- self .check_lhs ()
99
118
# Querying a subfield within the array elements (via nested KeyTransform).
100
119
# Replicates MongoDB's implicit ANY-match by mapping over the array and applying
101
120
# `$in` on the subfield.
102
- lhs_mql , inner_lhs_mql = process_lhs (self , compiler , connection )
121
+ lhs_mql = process_lhs (self , compiler , connection )
122
+ inner_lhs_mql = lhs_mql ["$ifNull" ][0 ]["$map" ]["in" ]
103
123
values = process_rhs (self , compiler , connection )
104
- return {
105
- "$anyElementTrue" : {
106
- "$ifNull" : [
107
- {
108
- "$map" : {
109
- "input" : lhs_mql ,
110
- "as" : "item" ,
111
- "in" : connection .mongo_operators [self .lookup_name ](
112
- inner_lhs_mql , values
113
- ),
114
- }
115
- },
116
- [],
117
- ]
118
- }
119
- }
124
+ lhs_mql ["$ifNull" ][0 ]["$map" ]["in" ] = connection .mongo_operators [self .lookup_name ](
125
+ inner_lhs_mql , values
126
+ )
127
+ return {"$anyElementTrue" : lhs_mql }
120
128
121
129
122
- @EmbeddedModelArrayField .register_lookup
130
+ @_EmbeddedModelArrayOutputField .register_lookup
123
131
class EmbeddedModelArrayFieldIn (EmbeddedModelArrayFieldBuiltinLookup , lookups .In ):
124
132
pass
125
133
126
134
127
- @EmbeddedModelArrayField .register_lookup
135
+ @_EmbeddedModelArrayOutputField .register_lookup
128
136
class EmbeddedModelArrayFieldExact (EmbeddedModelArrayFieldBuiltinLookup , lookups .Exact ):
129
137
pass
130
138
131
139
132
- @EmbeddedModelArrayField .register_lookup
140
+ @_EmbeddedModelArrayOutputField .register_lookup
133
141
class EmbeddedModelArrayFieldIExact (EmbeddedModelArrayFieldBuiltinLookup , lookups .IExact ):
134
142
get_db_prep_lookup_value_is_iterable = False
135
143
136
144
137
- @EmbeddedModelArrayField .register_lookup
145
+ @_EmbeddedModelArrayOutputField .register_lookup
138
146
class EmbeddedModelArrayFieldGreaterThan (EmbeddedModelArrayFieldBuiltinLookup , lookups .GreaterThan ):
139
147
pass
140
148
141
149
142
- @EmbeddedModelArrayField .register_lookup
150
+ @_EmbeddedModelArrayOutputField .register_lookup
143
151
class EmbeddedModelArrayFieldGreaterThanOrEqual (
144
152
EmbeddedModelArrayFieldBuiltinLookup , lookups .GreaterThanOrEqual
145
153
):
146
154
pass
147
155
148
156
149
- @EmbeddedModelArrayField .register_lookup
157
+ @_EmbeddedModelArrayOutputField .register_lookup
150
158
class EmbeddedModelArrayFieldLessThan (EmbeddedModelArrayFieldBuiltinLookup , lookups .LessThan ):
151
159
pass
152
160
153
161
154
- @EmbeddedModelArrayField .register_lookup
162
+ @_EmbeddedModelArrayOutputField .register_lookup
155
163
class EmbeddedModelArrayFieldLessThanOrEqual (
156
164
EmbeddedModelArrayFieldBuiltinLookup , lookups .LessThanOrEqual
157
165
):
158
166
pass
159
167
160
168
161
- @EmbeddedModelArrayField .register_lookup
169
+ @_EmbeddedModelArrayOutputField .register_lookup
162
170
class EmbeddedModelArrayFieldAll (EmbeddedModelArrayFieldBuiltinLookup , Lookup ):
163
171
lookup_name = "all"
164
172
get_db_prep_lookup_value_is_iterable = False
165
173
166
174
def as_mql (self , compiler , connection ):
167
- self .check_lhs ()
168
- lhs_mql , inner_lhs_mql = process_lhs (self , compiler , connection )
175
+ lhs_mql = process_lhs (self , compiler , connection )
169
176
values = process_rhs (self , compiler , connection )
170
177
return {
171
- "$setIsSubset" : [
172
- values ,
173
- {
174
- "$ifNull" : [
175
- {
176
- "$map" : {
177
- "input" : lhs_mql ,
178
- "as" : "item" ,
179
- "in" : inner_lhs_mql ,
180
- }
181
- },
182
- [],
183
- ]
184
- },
178
+ "$and" : [
179
+ {"$ne" : [lhs_mql , None ]},
180
+ {"$ne" : [values , None ]},
181
+ {"$setIsSubset" : [values , lhs_mql ]},
185
182
]
186
183
}
187
184
188
185
189
- @EmbeddedModelArrayField .register_lookup
186
+ @_EmbeddedModelArrayOutputField .register_lookup
190
187
class ArrayContainedBy (EmbeddedModelArrayFieldBuiltinLookup , Lookup ):
191
188
lookup_name = "contained_by"
192
189
get_db_prep_lookup_value_is_iterable = False
193
190
194
191
def as_mql (self , compiler , connection ):
195
- lhs_mql , inner_lhs_mql = process_lhs (self , compiler , connection )
196
- lhs_mql = {
197
- "$map" : {
198
- "input" : lhs_mql ,
199
- "as" : "item" ,
200
- "in" : inner_lhs_mql ,
201
- }
202
- }
192
+ lhs_mql = process_lhs (self , compiler , connection )
203
193
value = process_rhs (self , compiler , connection )
204
194
return {
205
195
"$and" : [
@@ -244,7 +234,7 @@ def get_transform(self, name):
244
234
self ._sub_transform = transform
245
235
return self
246
236
output_field = self ._lhs .output_field
247
- allowed_lookups = self .array_field .ALLOWED_LOOKUPS .intersection (
237
+ allowed_lookups = self .output_field .ALLOWED_LOOKUPS .intersection (
248
238
set (output_field .get_lookups ())
249
239
)
250
240
suggested_lookups = difflib .get_close_matches (name , allowed_lookups )
@@ -262,11 +252,22 @@ def get_transform(self, name):
262
252
def as_mql (self , compiler , connection ):
263
253
inner_lhs_mql = self ._lhs .as_mql (compiler , connection )
264
254
lhs_mql = process_lhs (self , compiler , connection )
265
- return lhs_mql , inner_lhs_mql
255
+ return {
256
+ "$ifNull" : [
257
+ {
258
+ "$map" : {
259
+ "input" : lhs_mql ,
260
+ "as" : "item" ,
261
+ "in" : inner_lhs_mql ,
262
+ }
263
+ },
264
+ [],
265
+ ]
266
+ }
266
267
267
268
@property
268
269
def output_field (self ):
269
- return self .array_field
270
+ return _EmbeddedModelArrayOutputField ( self ._lhs . output_field )
270
271
271
272
272
273
class KeyTransformFactory :
0 commit comments