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
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1"""
2"Rel objects" for related fields.
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.
8They also act as reverse fields for the purposes of the Meta API because
9they're the closest concept currently available.
10"""
12from django.core import exceptions
13from django.utils.functional import cached_property
14from django.utils.hashable import make_hashable
16from . import BLANK_CHOICE_DASH
17from .mixins import FieldCacheMixin
20class ForeignObjectRel(FieldCacheMixin):
21 """
22 Used by ForeignObject to store information about the relation.
24 ``_meta.get_fields()`` returns this class to provide access to the field
25 flags for the reverse relation.
26 """
28 # Field flags
29 auto_created = True
30 concrete = False
31 editable = False
32 is_relation = True
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
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
57 self.symmetrical = False
58 self.multiple = True
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()
68 @cached_property
69 def name(self):
70 return self.field.related_query_name()
72 @property
73 def remote_field(self):
74 return self.field
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]
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
98 @cached_property
99 def many_to_many(self):
100 return self.field.many_to_many
102 @cached_property
103 def many_to_one(self):
104 return self.field.one_to_many
106 @cached_property
107 def one_to_many(self):
108 return self.field.many_to_one
110 @cached_property
111 def one_to_one(self):
112 return self.field.one_to_one
114 def get_lookup(self, lookup_name):
115 return self.field.get_lookup(lookup_name)
117 def get_internal_type(self):
118 return self.field.get_internal_type()
120 @property
121 def db_type(self):
122 return self.field.db_type
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 )
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 )
145 def __eq__(self, other):
146 if not isinstance(other, self.__class__):
147 return NotImplemented
148 return self.identity == other.identity
150 def __hash__(self):
151 return hash(self.identity)
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
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.
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]
185 def is_hidden(self):
186 """Should the related object be hidden?"""
187 return bool(self.related_name) and self.related_name[-1] == "+"
189 def get_joining_columns(self):
190 return self.field.get_reverse_joining_columns()
192 def get_extra_restriction(self, alias, related_alias):
193 return self.field.get_extra_restriction(related_alias, alias)
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
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 "")
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
229 @cached_property
230 def path_infos(self):
231 return self.get_path_info()
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()
241class ManyToOneRel(ForeignObjectRel):
242 """
243 Used by the ForeignKey field to store information about the relation.
245 ``_meta.get_fields()`` returns this class to provide access to the field
246 flags for the reverse relation.
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 """
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 )
277 self.field_name = field_name
279 def __getstate__(self):
280 state = super().__getstate__()
281 state.pop("related_model", None)
282 return state
284 @property
285 def identity(self):
286 return super().identity + (self.field_name,)
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
299 def set_field_name(self):
300 self.field_name = self.field_name or self.model._meta.pk.name
303class OneToOneRel(ManyToOneRel):
304 """
305 Used by OneToOneField to store information about the relation.
307 ``_meta.get_fields()`` returns this class to provide access to the field
308 flags for the reverse relation.
309 """
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 )
333 self.multiple = False
336class ManyToManyRel(ForeignObjectRel):
337 """
338 Used by ManyToManyField to store information about the relation.
340 ``_meta.get_fields()`` returns this class to provide access to the field
341 flags for the reverse relation.
342 """
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 )
364 if through and not db_constraint:
365 raise ValueError("Can't supply a through model and db_constraint=False")
366 self.through = through
368 if through_fields and not through:
369 raise ValueError("Cannot specify through_fields without a through model")
370 self.through_fields = through_fields
372 self.symmetrical = symmetrical
373 self.db_constraint = db_constraint
375 @property
376 def identity(self):
377 return super().identity + (
378 self.through,
379 make_hashable(self.through_fields),
380 self.db_constraint,
381 )
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]