Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/db/models/fields/reverse_related.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

159 statements  

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 """Should the related object be hidden?""" 

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

68 

69 @cached_property 

70 def name(self): 

71 return self.field.related_query_name() 

72 

73 @property 

74 def remote_field(self): 

75 return self.field 

76 

77 @property 

78 def target_field(self): 

79 """ 

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

81 model against which the filtering should happen. 

82 """ 

83 target_fields = self.path_infos[-1].target_fields 

84 if len(target_fields) > 1: 

85 raise exceptions.FieldError( 

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

87 ) 

88 return target_fields[0] 

89 

90 @cached_property 

91 def related_model(self): 

92 if not self.field.model: 

93 raise AttributeError( 

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

95 "has been called." 

96 ) 

97 return self.field.model 

98 

99 @cached_property 

100 def many_to_many(self): 

101 return self.field.many_to_many 

102 

103 @cached_property 

104 def many_to_one(self): 

105 return self.field.one_to_many 

106 

107 @cached_property 

108 def one_to_many(self): 

109 return self.field.many_to_one 

110 

111 @cached_property 

112 def one_to_one(self): 

113 return self.field.one_to_one 

114 

115 def get_lookup(self, lookup_name): 

116 return self.field.get_lookup(lookup_name) 

117 

118 def get_lookups(self): 

119 return self.field.get_lookups() 

120 

121 def get_transform(self, name): 

122 return self.field.get_transform(name) 

123 

124 def get_internal_type(self): 

125 return self.field.get_internal_type() 

126 

127 @property 

128 def db_type(self): 

129 return self.field.db_type 

130 

131 def __repr__(self): 

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

133 type(self).__name__, 

134 self.related_model._meta.app_label, 

135 self.related_model._meta.model_name, 

136 ) 

137 

138 @property 

139 def identity(self): 

140 return ( 

141 self.field, 

142 self.model, 

143 self.related_name, 

144 self.related_query_name, 

145 make_hashable(self.limit_choices_to), 

146 self.parent_link, 

147 self.on_delete, 

148 self.symmetrical, 

149 self.multiple, 

150 ) 

151 

152 def __eq__(self, other): 

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

154 return NotImplemented 

155 return self.identity == other.identity 

156 

157 def __hash__(self): 

158 return hash(self.identity) 

159 

160 def __getstate__(self): 

161 state = self.__dict__.copy() 

162 # Delete the path_infos cached property because it can be recalculated 

163 # at first invocation after deserialization. The attribute must be 

164 # removed because subclasses like ManyToOneRel may have a PathInfo 

165 # which contains an intermediate M2M table that's been dynamically 

166 # created and doesn't exist in the .models module. 

167 # This is a reverse relation, so there is no reverse_path_infos to 

168 # delete. 

169 state.pop("path_infos", None) 

170 return state 

171 

172 def get_choices( 

173 self, 

174 include_blank=True, 

175 blank_choice=BLANK_CHOICE_DASH, 

176 limit_choices_to=None, 

177 ordering=(), 

178 ): 

179 """ 

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

181 as <select> choices for this field. 

182 

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

184 initially for utilization by RelatedFieldListFilter. 

185 """ 

186 limit_choices_to = limit_choices_to or self.limit_choices_to 

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

188 if ordering: 

189 qs = qs.order_by(*ordering) 

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

191 

192 def get_joining_fields(self): 

193 return self.field.get_reverse_joining_fields() 

194 

195 def get_extra_restriction(self, alias, related_alias): 

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

197 

198 def set_field_name(self): 

199 """ 

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

201 of app loading, so set_field_name is called from 

202 set_attributes_from_rel() 

203 """ 

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

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

206 self.field_name = None 

207 

208 @cached_property 

209 def accessor_name(self): 

210 return self.get_accessor_name() 

211 

212 def get_accessor_name(self, model=None): 

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

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

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

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

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

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

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

220 model = model or self.related_model 

221 if self.multiple: 

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

223 # reverse accessor. 

224 if self.symmetrical and model == self.model: 

225 return None 

226 if self.related_name: 

227 return self.related_name 

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

229 

230 def get_path_info(self, filtered_relation=None): 

231 if filtered_relation: 

232 return self.field.get_reverse_path_info(filtered_relation) 

233 else: 

234 return self.field.reverse_path_infos 

235 

236 @cached_property 

237 def path_infos(self): 

238 return self.get_path_info() 

239 

240 @cached_property 

241 def cache_name(self): 

242 """ 

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

244 forward model on the reverse model. 

245 """ 

246 return self.accessor_name 

247 

248 

249class ManyToOneRel(ForeignObjectRel): 

250 """ 

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

252 

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

254 flags for the reverse relation. 

255 

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

257 fields we get the funny situation where 

258 ``ManyToOneRel.many_to_one == False`` and 

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

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

261 reverse relations into actual fields. 

262 """ 

263 

264 def __init__( 

265 self, 

266 field, 

267 to, 

268 field_name, 

269 related_name=None, 

270 related_query_name=None, 

271 limit_choices_to=None, 

272 parent_link=False, 

273 on_delete=None, 

274 ): 

275 super().__init__( 

276 field, 

277 to, 

278 related_name=related_name, 

279 related_query_name=related_query_name, 

280 limit_choices_to=limit_choices_to, 

281 parent_link=parent_link, 

282 on_delete=on_delete, 

283 ) 

284 

285 self.field_name = field_name 

286 

287 def __getstate__(self): 

288 state = super().__getstate__() 

289 state.pop("related_model", None) 

290 return state 

291 

292 @property 

293 def identity(self): 

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

295 

296 def get_related_field(self): 

297 """ 

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

299 """ 

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

301 if not field.concrete: 

302 raise exceptions.FieldDoesNotExist( 

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

304 ) 

305 return field 

306 

307 def set_field_name(self): 

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

309 

310 

311class OneToOneRel(ManyToOneRel): 

312 """ 

313 Used by OneToOneField to store information about the relation. 

314 

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

316 flags for the reverse relation. 

317 """ 

318 

319 def __init__( 

320 self, 

321 field, 

322 to, 

323 field_name, 

324 related_name=None, 

325 related_query_name=None, 

326 limit_choices_to=None, 

327 parent_link=False, 

328 on_delete=None, 

329 ): 

330 super().__init__( 

331 field, 

332 to, 

333 field_name, 

334 related_name=related_name, 

335 related_query_name=related_query_name, 

336 limit_choices_to=limit_choices_to, 

337 parent_link=parent_link, 

338 on_delete=on_delete, 

339 ) 

340 

341 self.multiple = False 

342 

343 

344class ManyToManyRel(ForeignObjectRel): 

345 """ 

346 Used by ManyToManyField to store information about the relation. 

347 

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

349 flags for the reverse relation. 

350 """ 

351 

352 def __init__( 

353 self, 

354 field, 

355 to, 

356 related_name=None, 

357 related_query_name=None, 

358 limit_choices_to=None, 

359 symmetrical=True, 

360 through=None, 

361 through_fields=None, 

362 db_constraint=True, 

363 ): 

364 super().__init__( 

365 field, 

366 to, 

367 related_name=related_name, 

368 related_query_name=related_query_name, 

369 limit_choices_to=limit_choices_to, 

370 ) 

371 

372 if through and not db_constraint: 

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

374 self.through = through 

375 

376 if through_fields and not through: 

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

378 self.through_fields = through_fields 

379 

380 self.symmetrical = symmetrical 

381 self.db_constraint = db_constraint 

382 

383 @property 

384 def identity(self): 

385 return super().identity + ( 

386 self.through, 

387 make_hashable(self.through_fields), 

388 self.db_constraint, 

389 ) 

390 

391 def get_related_field(self): 

392 """ 

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

394 Provided for symmetry with ManyToOneRel. 

395 """ 

396 opts = self.through._meta 

397 if self.through_fields: 

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

399 else: 

400 for field in opts.fields: 

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

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

403 break 

404 return field.foreign_related_fields[0]