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

1import warnings 

2 

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 

13 

14 

15class MultiColSource: 

16 contains_aggregate = False 

17 contains_over_clause = False 

18 

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 

27 

28 def __repr__(self): 

29 return "{}({}, {})".format(self.__class__.__name__, self.alias, self.field) 

30 

31 def relabeled_clone(self, relabels): 

32 return self.__class__( 

33 relabels.get(self.alias, self.alias), self.targets, self.sources, self.field 

34 ) 

35 

36 def get_lookup(self, lookup): 

37 return self.output_field.get_lookup(lookup) 

38 

39 def resolve_expression(self, *args, **kwargs): 

40 return self 

41 

42 

43def get_normalized_value(value, lhs): 

44 from django.db.models import Model 

45 

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 

73 

74 

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

109 

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 ) 

122 

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) 

149 

150 

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) 

167 

168 return super().get_prep_lookup() 

169 

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 

175 

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) 

186 

187 

188class RelatedExact(RelatedLookupMixin, Exact): 

189 pass 

190 

191 

192class RelatedLessThan(RelatedLookupMixin, LessThan): 

193 pass 

194 

195 

196class RelatedGreaterThan(RelatedLookupMixin, GreaterThan): 

197 pass 

198 

199 

200class RelatedGreaterThanOrEqual(RelatedLookupMixin, GreaterThanOrEqual): 

201 pass 

202 

203 

204class RelatedLessThanOrEqual(RelatedLookupMixin, LessThanOrEqual): 

205 pass 

206 

207 

208class RelatedIsNull(RelatedLookupMixin, IsNull): 

209 pass