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

153 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

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.path_infos[-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: 

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 __getstate__(self): 

154 state = self.__dict__.copy() 

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

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

157 # removed because subclasses like ManyToOneRel may have a PathInfo 

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

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

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

161 # delete. 

162 state.pop("path_infos", None) 

163 return state 

164 

165 def get_choices( 

166 self, 

167 include_blank=True, 

168 blank_choice=BLANK_CHOICE_DASH, 

169 limit_choices_to=None, 

170 ordering=(), 

171 ): 

172 """ 

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

174 as <select> choices for this field. 

175 

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

177 initially for utilization by RelatedFieldListFilter. 

178 """ 

179 limit_choices_to = limit_choices_to or self.limit_choices_to 

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

181 if ordering: 

182 qs = qs.order_by(*ordering) 

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

184 

185 def is_hidden(self): 

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

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

188 

189 def get_joining_columns(self): 

190 return self.field.get_reverse_joining_columns() 

191 

192 def get_extra_restriction(self, alias, related_alias): 

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

194 

195 def set_field_name(self): 

196 """ 

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

198 of app loading, so set_field_name is called from 

199 set_attributes_from_rel() 

200 """ 

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

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

203 self.field_name = None 

204 

205 def get_accessor_name(self, model=None): 

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

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

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

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

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

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

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

213 model = model or self.related_model 

214 if self.multiple: 

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

216 # reverse accessor. 

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

218 return None 

219 if self.related_name: 

220 return self.related_name 

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

222 

223 def get_path_info(self, filtered_relation=None): 

224 if filtered_relation: 

225 return self.field.get_reverse_path_info(filtered_relation) 

226 else: 

227 return self.field.reverse_path_infos 

228 

229 @cached_property 

230 def path_infos(self): 

231 return self.get_path_info() 

232 

233 def get_cache_name(self): 

234 """ 

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

236 forward model on the reverse model. 

237 """ 

238 return self.get_accessor_name() 

239 

240 

241class ManyToOneRel(ForeignObjectRel): 

242 """ 

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

244 

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

246 flags for the reverse relation. 

247 

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

249 fields we get the funny situation where 

250 ``ManyToOneRel.many_to_one == False`` and 

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

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

253 reverse relations into actual fields. 

254 """ 

255 

256 def __init__( 

257 self, 

258 field, 

259 to, 

260 field_name, 

261 related_name=None, 

262 related_query_name=None, 

263 limit_choices_to=None, 

264 parent_link=False, 

265 on_delete=None, 

266 ): 

267 super().__init__( 

268 field, 

269 to, 

270 related_name=related_name, 

271 related_query_name=related_query_name, 

272 limit_choices_to=limit_choices_to, 

273 parent_link=parent_link, 

274 on_delete=on_delete, 

275 ) 

276 

277 self.field_name = field_name 

278 

279 def __getstate__(self): 

280 state = super().__getstate__() 

281 state.pop("related_model", None) 

282 return state 

283 

284 @property 

285 def identity(self): 

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

287 

288 def get_related_field(self): 

289 """ 

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

291 """ 

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

293 if not field.concrete: 

294 raise exceptions.FieldDoesNotExist( 

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

296 ) 

297 return field 

298 

299 def set_field_name(self): 

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

301 

302 

303class OneToOneRel(ManyToOneRel): 

304 """ 

305 Used by OneToOneField to store information about the relation. 

306 

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

308 flags for the reverse relation. 

309 """ 

310 

311 def __init__( 

312 self, 

313 field, 

314 to, 

315 field_name, 

316 related_name=None, 

317 related_query_name=None, 

318 limit_choices_to=None, 

319 parent_link=False, 

320 on_delete=None, 

321 ): 

322 super().__init__( 

323 field, 

324 to, 

325 field_name, 

326 related_name=related_name, 

327 related_query_name=related_query_name, 

328 limit_choices_to=limit_choices_to, 

329 parent_link=parent_link, 

330 on_delete=on_delete, 

331 ) 

332 

333 self.multiple = False 

334 

335 

336class ManyToManyRel(ForeignObjectRel): 

337 """ 

338 Used by ManyToManyField to store information about the relation. 

339 

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

341 flags for the reverse relation. 

342 """ 

343 

344 def __init__( 

345 self, 

346 field, 

347 to, 

348 related_name=None, 

349 related_query_name=None, 

350 limit_choices_to=None, 

351 symmetrical=True, 

352 through=None, 

353 through_fields=None, 

354 db_constraint=True, 

355 ): 

356 super().__init__( 

357 field, 

358 to, 

359 related_name=related_name, 

360 related_query_name=related_query_name, 

361 limit_choices_to=limit_choices_to, 

362 ) 

363 

364 if through and not db_constraint: 

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

366 self.through = through 

367 

368 if through_fields and not through: 

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

370 self.through_fields = through_fields 

371 

372 self.symmetrical = symmetrical 

373 self.db_constraint = db_constraint 

374 

375 @property 

376 def identity(self): 

377 return super().identity + ( 

378 self.through, 

379 make_hashable(self.through_fields), 

380 self.db_constraint, 

381 ) 

382 

383 def get_related_field(self): 

384 """ 

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

386 Provided for symmetry with ManyToOneRel. 

387 """ 

388 opts = self.through._meta 

389 if self.through_fields: 

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

391 else: 

392 for field in opts.fields: 

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

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

395 break 

396 return field.foreign_related_fields[0]