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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
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.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]
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
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 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.
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]
173 def is_hidden(self):
174 """Should the related object be hidden?"""
175 return bool(self.related_name) and self.related_name[-1] == "+"
177 def get_joining_columns(self):
178 return self.field.get_reverse_joining_columns()
180 def get_extra_restriction(self, alias, related_alias):
181 return self.field.get_extra_restriction(related_alias, alias)
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
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 "")
211 def get_path_info(self, filtered_relation=None):
212 return self.field.get_reverse_path_info(filtered_relation)
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()
222class ManyToOneRel(ForeignObjectRel):
223 """
224 Used by the ForeignKey field to store information about the relation.
226 ``_meta.get_fields()`` returns this class to provide access to the field
227 flags for the reverse relation.
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 """
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 )
258 self.field_name = field_name
260 def __getstate__(self):
261 state = self.__dict__.copy()
262 state.pop("related_model", None)
263 return state
265 @property
266 def identity(self):
267 return super().identity + (self.field_name,)
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
280 def set_field_name(self):
281 self.field_name = self.field_name or self.model._meta.pk.name
284class OneToOneRel(ManyToOneRel):
285 """
286 Used by OneToOneField to store information about the relation.
288 ``_meta.get_fields()`` returns this class to provide access to the field
289 flags for the reverse relation.
290 """
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 )
314 self.multiple = False
317class ManyToManyRel(ForeignObjectRel):
318 """
319 Used by ManyToManyField to store information about the relation.
321 ``_meta.get_fields()`` returns this class to provide access to the field
322 flags for the reverse relation.
323 """
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 )
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
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
353 self.symmetrical = symmetrical
354 self.db_constraint = db_constraint
356 @property
357 def identity(self):
358 return super().identity + (
359 self.through,
360 make_hashable(self.through_fields),
361 self.db_constraint,
362 )
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]