diff options
Diffstat (limited to 'django/db/models/fields/related.py')
| -rw-r--r-- | django/db/models/fields/related.py | 883 |
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): |
