Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/db/models/fields/reverse_related.py: 71%

144 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2"Rel objects" for related fields. 

3 

4"Rel objects" (for lack of a better name) carry information about the relation 

5modeled by a related field and provide some utility functions. They're stored 

6in the ``remote_field`` attribute of the field. 

7 

8They also act as reverse fields for the purposes of the Meta API because 

9they're the closest concept currently available. 

10""" 

11 

12from django.core import exceptions 

13from django.utils.functional import cached_property 

14from django.utils.hashable import make_hashable 

15 

16from . import BLANK_CHOICE_DASH 

17from .mixins import FieldCacheMixin 

18 

19 

20class ForeignObjectRel(FieldCacheMixin): 

21 """ 

22 Used by ForeignObject to store information about the relation. 

23 

24 ``_meta.get_fields()`` returns this class to provide access to the field 

25 flags for the reverse relation. 

26 """ 

27 

28 # Field flags 

29 auto_created = True 

30 concrete = False 

31 editable = False 

32 is_relation = True 

33 

34 # Reverse relations are always nullable (Django can't enforce that a 

35 # foreign key on the related model points to this model). 

36 null = True 

37 empty_strings_allowed = False 

38 

39 def __init__( 

40 self, 

41 field, 

42 to, 

43 related_name=None, 

44 related_query_name=None, 

45 limit_choices_to=None, 

46 parent_link=False, 

47 on_delete=None, 

48 ): 

49 self.field = field 

50 self.model = to 

51 self.related_name = related_name 

52 self.related_query_name = related_query_name 

53 self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to 

54 self.parent_link = parent_link 

55 self.on_delete = on_delete 

56 

57 self.symmetrical = False 

58 self.multiple = True 

59 

60 # Some of the following cached_properties can't be initialized in 

61 # __init__ as the field doesn't have its model yet. Calling these methods 

62 # before field.contribute_to_class() has been called will result in 

63 # AttributeError 

64 @cached_property 

65 def hidden(self): 

66 return self.is_hidden() 

67 

68 @cached_property 

69 def name(self): 

70 return self.field.related_query_name() 

71 

72 @property 

73 def remote_field(self): 

74 return self.field 

75 

76 @property 

77 def target_field(self): 

78 """ 

79 When filtering against this relation, return the field on the remote 

80 model against which the filtering should happen. 

81 """ 

82 target_fields = self.get_path_info()[-1].target_fields 

83 if len(target_fields) > 1: 

84 raise exceptions.FieldError( 

85 "Can't use target_field for multicolumn relations." 

86 ) 

87 return target_fields[0] 

88 

89 @cached_property 

90 def related_model(self): 

91 if not self.field.model: 91 ↛ 92line 91 didn't jump to line 92, because the condition on line 91 was never true

92 raise AttributeError( 

93 "This property can't be accessed before self.field.contribute_to_class " 

94 "has been called." 

95 ) 

96 return self.field.model 

97 

98 @cached_property 

99 def many_to_many(self): 

100 return self.field.many_to_many 

101 

102 @cached_property 

103 def many_to_one(self): 

104 return self.field.one_to_many 

105 

106 @cached_property 

107 def one_to_many(self): 

108 return self.field.many_to_one 

109 

110 @cached_property 

111 def one_to_one(self): 

112 return self.field.one_to_one 

113 

114 def get_lookup(self, lookup_name): 

115 return self.field.get_lookup(lookup_name) 

116 

117 def get_internal_type(self): 

118 return self.field.get_internal_type() 

119 

120 @property 

121 def db_type(self): 

122 return self.field.db_type 

123 

124 def __repr__(self): 

125 return "<%s: %s.%s>" % ( 

126 type(self).__name__, 

127 self.related_model._meta.app_label, 

128 self.related_model._meta.model_name, 

129 ) 

130 

131 @property 

132 def identity(self): 

133 return ( 

134 self.field, 

135 self.model, 

136 self.related_name, 

137 self.related_query_name, 

138 make_hashable(self.limit_choices_to), 

139 self.parent_link, 

140 self.on_delete, 

141 self.symmetrical, 

142 self.multiple, 

143 ) 

144 

145 def __eq__(self, other): 

146 if not isinstance(other, self.__class__): 

147 return NotImplemented 

148 return self.identity == other.identity 

149 

150 def __hash__(self): 

151 return hash(self.identity) 

152 

153 def get_choices( 

154 self, 

155 include_blank=True, 

156 blank_choice=BLANK_CHOICE_DASH, 

157 limit_choices_to=None, 

158 ordering=(), 

159 ): 

160 """ 

161 Return choices with a default blank choices included, for use 

162 as <select> choices for this field. 

163 

164 Analog of django.db.models.fields.Field.get_choices(), provided 

165 initially for utilization by RelatedFieldListFilter. 

166 """ 

167 limit_choices_to = limit_choices_to or self.limit_choices_to 

168 qs = self.related_model._default_manager.complex_filter(limit_choices_to) 

169 if ordering: 

170 qs = qs.order_by(*ordering) 

171 return (blank_choice if include_blank else []) + [(x.pk, str(x)) for x in qs] 

172 

173 def is_hidden(self): 

174 """Should the related object be hidden?""" 

175 return bool(self.related_name) and self.related_name[-1] == "+" 

176 

177 def get_joining_columns(self): 

178 return self.field.get_reverse_joining_columns() 

179 

180 def get_extra_restriction(self, alias, related_alias): 

181 return self.field.get_extra_restriction(related_alias, alias) 

182 

183 def set_field_name(self): 

184 """ 

185 Set the related field's name, this is not available until later stages 

186 of app loading, so set_field_name is called from 

187 set_attributes_from_rel() 

188 """ 

189 # By default foreign object doesn't relate to any remote field (for 

190 # example custom multicolumn joins currently have no remote field). 

191 self.field_name = None 

192 

193 def get_accessor_name(self, model=None): 

194 # This method encapsulates the logic that decides what name to give an 

195 # accessor descriptor that retrieves related many-to-one or 

196 # many-to-many objects. It uses the lowercased object_name + "_set", 

197 # but this can be overridden with the "related_name" option. Due to 

198 # backwards compatibility ModelForms need to be able to provide an 

199 # alternate model. See BaseInlineFormSet.get_default_prefix(). 

200 opts = model._meta if model else self.related_model._meta 

201 model = model or self.related_model 

202 if self.multiple: 

203 # If this is a symmetrical m2m relation on self, there is no 

204 # reverse accessor. 

205 if self.symmetrical and model == self.model: 205 ↛ 206line 205 didn't jump to line 206, because the condition on line 205 was never true

206 return None 

207 if self.related_name: 

208 return self.related_name 

209 return opts.model_name + ("_set" if self.multiple else "") 

210 

211 def get_path_info(self, filtered_relation=None): 

212 return self.field.get_reverse_path_info(filtered_relation) 

213 

214 def get_cache_name(self): 

215 """ 

216 Return the name of the cache key to use for storing an instance of the 

217 forward model on the reverse model. 

218 """ 

219 return self.get_accessor_name() 

220 

221 

222class ManyToOneRel(ForeignObjectRel): 

223 """ 

224 Used by the ForeignKey field to store information about the relation. 

225 

226 ``_meta.get_fields()`` returns this class to provide access to the field 

227 flags for the reverse relation. 

228 

229 Note: Because we somewhat abuse the Rel objects by using them as reverse 

230 fields we get the funny situation where 

231 ``ManyToOneRel.many_to_one == False`` and 

232 ``ManyToOneRel.one_to_many == True``. This is unfortunate but the actual 

233 ManyToOneRel class is a private API and there is work underway to turn 

234 reverse relations into actual fields. 

235 """ 

236 

237 def __init__( 

238 self, 

239 field, 

240 to, 

241 field_name, 

242 related_name=None, 

243 related_query_name=None, 

244 limit_choices_to=None, 

245 parent_link=False, 

246 on_delete=None, 

247 ): 

248 super().__init__( 

249 field, 

250 to, 

251 related_name=related_name, 

252 related_query_name=related_query_name, 

253 limit_choices_to=limit_choices_to, 

254 parent_link=parent_link, 

255 on_delete=on_delete, 

256 ) 

257 

258 self.field_name = field_name 

259 

260 def __getstate__(self): 

261 state = self.__dict__.copy() 

262 state.pop("related_model", None) 

263 return state 

264 

265 @property 

266 def identity(self): 

267 return super().identity + (self.field_name,) 

268 

269 def get_related_field(self): 

270 """ 

271 Return the Field in the 'to' object to which this relationship is tied. 

272 """ 

273 field = self.model._meta.get_field(self.field_name) 

274 if not field.concrete: 274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true

275 raise exceptions.FieldDoesNotExist( 

276 "No related field named '%s'" % self.field_name 

277 ) 

278 return field 

279 

280 def set_field_name(self): 

281 self.field_name = self.field_name or self.model._meta.pk.name 

282 

283 

284class OneToOneRel(ManyToOneRel): 

285 """ 

286 Used by OneToOneField to store information about the relation. 

287 

288 ``_meta.get_fields()`` returns this class to provide access to the field 

289 flags for the reverse relation. 

290 """ 

291 

292 def __init__( 

293 self, 

294 field, 

295 to, 

296 field_name, 

297 related_name=None, 

298 related_query_name=None, 

299 limit_choices_to=None, 

300 parent_link=False, 

301 on_delete=None, 

302 ): 

303 super().__init__( 

304 field, 

305 to, 

306 field_name, 

307 related_name=related_name, 

308 related_query_name=related_query_name, 

309 limit_choices_to=limit_choices_to, 

310 parent_link=parent_link, 

311 on_delete=on_delete, 

312 ) 

313 

314 self.multiple = False 

315 

316 

317class ManyToManyRel(ForeignObjectRel): 

318 """ 

319 Used by ManyToManyField to store information about the relation. 

320 

321 ``_meta.get_fields()`` returns this class to provide access to the field 

322 flags for the reverse relation. 

323 """ 

324 

325 def __init__( 

326 self, 

327 field, 

328 to, 

329 related_name=None, 

330 related_query_name=None, 

331 limit_choices_to=None, 

332 symmetrical=True, 

333 through=None, 

334 through_fields=None, 

335 db_constraint=True, 

336 ): 

337 super().__init__( 

338 field, 

339 to, 

340 related_name=related_name, 

341 related_query_name=related_query_name, 

342 limit_choices_to=limit_choices_to, 

343 ) 

344 

345 if through and not db_constraint: 345 ↛ 346line 345 didn't jump to line 346, because the condition on line 345 was never true

346 raise ValueError("Can't supply a through model and db_constraint=False") 

347 self.through = through 

348 

349 if through_fields and not through: 349 ↛ 350line 349 didn't jump to line 350, because the condition on line 349 was never true

350 raise ValueError("Cannot specify through_fields without a through model") 

351 self.through_fields = through_fields 

352 

353 self.symmetrical = symmetrical 

354 self.db_constraint = db_constraint 

355 

356 @property 

357 def identity(self): 

358 return super().identity + ( 

359 self.through, 

360 make_hashable(self.through_fields), 

361 self.db_constraint, 

362 ) 

363 

364 def get_related_field(self): 

365 """ 

366 Return the field in the 'to' object to which this relationship is tied. 

367 Provided for symmetry with ManyToOneRel. 

368 """ 

369 opts = self.through._meta 

370 if self.through_fields: 

371 field = opts.get_field(self.through_fields[0]) 

372 else: 

373 for field in opts.fields: 

374 rel = getattr(field, "remote_field", None) 

375 if rel and rel.model == self.model: 

376 break 

377 return field.foreign_related_fields[0]