summaryrefslogtreecommitdiff
path: root/django/db/models/fields/related.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/db/models/fields/related.py')
-rw-r--r--django/db/models/fields/related.py883
1 files changed, 546 insertions, 337 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 11407ac902..1cf447c6d4 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -19,19 +19,25 @@ from django.utils.translation import gettext_lazy as _
from . import Field
from .mixins import FieldCacheMixin
from .related_descriptors import (
- ForeignKeyDeferredAttribute, ForwardManyToOneDescriptor,
- ForwardOneToOneDescriptor, ManyToManyDescriptor,
- ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
+ ForeignKeyDeferredAttribute,
+ ForwardManyToOneDescriptor,
+ ForwardOneToOneDescriptor,
+ ManyToManyDescriptor,
+ ReverseManyToOneDescriptor,
+ ReverseOneToOneDescriptor,
)
from .related_lookups import (
- RelatedExact, RelatedGreaterThan, RelatedGreaterThanOrEqual, RelatedIn,
- RelatedIsNull, RelatedLessThan, RelatedLessThanOrEqual,
-)
-from .reverse_related import (
- ForeignObjectRel, ManyToManyRel, ManyToOneRel, OneToOneRel,
+ RelatedExact,
+ RelatedGreaterThan,
+ RelatedGreaterThanOrEqual,
+ RelatedIn,
+ RelatedIsNull,
+ RelatedLessThan,
+ RelatedLessThanOrEqual,
)
+from .reverse_related import ForeignObjectRel, ManyToManyRel, ManyToOneRel, OneToOneRel
-RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
+RECURSIVE_RELATIONSHIP_CONSTANT = "self"
def resolve_relation(scope_model, relation):
@@ -119,19 +125,25 @@ class RelatedField(FieldCacheMixin, Field):
def _check_related_name_is_valid(self):
import keyword
+
related_name = self.remote_field.related_name
if related_name is None:
return []
- is_valid_id = not keyword.iskeyword(related_name) and related_name.isidentifier()
- if not (is_valid_id or related_name.endswith('+')):
+ is_valid_id = (
+ not keyword.iskeyword(related_name) and related_name.isidentifier()
+ )
+ if not (is_valid_id or related_name.endswith("+")):
return [
checks.Error(
- "The name '%s' is invalid related_name for field %s.%s" %
- (self.remote_field.related_name, self.model._meta.object_name,
- self.name),
+ "The name '%s' is invalid related_name for field %s.%s"
+ % (
+ self.remote_field.related_name,
+ self.model._meta.object_name,
+ self.name,
+ ),
hint="Related name must be a valid Python identifier or end with a '+'",
obj=self,
- id='fields.E306',
+ id="fields.E306",
)
]
return []
@@ -141,15 +153,17 @@ class RelatedField(FieldCacheMixin, Field):
return []
rel_query_name = self.related_query_name()
errors = []
- if rel_query_name.endswith('_'):
+ if rel_query_name.endswith("_"):
errors.append(
checks.Error(
"Reverse query name '%s' must not end with an underscore."
% rel_query_name,
- hint=("Add or change a related_name or related_query_name "
- "argument for this field."),
+ hint=(
+ "Add or change a related_name or related_query_name "
+ "argument for this field."
+ ),
obj=self,
- id='fields.E308',
+ id="fields.E308",
)
)
if LOOKUP_SEP in rel_query_name:
@@ -157,10 +171,12 @@ class RelatedField(FieldCacheMixin, Field):
checks.Error(
"Reverse query name '%s' must not contain '%s'."
% (rel_query_name, LOOKUP_SEP),
- hint=("Add or change a related_name or related_query_name "
- "argument for this field."),
+ hint=(
+ "Add or change a related_name or related_query_name "
+ "argument for this field."
+ ),
obj=self,
- id='fields.E309',
+ id="fields.E309",
)
)
return errors
@@ -168,29 +184,38 @@ class RelatedField(FieldCacheMixin, Field):
def _check_relation_model_exists(self):
rel_is_missing = self.remote_field.model not in self.opts.apps.get_models()
rel_is_string = isinstance(self.remote_field.model, str)
- model_name = self.remote_field.model if rel_is_string else self.remote_field.model._meta.object_name
- if rel_is_missing and (rel_is_string or not self.remote_field.model._meta.swapped):
+ model_name = (
+ self.remote_field.model
+ if rel_is_string
+ else self.remote_field.model._meta.object_name
+ )
+ if rel_is_missing and (
+ rel_is_string or not self.remote_field.model._meta.swapped
+ ):
return [
checks.Error(
"Field defines a relation with model '%s', which is either "
"not installed, or is abstract." % model_name,
obj=self,
- id='fields.E300',
+ id="fields.E300",
)
]
return []
def _check_referencing_to_swapped_model(self):
- if (self.remote_field.model not in self.opts.apps.get_models() and
- not isinstance(self.remote_field.model, str) and
- self.remote_field.model._meta.swapped):
+ if (
+ self.remote_field.model not in self.opts.apps.get_models()
+ and not isinstance(self.remote_field.model, str)
+ and self.remote_field.model._meta.swapped
+ ):
return [
checks.Error(
"Field defines a relation with the model '%s', which has "
"been swapped out." % self.remote_field.model._meta.label,
- hint="Update the relation to point at 'settings.%s'." % self.remote_field.model._meta.swappable,
+ hint="Update the relation to point at 'settings.%s'."
+ % self.remote_field.model._meta.swappable,
obj=self,
- id='fields.E301',
+ id="fields.E301",
)
]
return []
@@ -227,7 +252,7 @@ class RelatedField(FieldCacheMixin, Field):
rel_name = self.remote_field.get_accessor_name() # i. e. "model_set"
rel_query_name = self.related_query_name() # i. e. "model"
# i.e. "app_label.Model.field".
- field_name = '%s.%s' % (opts.label, self.name)
+ field_name = "%s.%s" % (opts.label, self.name)
# Check clashes between accessor or reverse query name of `field`
# and any other field name -- i.e. accessor for Model.foreign is
@@ -235,28 +260,35 @@ class RelatedField(FieldCacheMixin, Field):
potential_clashes = rel_opts.fields + rel_opts.many_to_many
for clash_field in potential_clashes:
# i.e. "app_label.Target.model_set".
- clash_name = '%s.%s' % (rel_opts.label, clash_field.name)
+ clash_name = "%s.%s" % (rel_opts.label, clash_field.name)
if not rel_is_hidden and clash_field.name == rel_name:
errors.append(
checks.Error(
f"Reverse accessor '{rel_opts.object_name}.{rel_name}' "
f"for '{field_name}' clashes with field name "
f"'{clash_name}'.",
- hint=("Rename field '%s', or add/change a related_name "
- "argument to the definition for field '%s'.") % (clash_name, field_name),
+ hint=(
+ "Rename field '%s', or add/change a related_name "
+ "argument to the definition for field '%s'."
+ )
+ % (clash_name, field_name),
obj=self,
- id='fields.E302',
+ id="fields.E302",
)
)
if clash_field.name == rel_query_name:
errors.append(
checks.Error(
- "Reverse query name for '%s' clashes with field name '%s'." % (field_name, clash_name),
- hint=("Rename field '%s', or add/change a related_name "
- "argument to the definition for field '%s'.") % (clash_name, field_name),
+ "Reverse query name for '%s' clashes with field name '%s'."
+ % (field_name, clash_name),
+ hint=(
+ "Rename field '%s', or add/change a related_name "
+ "argument to the definition for field '%s'."
+ )
+ % (clash_name, field_name),
obj=self,
- id='fields.E303',
+ id="fields.E303",
)
)
@@ -266,7 +298,7 @@ class RelatedField(FieldCacheMixin, Field):
potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)
for clash_field in potential_clashes:
# i.e. "app_label.Model.m2m".
- clash_name = '%s.%s' % (
+ clash_name = "%s.%s" % (
clash_field.related_model._meta.label,
clash_field.field.name,
)
@@ -276,10 +308,13 @@ class RelatedField(FieldCacheMixin, Field):
f"Reverse accessor '{rel_opts.object_name}.{rel_name}' "
f"for '{field_name}' clashes with reverse accessor for "
f"'{clash_name}'.",
- hint=("Add or change a related_name argument "
- "to the definition for '%s' or '%s'.") % (field_name, clash_name),
+ hint=(
+ "Add or change a related_name argument "
+ "to the definition for '%s' or '%s'."
+ )
+ % (field_name, clash_name),
obj=self,
- id='fields.E304',
+ id="fields.E304",
)
)
@@ -288,10 +323,13 @@ class RelatedField(FieldCacheMixin, Field):
checks.Error(
"Reverse query name for '%s' clashes with reverse query name for '%s'."
% (field_name, clash_name),
- hint=("Add or change a related_name argument "
- "to the definition for '%s' or '%s'.") % (field_name, clash_name),
+ hint=(
+ "Add or change a related_name argument "
+ "to the definition for '%s' or '%s'."
+ )
+ % (field_name, clash_name),
obj=self,
- id='fields.E305',
+ id="fields.E305",
)
)
@@ -315,32 +353,35 @@ class RelatedField(FieldCacheMixin, Field):
related_name = self.opts.default_related_name
if related_name:
related_name = related_name % {
- 'class': cls.__name__.lower(),
- 'model_name': cls._meta.model_name.lower(),
- 'app_label': cls._meta.app_label.lower()
+ "class": cls.__name__.lower(),
+ "model_name": cls._meta.model_name.lower(),
+ "app_label": cls._meta.app_label.lower(),
}
self.remote_field.related_name = related_name
if self.remote_field.related_query_name:
related_query_name = self.remote_field.related_query_name % {
- 'class': cls.__name__.lower(),
- 'app_label': cls._meta.app_label.lower(),
+ "class": cls.__name__.lower(),
+ "app_label": cls._meta.app_label.lower(),
}
self.remote_field.related_query_name = related_query_name
def resolve_related_class(model, related, field):
field.remote_field.model = related
field.do_related_class(related, model)
- lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self)
+
+ lazy_related_operation(
+ resolve_related_class, cls, self.remote_field.model, field=self
+ )
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self._limit_choices_to:
- kwargs['limit_choices_to'] = self._limit_choices_to
+ kwargs["limit_choices_to"] = self._limit_choices_to
if self._related_name is not None:
- kwargs['related_name'] = self._related_name
+ kwargs["related_name"] = self._related_name
if self._related_query_name is not None:
- kwargs['related_query_name'] = self._related_query_name
+ kwargs["related_query_name"] = self._related_query_name
return name, path, args, kwargs
def get_forward_related_filter(self, obj):
@@ -352,7 +393,7 @@ class RelatedField(FieldCacheMixin, Field):
self.related_field.model.
"""
return {
- '%s__%s' % (self.name, rh_field.name): getattr(obj, rh_field.attname)
+ "%s__%s" % (self.name, rh_field.name): getattr(obj, rh_field.attname)
for _, rh_field in self.related_fields
}
@@ -391,9 +432,10 @@ class RelatedField(FieldCacheMixin, Field):
return None
def set_attributes_from_rel(self):
- self.name = (
- self.name or
- (self.remote_field.model._meta.model_name + '_' + self.remote_field.model._meta.pk.name)
+ self.name = self.name or (
+ self.remote_field.model._meta.model_name
+ + "_"
+ + self.remote_field.model._meta.pk.name
)
if self.verbose_name is None:
self.verbose_name = self.remote_field.model._meta.verbose_name
@@ -423,14 +465,16 @@ class RelatedField(FieldCacheMixin, Field):
being constructed.
"""
defaults = {}
- if hasattr(self.remote_field, 'get_related_field'):
+ if hasattr(self.remote_field, "get_related_field"):
# If this is a callable, do not invoke it here. Just pass
# it in the defaults for when the form class will later be
# instantiated.
limit_choices_to = self.remote_field.limit_choices_to
- defaults.update({
- 'limit_choices_to': limit_choices_to,
- })
+ defaults.update(
+ {
+ "limit_choices_to": limit_choices_to,
+ }
+ )
defaults.update(kwargs)
return super().formfield(**defaults)
@@ -439,7 +483,11 @@ class RelatedField(FieldCacheMixin, Field):
Define the name that can be used to identify this related object in a
table-spanning query.
"""
- return self.remote_field.related_query_name or self.remote_field.related_name or self.opts.model_name
+ return (
+ self.remote_field.related_query_name
+ or self.remote_field.related_name
+ or self.opts.model_name
+ )
@property
def target_field(self):
@@ -450,7 +498,8 @@ class RelatedField(FieldCacheMixin, Field):
target_fields = self.path_infos[-1].target_fields
if len(target_fields) > 1:
raise exceptions.FieldError(
- "The relation has multiple target fields, but only single target field was asked for")
+ "The relation has multiple target fields, but only single target field was asked for"
+ )
return target_fields[0]
def get_cache_name(self):
@@ -473,13 +522,25 @@ class ForeignObject(RelatedField):
forward_related_accessor_class = ForwardManyToOneDescriptor
rel_class = ForeignObjectRel
- def __init__(self, to, on_delete, from_fields, to_fields, rel=None, related_name=None,
- related_query_name=None, limit_choices_to=None, parent_link=False,
- swappable=True, **kwargs):
+ def __init__(
+ self,
+ to,
+ on_delete,
+ from_fields,
+ to_fields,
+ rel=None,
+ related_name=None,
+ related_query_name=None,
+ limit_choices_to=None,
+ parent_link=False,
+ swappable=True,
+ **kwargs,
+ ):
if rel is None:
rel = self.rel_class(
- self, to,
+ self,
+ to,
related_name=related_name,
related_query_name=related_query_name,
limit_choices_to=limit_choices_to,
@@ -502,8 +563,8 @@ class ForeignObject(RelatedField):
def __copy__(self):
obj = super().__copy__()
# Remove any cached PathInfo values.
- obj.__dict__.pop('path_infos', None)
- obj.__dict__.pop('reverse_path_infos', None)
+ obj.__dict__.pop("path_infos", None)
+ obj.__dict__.pop("reverse_path_infos", None)
return obj
def check(self, **kwargs):
@@ -530,7 +591,7 @@ class ForeignObject(RelatedField):
"model '%s'."
% (to_field, self.remote_field.model._meta.label),
obj=self,
- id='fields.E312',
+ id="fields.E312",
)
)
return errors
@@ -551,21 +612,22 @@ class ForeignObject(RelatedField):
unique_foreign_fields = {
frozenset([f.name])
for f in self.remote_field.model._meta.get_fields()
- if getattr(f, 'unique', False)
+ if getattr(f, "unique", False)
}
- unique_foreign_fields.update({
- frozenset(ut)
- for ut in self.remote_field.model._meta.unique_together
- })
- unique_foreign_fields.update({
- frozenset(uc.fields)
- for uc in self.remote_field.model._meta.total_unique_constraints
- })
+ unique_foreign_fields.update(
+ {frozenset(ut) for ut in self.remote_field.model._meta.unique_together}
+ )
+ unique_foreign_fields.update(
+ {
+ frozenset(uc.fields)
+ for uc in self.remote_field.model._meta.total_unique_constraints
+ }
+ )
foreign_fields = {f.name for f in self.foreign_related_fields}
has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields)
if not has_unique_constraint and len(self.foreign_related_fields) > 1:
- field_combination = ', '.join(
+ field_combination = ", ".join(
"'%s'" % rel_field.name for rel_field in self.foreign_related_fields
)
model_name = self.remote_field.model.__name__
@@ -574,13 +636,13 @@ class ForeignObject(RelatedField):
"No subset of the fields %s on model '%s' is unique."
% (field_combination, model_name),
hint=(
- 'Mark a single field as unique=True or add a set of '
- 'fields to a unique constraint (via unique_together '
- 'or a UniqueConstraint (without condition) in the '
- 'model Meta.constraints).'
+ "Mark a single field as unique=True or add a set of "
+ "fields to a unique constraint (via unique_together "
+ "or a UniqueConstraint (without condition) in the "
+ "model Meta.constraints)."
),
obj=self,
- id='fields.E310',
+ id="fields.E310",
)
]
elif not has_unique_constraint:
@@ -591,12 +653,12 @@ class ForeignObject(RelatedField):
"'%s.%s' must be unique because it is referenced by "
"a foreign key." % (model_name, field_name),
hint=(
- 'Add unique=True to this field or add a '
- 'UniqueConstraint (without condition) in the model '
- 'Meta.constraints.'
+ "Add unique=True to this field or add a "
+ "UniqueConstraint (without condition) in the model "
+ "Meta.constraints."
),
obj=self,
- id='fields.E311',
+ id="fields.E311",
)
]
else:
@@ -604,44 +666,48 @@ class ForeignObject(RelatedField):
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
- kwargs['on_delete'] = self.remote_field.on_delete
- kwargs['from_fields'] = self.from_fields
- kwargs['to_fields'] = self.to_fields
+ kwargs["on_delete"] = self.remote_field.on_delete
+ kwargs["from_fields"] = self.from_fields
+ kwargs["to_fields"] = self.to_fields
if self.remote_field.parent_link:
- kwargs['parent_link'] = self.remote_field.parent_link
+ kwargs["parent_link"] = self.remote_field.parent_link
if isinstance(self.remote_field.model, str):
- if '.' in self.remote_field.model:
- app_label, model_name = self.remote_field.model.split('.')
- kwargs['to'] = '%s.%s' % (app_label, model_name.lower())
+ if "." in self.remote_field.model:
+ app_label, model_name = self.remote_field.model.split(".")
+ kwargs["to"] = "%s.%s" % (app_label, model_name.lower())
else:
- kwargs['to'] = self.remote_field.model.lower()
+ kwargs["to"] = self.remote_field.model.lower()
else:
- kwargs['to'] = self.remote_field.model._meta.label_lower
+ kwargs["to"] = self.remote_field.model._meta.label_lower
# If swappable is True, then see if we're actually pointing to the target
# of a swap.
swappable_setting = self.swappable_setting
if swappable_setting is not None:
# If it's already a settings reference, error
- if hasattr(kwargs['to'], "setting_name"):
- if kwargs['to'].setting_name != swappable_setting:
+ if hasattr(kwargs["to"], "setting_name"):
+ if kwargs["to"].setting_name != swappable_setting:
raise ValueError(
"Cannot deconstruct a ForeignKey pointing to a model "
"that is swapped in place of more than one model (%s and %s)"
- % (kwargs['to'].setting_name, swappable_setting)
+ % (kwargs["to"].setting_name, swappable_setting)
)
# Set it
- kwargs['to'] = SettingsReference(
- kwargs['to'],
+ kwargs["to"] = SettingsReference(
+ kwargs["to"],
swappable_setting,
)
return name, path, args, kwargs
def resolve_related_fields(self):
if not self.from_fields or len(self.from_fields) != len(self.to_fields):
- raise ValueError('Foreign Object from and to fields must be the same non-zero length')
+ raise ValueError(
+ "Foreign Object from and to fields must be the same non-zero length"
+ )
if isinstance(self.remote_field.model, str):
- raise ValueError('Related model %r cannot be resolved' % self.remote_field.model)
+ raise ValueError(
+ "Related model %r cannot be resolved" % self.remote_field.model
+ )
related_fields = []
for index in range(len(self.from_fields)):
from_field_name = self.from_fields[index]
@@ -651,8 +717,11 @@ class ForeignObject(RelatedField):
if from_field_name == RECURSIVE_RELATIONSHIP_CONSTANT
else self.opts.get_field(from_field_name)
)
- to_field = (self.remote_field.model._meta.pk if to_field_name is None
- else self.remote_field.model._meta.get_field(to_field_name))
+ to_field = (
+ self.remote_field.model._meta.pk
+ if to_field_name is None
+ else self.remote_field.model._meta.get_field(to_field_name)
+ )
related_fields.append((from_field, to_field))
return related_fields
@@ -670,7 +739,9 @@ class ForeignObject(RelatedField):
@cached_property
def foreign_related_fields(self):
- return tuple(rhs_field for lhs_field, rhs_field in self.related_fields if rhs_field)
+ return tuple(
+ rhs_field for lhs_field, rhs_field in self.related_fields if rhs_field
+ )
def get_local_related_value(self, instance):
return self.get_instance_value_for_fields(instance, self.local_related_fields)
@@ -688,9 +759,11 @@ class ForeignObject(RelatedField):
# instance.pk (that is, parent_ptr_id) when asked for instance.id.
if field.primary_key:
possible_parent_link = opts.get_ancestor_link(field.model)
- if (not possible_parent_link or
- possible_parent_link.primary_key or
- possible_parent_link.model._meta.abstract):
+ if (
+ not possible_parent_link
+ or possible_parent_link.primary_key
+ or possible_parent_link.model._meta.abstract
+ ):
ret.append(instance.pk)
continue
ret.append(getattr(instance, field.attname))
@@ -702,7 +775,9 @@ class ForeignObject(RelatedField):
def get_joining_columns(self, reverse_join=False):
source = self.reverse_related_fields if reverse_join else self.related_fields
- return tuple((lhs_field.column, rhs_field.column) for lhs_field, rhs_field in source)
+ return tuple(
+ (lhs_field.column, rhs_field.column) for lhs_field, rhs_field in source
+ )
def get_reverse_joining_columns(self):
return self.get_joining_columns(reverse_join=True)
@@ -740,15 +815,17 @@ class ForeignObject(RelatedField):
"""Get path from this field to the related model."""
opts = self.remote_field.model._meta
from_opts = self.model._meta
- return [PathInfo(
- from_opts=from_opts,
- to_opts=opts,
- target_fields=self.foreign_related_fields,
- join_field=self,
- m2m=False,
- direct=True,
- filtered_relation=filtered_relation,
- )]
+ return [
+ PathInfo(
+ from_opts=from_opts,
+ to_opts=opts,
+ target_fields=self.foreign_related_fields,
+ join_field=self,
+ m2m=False,
+ direct=True,
+ filtered_relation=filtered_relation,
+ )
+ ]
@cached_property
def path_infos(self):
@@ -758,15 +835,17 @@ class ForeignObject(RelatedField):
"""Get path from the related model to this field's model."""
opts = self.model._meta
from_opts = self.remote_field.model._meta
- return [PathInfo(
- from_opts=from_opts,
- to_opts=opts,
- target_fields=(opts.pk,),
- join_field=self.remote_field,
- m2m=not self.unique,
- direct=False,
- filtered_relation=filtered_relation,
- )]
+ return [
+ PathInfo(
+ from_opts=from_opts,
+ to_opts=opts,
+ target_fields=(opts.pk,),
+ join_field=self.remote_field,
+ m2m=not self.unique,
+ direct=False,
+ filtered_relation=filtered_relation,
+ )
+ ]
@cached_property
def reverse_path_infos(self):
@@ -776,8 +855,8 @@ class ForeignObject(RelatedField):
@functools.lru_cache(maxsize=None)
def get_lookups(cls):
bases = inspect.getmro(cls)
- bases = bases[:bases.index(ForeignObject) + 1]
- class_lookups = [parent.__dict__.get('class_lookups', {}) for parent in bases]
+ bases = bases[: bases.index(ForeignObject) + 1]
+ class_lookups = [parent.__dict__.get("class_lookups", {}) for parent in bases]
return cls.merge_dicts(class_lookups)
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
@@ -787,13 +866,22 @@ class ForeignObject(RelatedField):
def contribute_to_related_class(self, cls, related):
# Internal FK's - i.e., those with a related name ending with '+' -
# and swapped models don't get a related descriptor.
- if not self.remote_field.is_hidden() and not related.related_model._meta.swapped:
- setattr(cls._meta.concrete_model, related.get_accessor_name(), self.related_accessor_class(related))
+ if (
+ not self.remote_field.is_hidden()
+ and not related.related_model._meta.swapped
+ ):
+ setattr(
+ cls._meta.concrete_model,
+ related.get_accessor_name(),
+ self.related_accessor_class(related),
+ )
# While 'limit_choices_to' might be a callable, simply pass
# it along for later - this is too early because it's still
# model load time.
if self.remote_field.limit_choices_to:
- cls._meta.related_fkey_lookups.append(self.remote_field.limit_choices_to)
+ cls._meta.related_fkey_lookups.append(
+ self.remote_field.limit_choices_to
+ )
ForeignObject.register_lookup(RelatedIn)
@@ -813,6 +901,7 @@ class ForeignKey(ForeignObject):
By default ForeignKey will target the pk of the remote model but this
behavior can be changed by using the ``to_field`` argument.
"""
+
descriptor_class = ForeignKeyDeferredAttribute
# Field flags
many_to_many = False
@@ -824,21 +913,33 @@ class ForeignKey(ForeignObject):
empty_strings_allowed = False
default_error_messages = {
- 'invalid': _('%(model)s instance with %(field)s %(value)r does not exist.')
+ "invalid": _("%(model)s instance with %(field)s %(value)r does not exist.")
}
description = _("Foreign Key (type determined by related field)")
- def __init__(self, to, on_delete, related_name=None, related_query_name=None,
- limit_choices_to=None, parent_link=False, to_field=None,
- db_constraint=True, **kwargs):
+ def __init__(
+ self,
+ to,
+ on_delete,
+ related_name=None,
+ related_query_name=None,
+ limit_choices_to=None,
+ parent_link=False,
+ to_field=None,
+ db_constraint=True,
+ **kwargs,
+ ):
try:
to._meta.model_name
except AttributeError:
if not isinstance(to, str):
raise TypeError(
- '%s(%r) is invalid. First parameter to ForeignKey must be '
- 'either a model, a model name, or the string %r' % (
- self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT,
+ "%s(%r) is invalid. First parameter to ForeignKey must be "
+ "either a model, a model name, or the string %r"
+ % (
+ self.__class__.__name__,
+ to,
+ RECURSIVE_RELATIONSHIP_CONSTANT,
)
)
else:
@@ -847,17 +948,19 @@ class ForeignKey(ForeignObject):
# be correct until contribute_to_class is called. Refs #12190.
to_field = to_field or (to._meta.pk and to._meta.pk.name)
if not callable(on_delete):
- raise TypeError('on_delete must be callable.')
+ raise TypeError("on_delete must be callable.")
- kwargs['rel'] = self.rel_class(
- self, to, to_field,
+ kwargs["rel"] = self.rel_class(
+ self,
+ to,
+ to_field,
related_name=related_name,
related_query_name=related_query_name,
limit_choices_to=limit_choices_to,
parent_link=parent_link,
on_delete=on_delete,
)
- kwargs.setdefault('db_index', True)
+ kwargs.setdefault("db_index", True)
super().__init__(
to,
@@ -879,54 +982,60 @@ class ForeignKey(ForeignObject):
]
def _check_on_delete(self):
- on_delete = getattr(self.remote_field, 'on_delete', None)
+ on_delete = getattr(self.remote_field, "on_delete", None)
if on_delete == SET_NULL and not self.null:
return [
checks.Error(
- 'Field specifies on_delete=SET_NULL, but cannot be null.',
- hint='Set null=True argument on the field, or change the on_delete rule.',
+ "Field specifies on_delete=SET_NULL, but cannot be null.",
+ hint="Set null=True argument on the field, or change the on_delete rule.",
obj=self,
- id='fields.E320',
+ id="fields.E320",
)
]
elif on_delete == SET_DEFAULT and not self.has_default():
return [
checks.Error(
- 'Field specifies on_delete=SET_DEFAULT, but has no default value.',
- hint='Set a default value, or change the on_delete rule.',
+ "Field specifies on_delete=SET_DEFAULT, but has no default value.",
+ hint="Set a default value, or change the on_delete rule.",
obj=self,
- id='fields.E321',
+ id="fields.E321",
)
]
else:
return []
def _check_unique(self, **kwargs):
- return [
- checks.Warning(
- 'Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.',
- hint='ForeignKey(unique=True) is usually better served by a OneToOneField.',
- obj=self,
- id='fields.W342',
- )
- ] if self.unique else []
+ return (
+ [
+ checks.Warning(
+ "Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.",
+ hint="ForeignKey(unique=True) is usually better served by a OneToOneField.",
+ obj=self,
+ id="fields.W342",
+ )
+ ]
+ if self.unique
+ else []
+ )
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
- del kwargs['to_fields']
- del kwargs['from_fields']
+ del kwargs["to_fields"]
+ del kwargs["from_fields"]
# Handle the simpler arguments
if self.db_index:
- del kwargs['db_index']
+ del kwargs["db_index"]
else:
- kwargs['db_index'] = False
+ kwargs["db_index"] = False
if self.db_constraint is not True:
- kwargs['db_constraint'] = self.db_constraint
+ kwargs["db_constraint"] = self.db_constraint
# Rel needs more work.
to_meta = getattr(self.remote_field.model, "_meta", None)
if self.remote_field.field_name and (
- not to_meta or (to_meta.pk and self.remote_field.field_name != to_meta.pk.name)):
- kwargs['to_field'] = self.remote_field.field_name
+ not to_meta
+ or (to_meta.pk and self.remote_field.field_name != to_meta.pk.name)
+ ):
+ kwargs["to_field"] = self.remote_field.field_name
return name, path, args, kwargs
def to_python(self, value):
@@ -940,15 +1049,17 @@ class ForeignKey(ForeignObject):
"""Get path from the related model to this field's model."""
opts = self.model._meta
from_opts = self.remote_field.model._meta
- return [PathInfo(
- from_opts=from_opts,
- to_opts=opts,
- target_fields=(opts.pk,),
- join_field=self.remote_field,
- m2m=not self.unique,
- direct=False,
- filtered_relation=filtered_relation,
- )]
+ return [
+ PathInfo(
+ from_opts=from_opts,
+ to_opts=opts,
+ target_fields=(opts.pk,),
+ join_field=self.remote_field,
+ m2m=not self.unique,
+ direct=False,
+ filtered_relation=filtered_relation,
+ )
+ ]
def validate(self, value, model_instance):
if self.remote_field.parent_link:
@@ -964,21 +1075,27 @@ class ForeignKey(ForeignObject):
qs = qs.complex_filter(self.get_limit_choices_to())
if not qs.exists():
raise exceptions.ValidationError(
- self.error_messages['invalid'],
- code='invalid',
+ self.error_messages["invalid"],
+ code="invalid",
params={
- 'model': self.remote_field.model._meta.verbose_name, 'pk': value,
- 'field': self.remote_field.field_name, 'value': value,
+ "model": self.remote_field.model._meta.verbose_name,
+ "pk": value,
+ "field": self.remote_field.field_name,
+ "value": value,
}, # 'pk' is included for backwards compatibility
)
def resolve_related_fields(self):
related_fields = super().resolve_related_fields()
for from_field, to_field in related_fields:
- if to_field and to_field.model != self.remote_field.model._meta.concrete_model:
+ if (
+ to_field
+ and to_field.model != self.remote_field.model._meta.concrete_model
+ ):
raise exceptions.FieldError(
"'%s.%s' refers to field '%s' which is not local to model "
- "'%s'." % (
+ "'%s'."
+ % (
self.model._meta.label,
self.name,
to_field.name,
@@ -988,7 +1105,7 @@ class ForeignKey(ForeignObject):
return related_fields
def get_attname(self):
- return '%s_id' % self.name
+ return "%s_id" % self.name
def get_attname_column(self):
attname = self.get_attname()
@@ -1003,9 +1120,13 @@ class ForeignKey(ForeignObject):
return field_default
def get_db_prep_save(self, value, connection):
- if value is None or (value == '' and
- (not self.target_field.empty_strings_allowed or
- connection.features.interprets_empty_strings_as_nulls)):
+ if value is None or (
+ value == ""
+ and (
+ not self.target_field.empty_strings_allowed
+ or connection.features.interprets_empty_strings_as_nulls
+ )
+ ):
return None
else:
return self.target_field.get_db_prep_save(value, connection=connection)
@@ -1023,16 +1144,20 @@ class ForeignKey(ForeignObject):
def formfield(self, *, using=None, **kwargs):
if isinstance(self.remote_field.model, str):
- raise ValueError("Cannot create form field for %r yet, because "
- "its related model %r has not been loaded yet" %
- (self.name, self.remote_field.model))
- return super().formfield(**{
- 'form_class': forms.ModelChoiceField,
- 'queryset': self.remote_field.model._default_manager.using(using),
- 'to_field_name': self.remote_field.field_name,
- **kwargs,
- 'blank': self.blank,
- })
+ raise ValueError(
+ "Cannot create form field for %r yet, because "
+ "its related model %r has not been loaded yet"
+ % (self.name, self.remote_field.model)
+ )
+ return super().formfield(
+ **{
+ "form_class": forms.ModelChoiceField,
+ "queryset": self.remote_field.model._default_manager.using(using),
+ "to_field_name": self.remote_field.field_name,
+ **kwargs,
+ "blank": self.blank,
+ }
+ )
def db_check(self, connection):
return None
@@ -1060,7 +1185,7 @@ class ForeignKey(ForeignObject):
while isinstance(output_field, ForeignKey):
output_field = output_field.target_field
if output_field is self:
- raise ValueError('Cannot resolve output_field.')
+ raise ValueError("Cannot resolve output_field.")
return super().get_col(alias, output_field)
@@ -1085,13 +1210,13 @@ class OneToOneField(ForeignKey):
description = _("One-to-one relationship")
def __init__(self, to, on_delete, to_field=None, **kwargs):
- kwargs['unique'] = True
+ kwargs["unique"] = True
super().__init__(to, on_delete, to_field=to_field, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if "unique" in kwargs:
- del kwargs['unique']
+ del kwargs["unique"]
return name, path, args, kwargs
def formfield(self, **kwargs):
@@ -1121,44 +1246,54 @@ def create_many_to_many_intermediary_model(field, klass):
through._meta.managed = model._meta.managed or related._meta.managed
to_model = resolve_relation(klass, field.remote_field.model)
- name = '%s_%s' % (klass._meta.object_name, field.name)
+ name = "%s_%s" % (klass._meta.object_name, field.name)
lazy_related_operation(set_managed, klass, to_model, name)
to = make_model_tuple(to_model)[1]
from_ = klass._meta.model_name
if to == from_:
- to = 'to_%s' % to
- from_ = 'from_%s' % from_
+ to = "to_%s" % to
+ from_ = "from_%s" % from_
- meta = type('Meta', (), {
- 'db_table': field._get_m2m_db_table(klass._meta),
- 'auto_created': klass,
- 'app_label': klass._meta.app_label,
- 'db_tablespace': klass._meta.db_tablespace,
- 'unique_together': (from_, to),
- 'verbose_name': _('%(from)s-%(to)s relationship') % {'from': from_, 'to': to},
- 'verbose_name_plural': _('%(from)s-%(to)s relationships') % {'from': from_, 'to': to},
- 'apps': field.model._meta.apps,
- })
+ meta = type(
+ "Meta",
+ (),
+ {
+ "db_table": field._get_m2m_db_table(klass._meta),
+ "auto_created": klass,
+ "app_label": klass._meta.app_label,
+ "db_tablespace": klass._meta.db_tablespace,
+ "unique_together": (from_, to),
+ "verbose_name": _("%(from)s-%(to)s relationship")
+ % {"from": from_, "to": to},
+ "verbose_name_plural": _("%(from)s-%(to)s relationships")
+ % {"from": from_, "to": to},
+ "apps": field.model._meta.apps,
+ },
+ )
# Construct and return the new class.
- return type(name, (models.Model,), {
- 'Meta': meta,
- '__module__': klass.__module__,
- from_: models.ForeignKey(
- klass,
- related_name='%s+' % name,
- db_tablespace=field.db_tablespace,
- db_constraint=field.remote_field.db_constraint,
- on_delete=CASCADE,
- ),
- to: models.ForeignKey(
- to_model,
- related_name='%s+' % name,
- db_tablespace=field.db_tablespace,
- db_constraint=field.remote_field.db_constraint,
- on_delete=CASCADE,
- )
- })
+ return type(
+ name,
+ (models.Model,),
+ {
+ "Meta": meta,
+ "__module__": klass.__module__,
+ from_: models.ForeignKey(
+ klass,
+ related_name="%s+" % name,
+ db_tablespace=field.db_tablespace,
+ db_constraint=field.remote_field.db_constraint,
+ on_delete=CASCADE,
+ ),
+ to: models.ForeignKey(
+ to_model,
+ related_name="%s+" % name,
+ db_tablespace=field.db_tablespace,
+ db_constraint=field.remote_field.db_constraint,
+ on_delete=CASCADE,
+ ),
+ },
+ )
class ManyToManyField(RelatedField):
@@ -1181,31 +1316,45 @@ class ManyToManyField(RelatedField):
description = _("Many-to-many relationship")
- def __init__(self, to, related_name=None, related_query_name=None,
- limit_choices_to=None, symmetrical=None, through=None,
- through_fields=None, db_constraint=True, db_table=None,
- swappable=True, **kwargs):
+ def __init__(
+ self,
+ to,
+ related_name=None,
+ related_query_name=None,
+ limit_choices_to=None,
+ symmetrical=None,
+ through=None,
+ through_fields=None,
+ db_constraint=True,
+ db_table=None,
+ swappable=True,
+ **kwargs,
+ ):
try:
to._meta
except AttributeError:
if not isinstance(to, str):
raise TypeError(
- '%s(%r) is invalid. First parameter to ManyToManyField '
- 'must be either a model, a model name, or the string %r' % (
- self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT,
+ "%s(%r) is invalid. First parameter to ManyToManyField "
+ "must be either a model, a model name, or the string %r"
+ % (
+ self.__class__.__name__,
+ to,
+ RECURSIVE_RELATIONSHIP_CONSTANT,
)
)
if symmetrical is None:
- symmetrical = (to == RECURSIVE_RELATIONSHIP_CONSTANT)
+ symmetrical = to == RECURSIVE_RELATIONSHIP_CONSTANT
if through is not None and db_table is not None:
raise ValueError(
- 'Cannot specify a db_table if an intermediary model is used.'
+ "Cannot specify a db_table if an intermediary model is used."
)
- kwargs['rel'] = self.rel_class(
- self, to,
+ kwargs["rel"] = self.rel_class(
+ self,
+ to,
related_name=related_name,
related_query_name=related_query_name,
limit_choices_to=limit_choices_to,
@@ -1214,7 +1363,7 @@ class ManyToManyField(RelatedField):
through_fields=through_fields,
db_constraint=db_constraint,
)
- self.has_null_arg = 'null' in kwargs
+ self.has_null_arg = "null" in kwargs
super().__init__(
related_name=related_name,
@@ -1239,9 +1388,9 @@ class ManyToManyField(RelatedField):
if self.unique:
return [
checks.Error(
- 'ManyToManyFields cannot be unique.',
+ "ManyToManyFields cannot be unique.",
obj=self,
- id='fields.E330',
+ id="fields.E330",
)
]
return []
@@ -1252,49 +1401,53 @@ class ManyToManyField(RelatedField):
if self.has_null_arg:
warnings.append(
checks.Warning(
- 'null has no effect on ManyToManyField.',
+ "null has no effect on ManyToManyField.",
obj=self,
- id='fields.W340',
+ id="fields.W340",
)
)
if self._validators:
warnings.append(
checks.Warning(
- 'ManyToManyField does not support validators.',
+ "ManyToManyField does not support validators.",
obj=self,
- id='fields.W341',
+ id="fields.W341",
)
)
if self.remote_field.symmetrical and self._related_name:
warnings.append(
checks.Warning(
- 'related_name has no effect on ManyToManyField '
+ "related_name has no effect on ManyToManyField "
'with a symmetrical relationship, e.g. to "self".',
obj=self,
- id='fields.W345',
+ id="fields.W345",
)
)
return warnings
def _check_relationship_model(self, from_model=None, **kwargs):
- if hasattr(self.remote_field.through, '_meta'):
+ if hasattr(self.remote_field.through, "_meta"):
qualified_model_name = "%s.%s" % (
- self.remote_field.through._meta.app_label, self.remote_field.through.__name__)
+ self.remote_field.through._meta.app_label,
+ self.remote_field.through.__name__,
+ )
else:
qualified_model_name = self.remote_field.through
errors = []
- if self.remote_field.through not in self.opts.apps.get_models(include_auto_created=True):
+ if self.remote_field.through not in self.opts.apps.get_models(
+ include_auto_created=True
+ ):
# The relationship model is not installed.
errors.append(
checks.Error(
"Field specifies a many-to-many relation through model "
"'%s', which has not been installed." % qualified_model_name,
obj=self,
- id='fields.E331',
+ id="fields.E331",
)
)
@@ -1316,7 +1469,7 @@ class ManyToManyField(RelatedField):
# Count foreign keys in intermediate model
if self_referential:
seen_self = sum(
- from_model == getattr(field.remote_field, 'model', None)
+ from_model == getattr(field.remote_field, "model", None)
for field in self.remote_field.through._meta.fields
)
@@ -1327,41 +1480,46 @@ class ManyToManyField(RelatedField):
"'%s', but it has more than two foreign keys "
"to '%s', which is ambiguous. You must specify "
"which two foreign keys Django should use via the "
- "through_fields keyword argument." % (self, from_model_name),
+ "through_fields keyword argument."
+ % (self, from_model_name),
hint="Use through_fields to specify which two foreign keys Django should use.",
obj=self.remote_field.through,
- id='fields.E333',
+ id="fields.E333",
)
)
else:
# Count foreign keys in relationship model
seen_from = sum(
- from_model == getattr(field.remote_field, 'model', None)
+ from_model == getattr(field.remote_field, "model", None)
for field in self.remote_field.through._meta.fields
)
seen_to = sum(
- to_model == getattr(field.remote_field, 'model', None)
+ to_model == getattr(field.remote_field, "model", None)
for field in self.remote_field.through._meta.fields
)
if seen_from > 1 and not self.remote_field.through_fields:
errors.append(
checks.Error(
- ("The model is used as an intermediate model by "
- "'%s', but it has more than one foreign key "
- "from '%s', which is ambiguous. You must specify "
- "which foreign key Django should use via the "
- "through_fields keyword argument.") % (self, from_model_name),
+ (
+ "The model is used as an intermediate model by "
+ "'%s', but it has more than one foreign key "
+ "from '%s', which is ambiguous. You must specify "
+ "which foreign key Django should use via the "
+ "through_fields keyword argument."
+ )
+ % (self, from_model_name),
hint=(
- 'If you want to create a recursive relationship, '
+ "If you want to create a recursive relationship, "
'use ManyToManyField("%s", through="%s").'
- ) % (
+ )
+ % (
RECURSIVE_RELATIONSHIP_CONSTANT,
relationship_model_name,
),
obj=self,
- id='fields.E334',
+ id="fields.E334",
)
)
@@ -1374,14 +1532,15 @@ class ManyToManyField(RelatedField):
"which foreign key Django should use via the "
"through_fields keyword argument." % (self, to_model_name),
hint=(
- 'If you want to create a recursive relationship, '
+ "If you want to create a recursive relationship, "
'use ManyToManyField("%s", through="%s").'
- ) % (
+ )
+ % (
RECURSIVE_RELATIONSHIP_CONSTANT,
relationship_model_name,
),
obj=self,
- id='fields.E335',
+ id="fields.E335",
)
)
@@ -1389,11 +1548,10 @@ class ManyToManyField(RelatedField):
errors.append(
checks.Error(
"The model is used as an intermediate model by "
- "'%s', but it does not have a foreign key to '%s' or '%s'." % (
- self, from_model_name, to_model_name
- ),
+ "'%s', but it does not have a foreign key to '%s' or '%s'."
+ % (self, from_model_name, to_model_name),
obj=self.remote_field.through,
- id='fields.E336',
+ id="fields.E336",
)
)
@@ -1401,8 +1559,11 @@ class ManyToManyField(RelatedField):
if self.remote_field.through_fields is not None:
# Validate that we're given an iterable of at least two items
# and that none of them is "falsy".
- if not (len(self.remote_field.through_fields) >= 2 and
- self.remote_field.through_fields[0] and self.remote_field.through_fields[1]):
+ if not (
+ len(self.remote_field.through_fields) >= 2
+ and self.remote_field.through_fields[0]
+ and self.remote_field.through_fields[1]
+ ):
errors.append(
checks.Error(
"Field specifies 'through_fields' but does not provide "
@@ -1410,7 +1571,7 @@ class ManyToManyField(RelatedField):
"for the relation through model '%s'." % qualified_model_name,
hint="Make sure you specify 'through_fields' as through_fields=('field1', 'field2')",
obj=self,
- id='fields.E337',
+ id="fields.E337",
)
)
@@ -1424,20 +1585,34 @@ class ManyToManyField(RelatedField):
"where the field is attached to."
)
- source, through, target = from_model, self.remote_field.through, self.remote_field.model
- source_field_name, target_field_name = self.remote_field.through_fields[:2]
+ source, through, target = (
+ from_model,
+ self.remote_field.through,
+ self.remote_field.model,
+ )
+ source_field_name, target_field_name = self.remote_field.through_fields[
+ :2
+ ]
- for field_name, related_model in ((source_field_name, source),
- (target_field_name, target)):
+ for field_name, related_model in (
+ (source_field_name, source),
+ (target_field_name, target),
+ ):
possible_field_names = []
for f in through._meta.fields:
- if hasattr(f, 'remote_field') and getattr(f.remote_field, 'model', None) == related_model:
+ if (
+ hasattr(f, "remote_field")
+ and getattr(f.remote_field, "model", None) == related_model
+ ):
possible_field_names.append(f.name)
if possible_field_names:
- hint = "Did you mean one of the following foreign keys to '%s': %s?" % (
- related_model._meta.object_name,
- ', '.join(possible_field_names),
+ hint = (
+ "Did you mean one of the following foreign keys to '%s': %s?"
+ % (
+ related_model._meta.object_name,
+ ", ".join(possible_field_names),
+ )
)
else:
hint = None
@@ -1451,28 +1626,36 @@ class ManyToManyField(RelatedField):
% (qualified_model_name, field_name),
hint=hint,
obj=self,
- id='fields.E338',
+ id="fields.E338",
)
)
else:
- if not (hasattr(field, 'remote_field') and
- getattr(field.remote_field, 'model', None) == related_model):
+ if not (
+ hasattr(field, "remote_field")
+ and getattr(field.remote_field, "model", None)
+ == related_model
+ ):
errors.append(
checks.Error(
- "'%s.%s' is not a foreign key to '%s'." % (
- through._meta.object_name, field_name,
+ "'%s.%s' is not a foreign key to '%s'."
+ % (
+ through._meta.object_name,
+ field_name,
related_model._meta.object_name,
),
hint=hint,
obj=self,
- id='fields.E339',
+ id="fields.E339",
)
)
return errors
def _check_table_uniqueness(self, **kwargs):
- if isinstance(self.remote_field.through, str) or not self.remote_field.through._meta.managed:
+ if (
+ isinstance(self.remote_field.through, str)
+ or not self.remote_field.through._meta.managed
+ ):
return []
registered_tables = {
model._meta.db_table: model
@@ -1483,25 +1666,31 @@ class ManyToManyField(RelatedField):
model = registered_tables.get(m2m_db_table)
# The second condition allows multiple m2m relations on a model if
# some point to a through model that proxies another through model.
- if model and model._meta.concrete_model != self.remote_field.through._meta.concrete_model:
+ if (
+ model
+ and model._meta.concrete_model
+ != self.remote_field.through._meta.concrete_model
+ ):
if model._meta.auto_created:
+
def _get_field_name(model):
for field in model._meta.auto_created._meta.many_to_many:
if field.remote_field.through is model:
return field.name
+
opts = model._meta.auto_created._meta
- clashing_obj = '%s.%s' % (opts.label, _get_field_name(model))
+ clashing_obj = "%s.%s" % (opts.label, _get_field_name(model))
else:
clashing_obj = model._meta.label
if settings.DATABASE_ROUTERS:
- error_class, error_id = checks.Warning, 'fields.W344'
+ error_class, error_id = checks.Warning, "fields.W344"
error_hint = (
- 'You have configured settings.DATABASE_ROUTERS. Verify '
- 'that the table of %r is correctly routed to a separate '
- 'database.' % clashing_obj
+ "You have configured settings.DATABASE_ROUTERS. Verify "
+ "that the table of %r is correctly routed to a separate "
+ "database." % clashing_obj
)
else:
- error_class, error_id = checks.Error, 'fields.E340'
+ error_class, error_id = checks.Error, "fields.E340"
error_hint = None
return [
error_class(
@@ -1518,34 +1707,34 @@ class ManyToManyField(RelatedField):
name, path, args, kwargs = super().deconstruct()
# Handle the simpler arguments.
if self.db_table is not None:
- kwargs['db_table'] = self.db_table
+ kwargs["db_table"] = self.db_table
if self.remote_field.db_constraint is not True:
- kwargs['db_constraint'] = self.remote_field.db_constraint
+ kwargs["db_constraint"] = self.remote_field.db_constraint
# Rel needs more work.
if isinstance(self.remote_field.model, str):
- kwargs['to'] = self.remote_field.model
+ kwargs["to"] = self.remote_field.model
else:
- kwargs['to'] = self.remote_field.model._meta.label
- if getattr(self.remote_field, 'through', None) is not None:
+ kwargs["to"] = self.remote_field.model._meta.label
+ if getattr(self.remote_field, "through", None) is not None:
if isinstance(self.remote_field.through, str):
- kwargs['through'] = self.remote_field.through
+ kwargs["through"] = self.remote_field.through
elif not self.remote_field.through._meta.auto_created:
- kwargs['through'] = self.remote_field.through._meta.label
+ kwargs["through"] = self.remote_field.through._meta.label
# If swappable is True, then see if we're actually pointing to the target
# of a swap.
swappable_setting = self.swappable_setting
if swappable_setting is not None:
# If it's already a settings reference, error.
- if hasattr(kwargs['to'], "setting_name"):
- if kwargs['to'].setting_name != swappable_setting:
+ if hasattr(kwargs["to"], "setting_name"):
+ if kwargs["to"].setting_name != swappable_setting:
raise ValueError(
"Cannot deconstruct a ManyToManyField pointing to a "
"model that is swapped in place of more than one model "
- "(%s and %s)" % (kwargs['to'].setting_name, swappable_setting)
+ "(%s and %s)" % (kwargs["to"].setting_name, swappable_setting)
)
- kwargs['to'] = SettingsReference(
- kwargs['to'],
+ kwargs["to"] = SettingsReference(
+ kwargs["to"],
swappable_setting,
)
return name, path, args, kwargs
@@ -1605,7 +1794,7 @@ class ManyToManyField(RelatedField):
elif self.db_table:
return self.db_table
else:
- m2m_table_name = '%s_%s' % (utils.strip_quotes(opts.db_table), self.name)
+ m2m_table_name = "%s_%s" % (utils.strip_quotes(opts.db_table), self.name)
return utils.truncate_name(m2m_table_name, connection.ops.max_name_length())
def _get_m2m_attr(self, related, attr):
@@ -1613,7 +1802,7 @@ class ManyToManyField(RelatedField):
Function that can be curried to provide the source accessor or DB
column name for the m2m table.
"""
- cache_attr = '_m2m_%s_cache' % attr
+ cache_attr = "_m2m_%s_cache" % attr
if hasattr(self, cache_attr):
return getattr(self, cache_attr)
if self.remote_field.through_fields is not None:
@@ -1621,8 +1810,11 @@ class ManyToManyField(RelatedField):
else:
link_field_name = None
for f in self.remote_field.through._meta.fields:
- if (f.is_relation and f.remote_field.model == related.related_model and
- (link_field_name is None or link_field_name == f.name)):
+ if (
+ f.is_relation
+ and f.remote_field.model == related.related_model
+ and (link_field_name is None or link_field_name == f.name)
+ ):
setattr(self, cache_attr, getattr(f, attr))
return getattr(self, cache_attr)
@@ -1631,7 +1823,7 @@ class ManyToManyField(RelatedField):
Function that can be curried to provide the related accessor or DB
column name for the m2m table.
"""
- cache_attr = '_m2m_reverse_%s_cache' % attr
+ cache_attr = "_m2m_reverse_%s_cache" % attr
if hasattr(self, cache_attr):
return getattr(self, cache_attr)
found = False
@@ -1664,8 +1856,8 @@ class ManyToManyField(RelatedField):
# automatically. The funky name reduces the chance of an accidental
# clash.
if self.remote_field.symmetrical and (
- self.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT or
- self.remote_field.model == cls._meta.object_name
+ self.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT
+ or self.remote_field.model == cls._meta.object_name
):
self.remote_field.related_name = "%s_rel_+" % name
elif self.remote_field.is_hidden():
@@ -1673,7 +1865,7 @@ class ManyToManyField(RelatedField):
# related_name with one generated from the m2m field name. Django
# still uses backwards relations internally and we need to avoid
# clashes between multiple m2m fields with related_name == '+'.
- self.remote_field.related_name = '_%s_%s_%s_+' % (
+ self.remote_field.related_name = "_%s_%s_%s_+" % (
cls._meta.app_label,
cls.__name__.lower(),
name,
@@ -1687,11 +1879,17 @@ class ManyToManyField(RelatedField):
# 3) The class owning the m2m field has been swapped out.
if not cls._meta.abstract:
if self.remote_field.through:
+
def resolve_through_model(_, model, field):
field.remote_field.through = model
- lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self)
+
+ lazy_related_operation(
+ resolve_through_model, cls, self.remote_field.through, field=self
+ )
elif not cls._meta.swapped:
- self.remote_field.through = create_many_to_many_intermediary_model(self, cls)
+ self.remote_field.through = create_many_to_many_intermediary_model(
+ self, cls
+ )
# Add the descriptor for the m2m relation.
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
@@ -1702,19 +1900,30 @@ class ManyToManyField(RelatedField):
def contribute_to_related_class(self, cls, related):
# Internal M2Ms (i.e., those with a related name ending with '+')
# and swapped models don't get a related descriptor.
- if not self.remote_field.is_hidden() and not related.related_model._meta.swapped:
- setattr(cls, related.get_accessor_name(), ManyToManyDescriptor(self.remote_field, reverse=True))
+ if (
+ not self.remote_field.is_hidden()
+ and not related.related_model._meta.swapped
+ ):
+ setattr(
+ cls,
+ related.get_accessor_name(),
+ ManyToManyDescriptor(self.remote_field, reverse=True),
+ )
# Set up the accessors for the column names on the m2m table.
- self.m2m_column_name = partial(self._get_m2m_attr, related, 'column')
- self.m2m_reverse_name = partial(self._get_m2m_reverse_attr, related, 'column')
+ self.m2m_column_name = partial(self._get_m2m_attr, related, "column")
+ self.m2m_reverse_name = partial(self._get_m2m_reverse_attr, related, "column")
- self.m2m_field_name = partial(self._get_m2m_attr, related, 'name')
- self.m2m_reverse_field_name = partial(self._get_m2m_reverse_attr, related, 'name')
+ self.m2m_field_name = partial(self._get_m2m_attr, related, "name")
+ self.m2m_reverse_field_name = partial(
+ self._get_m2m_reverse_attr, related, "name"
+ )
- get_m2m_rel = partial(self._get_m2m_attr, related, 'remote_field')
+ get_m2m_rel = partial(self._get_m2m_attr, related, "remote_field")
self.m2m_target_field_name = lambda: get_m2m_rel().field_name
- get_m2m_reverse_rel = partial(self._get_m2m_reverse_attr, related, 'remote_field')
+ get_m2m_reverse_rel = partial(
+ self._get_m2m_reverse_attr, related, "remote_field"
+ )
self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name
def set_attributes_from_rel(self):
@@ -1728,17 +1937,17 @@ class ManyToManyField(RelatedField):
def formfield(self, *, using=None, **kwargs):
defaults = {
- 'form_class': forms.ModelMultipleChoiceField,
- 'queryset': self.remote_field.model._default_manager.using(using),
+ "form_class": forms.ModelMultipleChoiceField,
+ "queryset": self.remote_field.model._default_manager.using(using),
**kwargs,
}
# If initial is passed in, it's a list of related objects, but the
# MultipleChoiceField takes a list of IDs.
- if defaults.get('initial') is not None:
- initial = defaults['initial']
+ if defaults.get("initial") is not None:
+ initial = defaults["initial"]
if callable(initial):
initial = initial()
- defaults['initial'] = [i.pk for i in initial]
+ defaults["initial"] = [i.pk for i in initial]
return super().formfield(**defaults)
def db_check(self, connection):