Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/db/models/fields/related_lookups.py: 31%
96 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1import warnings
3from django.db.models.lookups import (
4 Exact,
5 GreaterThan,
6 GreaterThanOrEqual,
7 In,
8 IsNull,
9 LessThan,
10 LessThanOrEqual,
11)
12from django.utils.deprecation import RemovedInDjango50Warning
15class MultiColSource:
16 contains_aggregate = False
17 contains_over_clause = False
19 def __init__(self, alias, targets, sources, field):
20 self.targets, self.sources, self.field, self.alias = (
21 targets,
22 sources,
23 field,
24 alias,
25 )
26 self.output_field = self.field
28 def __repr__(self):
29 return "{}({}, {})".format(self.__class__.__name__, self.alias, self.field)
31 def relabeled_clone(self, relabels):
32 return self.__class__(
33 relabels.get(self.alias, self.alias), self.targets, self.sources, self.field
34 )
36 def get_lookup(self, lookup):
37 return self.output_field.get_lookup(lookup)
39 def resolve_expression(self, *args, **kwargs):
40 return self
43def get_normalized_value(value, lhs):
44 from django.db.models import Model
46 if isinstance(value, Model):
47 if value.pk is None:
48 # When the deprecation ends, replace with:
49 # raise ValueError(
50 # "Model instances passed to related filters must be saved."
51 # )
52 warnings.warn(
53 "Passing unsaved model instances to related filters is deprecated.",
54 RemovedInDjango50Warning,
55 )
56 value_list = []
57 sources = lhs.output_field.path_infos[-1].target_fields
58 for source in sources:
59 while not isinstance(value, source.model) and source.remote_field:
60 source = source.remote_field.model._meta.get_field(
61 source.remote_field.field_name
62 )
63 try:
64 value_list.append(getattr(value, source.attname))
65 except AttributeError:
66 # A case like Restaurant.objects.filter(place=restaurant_instance),
67 # where place is a OneToOneField and the primary key of Restaurant.
68 return (value.pk,)
69 return tuple(value_list)
70 if not isinstance(value, tuple):
71 return (value,)
72 return value
75class RelatedIn(In):
76 def get_prep_lookup(self):
77 if not isinstance(self.lhs, MultiColSource):
78 if self.rhs_is_direct_value():
79 # If we get here, we are dealing with single-column relations.
80 self.rhs = [get_normalized_value(val, self.lhs)[0] for val in self.rhs]
81 # We need to run the related field's get_prep_value(). Consider
82 # case ForeignKey to IntegerField given value 'abc'. The
83 # ForeignKey itself doesn't have validation for non-integers,
84 # so we must run validation using the target field.
85 if hasattr(self.lhs.output_field, "path_infos"):
86 # Run the target field's get_prep_value. We can safely
87 # assume there is only one as we don't get to the direct
88 # value branch otherwise.
89 target_field = self.lhs.output_field.path_infos[-1].target_fields[
90 -1
91 ]
92 self.rhs = [target_field.get_prep_value(v) for v in self.rhs]
93 elif not getattr(self.rhs, "has_select_fields", True) and not getattr(
94 self.lhs.field.target_field, "primary_key", False
95 ):
96 if (
97 getattr(self.lhs.output_field, "primary_key", False)
98 and self.lhs.output_field.model == self.rhs.model
99 ):
100 # A case like
101 # Restaurant.objects.filter(place__in=restaurant_qs), where
102 # place is a OneToOneField and the primary key of
103 # Restaurant.
104 target_field = self.lhs.field.name
105 else:
106 target_field = self.lhs.field.target_field.name
107 self.rhs.set_values([target_field])
108 return super().get_prep_lookup()
110 def as_sql(self, compiler, connection):
111 if isinstance(self.lhs, MultiColSource):
112 # For multicolumn lookups we need to build a multicolumn where clause.
113 # This clause is either a SubqueryConstraint (for values that need
114 # to be compiled to SQL) or an OR-combined list of
115 # (col1 = val1 AND col2 = val2 AND ...) clauses.
116 from django.db.models.sql.where import (
117 AND,
118 OR,
119 SubqueryConstraint,
120 WhereNode,
121 )
123 root_constraint = WhereNode(connector=OR)
124 if self.rhs_is_direct_value():
125 values = [get_normalized_value(value, self.lhs) for value in self.rhs]
126 for value in values:
127 value_constraint = WhereNode()
128 for source, target, val in zip(
129 self.lhs.sources, self.lhs.targets, value
130 ):
131 lookup_class = target.get_lookup("exact")
132 lookup = lookup_class(
133 target.get_col(self.lhs.alias, source), val
134 )
135 value_constraint.add(lookup, AND)
136 root_constraint.add(value_constraint, OR)
137 else:
138 root_constraint.add(
139 SubqueryConstraint(
140 self.lhs.alias,
141 [target.column for target in self.lhs.targets],
142 [source.name for source in self.lhs.sources],
143 self.rhs,
144 ),
145 AND,
146 )
147 return root_constraint.as_sql(compiler, connection)
148 return super().as_sql(compiler, connection)
151class RelatedLookupMixin:
152 def get_prep_lookup(self):
153 if not isinstance(self.lhs, MultiColSource) and not hasattr(
154 self.rhs, "resolve_expression"
155 ):
156 # If we get here, we are dealing with single-column relations.
157 self.rhs = get_normalized_value(self.rhs, self.lhs)[0]
158 # We need to run the related field's get_prep_value(). Consider case
159 # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself
160 # doesn't have validation for non-integers, so we must run validation
161 # using the target field.
162 if self.prepare_rhs and hasattr(self.lhs.output_field, "path_infos"):
163 # Get the target field. We can safely assume there is only one
164 # as we don't get to the direct value branch otherwise.
165 target_field = self.lhs.output_field.path_infos[-1].target_fields[-1]
166 self.rhs = target_field.get_prep_value(self.rhs)
168 return super().get_prep_lookup()
170 def as_sql(self, compiler, connection):
171 if isinstance(self.lhs, MultiColSource):
172 assert self.rhs_is_direct_value()
173 self.rhs = get_normalized_value(self.rhs, self.lhs)
174 from django.db.models.sql.where import AND, WhereNode
176 root_constraint = WhereNode()
177 for target, source, val in zip(
178 self.lhs.targets, self.lhs.sources, self.rhs
179 ):
180 lookup_class = target.get_lookup(self.lookup_name)
181 root_constraint.add(
182 lookup_class(target.get_col(self.lhs.alias, source), val), AND
183 )
184 return root_constraint.as_sql(compiler, connection)
185 return super().as_sql(compiler, connection)
188class RelatedExact(RelatedLookupMixin, Exact):
189 pass
192class RelatedLessThan(RelatedLookupMixin, LessThan):
193 pass
196class RelatedGreaterThan(RelatedLookupMixin, GreaterThan):
197 pass
200class RelatedGreaterThanOrEqual(RelatedLookupMixin, GreaterThanOrEqual):
201 pass
204class RelatedLessThanOrEqual(RelatedLookupMixin, LessThanOrEqual):
205 pass
208class RelatedIsNull(RelatedLookupMixin, IsNull):
209 pass