diff options
| author | django-bot <ops@djangoproject.com> | 2022-02-03 20:24:19 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-02-07 20:37:05 +0100 |
| commit | 9c19aff7c7561e3a82978a272ecdaad40dda5c00 (patch) | |
| tree | f0506b668a013d0063e5fba3dbf4863b466713ba /django/db/models/fields | |
| parent | f68fa8b45dfac545cfc4111d4e52804c86db68d3 (diff) | |
Refs #33476 -- Reformatted code with Black.
Diffstat (limited to 'django/db/models/fields')
| -rw-r--r-- | django/db/models/fields/__init__.py | 1094 | ||||
| -rw-r--r-- | django/db/models/fields/files.py | 123 | ||||
| -rw-r--r-- | django/db/models/fields/json.py | 249 | ||||
| -rw-r--r-- | django/db/models/fields/mixins.py | 19 | ||||
| -rw-r--r-- | django/db/models/fields/proxy.py | 4 | ||||
| -rw-r--r-- | django/db/models/fields/related.py | 883 | ||||
| -rw-r--r-- | django/db/models/fields/related_descriptors.py | 425 | ||||
| -rw-r--r-- | django/db/models/fields/related_lookups.py | 81 | ||||
| -rw-r--r-- | django/db/models/fields/reverse_related.py | 97 |
9 files changed, 1845 insertions, 1130 deletions
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 6d6d10a483..313e31b5f5 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -19,7 +19,10 @@ from django.db.models.query_utils import DeferredAttribute, RegisterLookupMixin from django.utils import timezone from django.utils.datastructures import DictWrapper from django.utils.dateparse import ( - parse_date, parse_datetime, parse_duration, parse_time, + parse_date, + parse_datetime, + parse_duration, + parse_time, ) from django.utils.duration import duration_microseconds, duration_string from django.utils.functional import Promise, cached_property @@ -29,14 +32,38 @@ from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ __all__ = [ - 'AutoField', 'BLANK_CHOICE_DASH', 'BigAutoField', 'BigIntegerField', - 'BinaryField', 'BooleanField', 'CharField', 'CommaSeparatedIntegerField', - 'DateField', 'DateTimeField', 'DecimalField', 'DurationField', - 'EmailField', 'Empty', 'Field', 'FilePathField', 'FloatField', - 'GenericIPAddressField', 'IPAddressField', 'IntegerField', 'NOT_PROVIDED', - 'NullBooleanField', 'PositiveBigIntegerField', 'PositiveIntegerField', - 'PositiveSmallIntegerField', 'SlugField', 'SmallAutoField', - 'SmallIntegerField', 'TextField', 'TimeField', 'URLField', 'UUIDField', + "AutoField", + "BLANK_CHOICE_DASH", + "BigAutoField", + "BigIntegerField", + "BinaryField", + "BooleanField", + "CharField", + "CommaSeparatedIntegerField", + "DateField", + "DateTimeField", + "DecimalField", + "DurationField", + "EmailField", + "Empty", + "Field", + "FilePathField", + "FloatField", + "GenericIPAddressField", + "IPAddressField", + "IntegerField", + "NOT_PROVIDED", + "NullBooleanField", + "PositiveBigIntegerField", + "PositiveIntegerField", + "PositiveSmallIntegerField", + "SlugField", + "SmallAutoField", + "SmallIntegerField", + "TextField", + "TimeField", + "URLField", + "UUIDField", ] @@ -72,6 +99,7 @@ def _load_field(app_label, model_name, field_name): # # getattr(obj, opts.pk.attname) + def _empty(of_cls): new = Empty() new.__class__ = of_cls @@ -98,14 +126,16 @@ class Field(RegisterLookupMixin): auto_creation_counter = -1 default_validators = [] # Default set of validators default_error_messages = { - 'invalid_choice': _('Value %(value)r is not a valid choice.'), - 'null': _('This field cannot be null.'), - 'blank': _('This field cannot be blank.'), - 'unique': _('%(model_name)s with this %(field_label)s already exists.'), + "invalid_choice": _("Value %(value)r is not a valid choice."), + "null": _("This field cannot be null."), + "blank": _("This field cannot be blank."), + "unique": _("%(model_name)s with this %(field_label)s already exists."), # Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. # Eg: "Title must be unique for pub_date year" - 'unique_for_date': _("%(field_label)s must be unique for " - "%(date_field_label)s %(lookup_type)s."), + "unique_for_date": _( + "%(field_label)s must be unique for " + "%(date_field_label)s %(lookup_type)s." + ), } system_check_deprecated_details = None system_check_removed_details = None @@ -123,18 +153,37 @@ class Field(RegisterLookupMixin): # Generic field type description, usually overridden by subclasses def _description(self): - return _('Field of type: %(field_type)s') % { - 'field_type': self.__class__.__name__ + return _("Field of type: %(field_type)s") % { + "field_type": self.__class__.__name__ } + description = property(_description) - def __init__(self, verbose_name=None, name=None, primary_key=False, - max_length=None, unique=False, blank=False, null=False, - db_index=False, rel=None, default=NOT_PROVIDED, editable=True, - serialize=True, unique_for_date=None, unique_for_month=None, - unique_for_year=None, choices=None, help_text='', db_column=None, - db_tablespace=None, auto_created=False, validators=(), - error_messages=None): + def __init__( + self, + verbose_name=None, + name=None, + primary_key=False, + max_length=None, + unique=False, + blank=False, + null=False, + db_index=False, + rel=None, + default=NOT_PROVIDED, + editable=True, + serialize=True, + unique_for_date=None, + unique_for_month=None, + unique_for_year=None, + choices=None, + help_text="", + db_column=None, + db_tablespace=None, + auto_created=False, + validators=(), + error_messages=None, + ): self.name = name self.verbose_name = verbose_name # May be set by set_attributes_from_name self._verbose_name = verbose_name # Store original for deconstruction @@ -170,7 +219,7 @@ class Field(RegisterLookupMixin): messages = {} for c in reversed(self.__class__.__mro__): - messages.update(getattr(c, 'default_error_messages', {})) + messages.update(getattr(c, "default_error_messages", {})) messages.update(error_messages or {}) self._error_messages = error_messages # Store for deconstruction later self.error_messages = messages @@ -180,18 +229,18 @@ class Field(RegisterLookupMixin): Return "app_label.model_label.field_name" for fields attached to models. """ - if not hasattr(self, 'model'): + if not hasattr(self, "model"): return super().__str__() model = self.model - return '%s.%s' % (model._meta.label, self.name) + return "%s.%s" % (model._meta.label, self.name) def __repr__(self): """Display the module, class, and name of the field.""" - path = '%s.%s' % (self.__class__.__module__, self.__class__.__qualname__) - name = getattr(self, 'name', None) + path = "%s.%s" % (self.__class__.__module__, self.__class__.__qualname__) + name = getattr(self, "name", None) if name is not None: - return '<%s: %s>' % (path, name) - return '<%s>' % path + return "<%s: %s>" % (path, name) + return "<%s>" % path def check(self, **kwargs): return [ @@ -209,12 +258,12 @@ class Field(RegisterLookupMixin): Check if field name is valid, i.e. 1) does not end with an underscore, 2) does not contain "__" and 3) is not "pk". """ - if self.name.endswith('_'): + if self.name.endswith("_"): return [ checks.Error( - 'Field names must not end with an underscore.', + "Field names must not end with an underscore.", obj=self, - id='fields.E001', + id="fields.E001", ) ] elif LOOKUP_SEP in self.name: @@ -222,15 +271,15 @@ class Field(RegisterLookupMixin): checks.Error( 'Field names must not contain "%s".' % LOOKUP_SEP, obj=self, - id='fields.E002', + id="fields.E002", ) ] - elif self.name == 'pk': + elif self.name == "pk": return [ checks.Error( "'pk' is a reserved word that cannot be used as a field name.", obj=self, - id='fields.E003', + id="fields.E003", ) ] else: @@ -249,7 +298,7 @@ class Field(RegisterLookupMixin): checks.Error( "'choices' must be an iterable (e.g., a list or tuple).", obj=self, - id='fields.E004', + id="fields.E004", ) ] @@ -268,14 +317,22 @@ class Field(RegisterLookupMixin): ): break if self.max_length is not None and group_choices: - choice_max_length = max([ - choice_max_length, - *(len(value) for value, _ in group_choices if isinstance(value, str)), - ]) + choice_max_length = max( + [ + choice_max_length, + *( + len(value) + for value, _ in group_choices + if isinstance(value, str) + ), + ] + ) except (TypeError, ValueError): # No groups, choices in the form [value, display] value, human_name = group_name, group_choices - if not self._choices_is_value(value) or not self._choices_is_value(human_name): + if not self._choices_is_value(value) or not self._choices_is_value( + human_name + ): break if self.max_length is not None and isinstance(value, str): choice_max_length = max(choice_max_length, len(value)) @@ -290,7 +347,7 @@ class Field(RegisterLookupMixin): "'max_length' is too small to fit the longest value " "in 'choices' (%d characters)." % choice_max_length, obj=self, - id='fields.E009', + id="fields.E009", ), ] return [] @@ -300,7 +357,7 @@ class Field(RegisterLookupMixin): "'choices' must be an iterable containing " "(actual value, human readable name) tuples.", obj=self, - id='fields.E005', + id="fields.E005", ) ] @@ -310,25 +367,30 @@ class Field(RegisterLookupMixin): checks.Error( "'db_index' must be None, True or False.", obj=self, - id='fields.E006', + id="fields.E006", ) ] else: return [] def _check_null_allowed_for_primary_keys(self): - if (self.primary_key and self.null and - not connection.features.interprets_empty_strings_as_nulls): + if ( + self.primary_key + and self.null + and not connection.features.interprets_empty_strings_as_nulls + ): # We cannot reliably check this for backends like Oracle which # consider NULL and '' to be equal (and thus set up # character-based fields a little differently). return [ checks.Error( - 'Primary keys must not have null=True.', - hint=('Set null=False on the field, or ' - 'remove primary_key=True argument.'), + "Primary keys must not have null=True.", + hint=( + "Set null=False on the field, or " + "remove primary_key=True argument." + ), obj=self, - id='fields.E007', + id="fields.E007", ) ] else: @@ -340,7 +402,9 @@ class Field(RegisterLookupMixin): app_label = self.model._meta.app_label errors = [] for alias in databases: - if router.allow_migrate(alias, app_label, model_name=self.model._meta.model_name): + if router.allow_migrate( + alias, app_label, model_name=self.model._meta.model_name + ): errors.extend(connections[alias].validation.check_field(self, **kwargs)) return errors @@ -354,11 +418,12 @@ class Field(RegisterLookupMixin): hint=( "validators[{i}] ({repr}) isn't a function or " "instance of a validator class.".format( - i=i, repr=repr(validator), + i=i, + repr=repr(validator), ) ), obj=self, - id='fields.E008', + id="fields.E008", ) ) return errors @@ -368,41 +433,41 @@ class Field(RegisterLookupMixin): return [ checks.Error( self.system_check_removed_details.get( - 'msg', - '%s has been removed except for support in historical ' - 'migrations.' % self.__class__.__name__ + "msg", + "%s has been removed except for support in historical " + "migrations." % self.__class__.__name__, ), - hint=self.system_check_removed_details.get('hint'), + hint=self.system_check_removed_details.get("hint"), obj=self, - id=self.system_check_removed_details.get('id', 'fields.EXXX'), + id=self.system_check_removed_details.get("id", "fields.EXXX"), ) ] elif self.system_check_deprecated_details is not None: return [ checks.Warning( self.system_check_deprecated_details.get( - 'msg', - '%s has been deprecated.' % self.__class__.__name__ + "msg", "%s has been deprecated." % self.__class__.__name__ ), - hint=self.system_check_deprecated_details.get('hint'), + hint=self.system_check_deprecated_details.get("hint"), obj=self, - id=self.system_check_deprecated_details.get('id', 'fields.WXXX'), + id=self.system_check_deprecated_details.get("id", "fields.WXXX"), ) ] return [] def get_col(self, alias, output_field=None): - if ( - alias == self.model._meta.db_table and - (output_field is None or output_field == self) + if alias == self.model._meta.db_table and ( + output_field is None or output_field == self ): return self.cached_col from django.db.models.expressions import Col + return Col(alias, self, output_field) @cached_property def cached_col(self): from django.db.models.expressions import Col + return Col(self.model._meta.db_table, self) def select_format(self, compiler, sql, params): @@ -462,7 +527,7 @@ class Field(RegisterLookupMixin): "unique_for_month": None, "unique_for_year": None, "choices": None, - "help_text": '', + "help_text": "", "db_column": None, "db_tablespace": None, "auto_created": False, @@ -495,8 +560,8 @@ class Field(RegisterLookupMixin): path = path.replace("django.db.models.fields.related", "django.db.models") elif path.startswith("django.db.models.fields.files"): path = path.replace("django.db.models.fields.files", "django.db.models") - elif path.startswith('django.db.models.fields.json'): - path = path.replace('django.db.models.fields.json', 'django.db.models') + elif path.startswith("django.db.models.fields.json"): + path = path.replace("django.db.models.fields.json", "django.db.models") elif path.startswith("django.db.models.fields.proxy"): path = path.replace("django.db.models.fields.proxy", "django.db.models") elif path.startswith("django.db.models.fields"): @@ -515,10 +580,9 @@ class Field(RegisterLookupMixin): def __eq__(self, other): # Needed for @total_ordering if isinstance(other, Field): - return ( - self.creation_counter == other.creation_counter and - getattr(self, 'model', None) == getattr(other, 'model', None) - ) + return self.creation_counter == other.creation_counter and getattr( + self, "model", None + ) == getattr(other, "model", None) return NotImplemented def __lt__(self, other): @@ -526,17 +590,18 @@ class Field(RegisterLookupMixin): # Order by creation_counter first for backward compatibility. if isinstance(other, Field): if ( - self.creation_counter != other.creation_counter or - not hasattr(self, 'model') and not hasattr(other, 'model') + self.creation_counter != other.creation_counter + or not hasattr(self, "model") + and not hasattr(other, "model") ): return self.creation_counter < other.creation_counter - elif hasattr(self, 'model') != hasattr(other, 'model'): - return not hasattr(self, 'model') # Order no-model fields first + elif hasattr(self, "model") != hasattr(other, "model"): + return not hasattr(self, "model") # Order no-model fields first else: # creation_counter's are equal, compare only models. - return ( - (self.model._meta.app_label, self.model._meta.model_name) < - (other.model._meta.app_label, other.model._meta.model_name) + return (self.model._meta.app_label, self.model._meta.model_name) < ( + other.model._meta.app_label, + other.model._meta.model_name, ) return NotImplemented @@ -549,7 +614,7 @@ class Field(RegisterLookupMixin): obj = copy.copy(self) if self.remote_field: obj.remote_field = copy.copy(self.remote_field) - if hasattr(self.remote_field, 'field') and self.remote_field.field is self: + if hasattr(self.remote_field, "field") and self.remote_field.field is self: obj.remote_field.field = obj memodict[id(self)] = obj return obj @@ -568,7 +633,7 @@ class Field(RegisterLookupMixin): not a new copy of that field. So, use the app registry to load the model and then the field back. """ - if not hasattr(self, 'model'): + if not hasattr(self, "model"): # Fields are sometimes used without attaching them to models (for # example in aggregation). In this case give back a plain field # instance. The code below will create a new empty instance of @@ -577,10 +642,13 @@ class Field(RegisterLookupMixin): state = self.__dict__.copy() # The _get_default cached_property can't be pickled due to lambda # usage. - state.pop('_get_default', None) + state.pop("_get_default", None) return _empty, (self.__class__,), state - return _load_field, (self.model._meta.app_label, self.model._meta.object_name, - self.name) + return _load_field, ( + self.model._meta.app_label, + self.model._meta.object_name, + self.name, + ) def get_pk_value_on_save(self, instance): """ @@ -618,7 +686,7 @@ class Field(RegisterLookupMixin): try: v(value) except exceptions.ValidationError as e: - if hasattr(e, 'code') and e.code in self.error_messages: + if hasattr(e, "code") and e.code in self.error_messages: e.message = self.error_messages[e.code] errors.extend(e.error_list) @@ -645,16 +713,16 @@ class Field(RegisterLookupMixin): elif value == option_key: return raise exceptions.ValidationError( - self.error_messages['invalid_choice'], - code='invalid_choice', - params={'value': value}, + self.error_messages["invalid_choice"], + code="invalid_choice", + params={"value": value}, ) if value is None and not self.null: - raise exceptions.ValidationError(self.error_messages['null'], code='null') + raise exceptions.ValidationError(self.error_messages["null"], code="null") if not self.blank and value in self.empty_values: - raise exceptions.ValidationError(self.error_messages['blank'], code='blank') + raise exceptions.ValidationError(self.error_messages["blank"], code="blank") def clean(self, value, model_instance): """ @@ -668,7 +736,7 @@ class Field(RegisterLookupMixin): return value def db_type_parameters(self, connection): - return DictWrapper(self.__dict__, connection.ops.quote_name, 'qn_') + return DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") def db_check(self, connection): """ @@ -678,7 +746,9 @@ class Field(RegisterLookupMixin): """ data = self.db_type_parameters(connection) try: - return connection.data_type_check_constraints[self.get_internal_type()] % data + return ( + connection.data_type_check_constraints[self.get_internal_type()] % data + ) except KeyError: return None @@ -740,7 +810,7 @@ class Field(RegisterLookupMixin): return connection.data_types_suffix.get(self.get_internal_type()) def get_db_converters(self, connection): - if hasattr(self, 'from_db_value'): + if hasattr(self, "from_db_value"): return [self.from_db_value] return [] @@ -765,7 +835,7 @@ class Field(RegisterLookupMixin): self.attname, self.column = self.get_attname_column() self.concrete = self.column is not None if self.verbose_name is None and self.name: - self.verbose_name = self.name.replace('_', ' ') + self.verbose_name = self.name.replace("_", " ") def contribute_to_class(self, cls, name, private_only=False): """ @@ -784,10 +854,10 @@ class Field(RegisterLookupMixin): # this class, but don't check methods derived from inheritance, to # allow overriding inherited choices. For more complex inheritance # structures users should override contribute_to_class(). - if 'get_%s_display' % self.name not in cls.__dict__: + if "get_%s_display" % self.name not in cls.__dict__: setattr( cls, - 'get_%s_display' % self.name, + "get_%s_display" % self.name, partialmethod(cls._get_FIELD_display, field=self), ) @@ -848,11 +918,21 @@ class Field(RegisterLookupMixin): return self.default return lambda: self.default - if not self.empty_strings_allowed or self.null and not connection.features.interprets_empty_strings_as_nulls: + if ( + not self.empty_strings_allowed + or self.null + and not connection.features.interprets_empty_strings_as_nulls + ): return return_None return str # return empty string - def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None, ordering=()): + def get_choices( + self, + include_blank=True, + blank_choice=BLANK_CHOICE_DASH, + limit_choices_to=None, + ordering=(), + ): """ Return choices with a default blank choices included, for use as <select> choices for this field. @@ -860,7 +940,9 @@ class Field(RegisterLookupMixin): if self.choices is not None: choices = list(self.choices) if include_blank: - blank_defined = any(choice in ('', None) for choice, _ in self.flatchoices) + blank_defined = any( + choice in ("", None) for choice, _ in self.flatchoices + ) if not blank_defined: choices = blank_choice + choices return choices @@ -868,8 +950,8 @@ class Field(RegisterLookupMixin): limit_choices_to = limit_choices_to or self.get_limit_choices_to() choice_func = operator.attrgetter( self.remote_field.get_related_field().attname - if hasattr(self.remote_field, 'get_related_field') - else 'pk' + if hasattr(self.remote_field, "get_related_field") + else "pk" ) qs = rel_model._default_manager.complex_filter(limit_choices_to) if ordering: @@ -896,6 +978,7 @@ class Field(RegisterLookupMixin): else: flat.append((choice, value)) return flat + flatchoices = property(_get_flatchoices) def save_form_data(self, instance, data): @@ -904,24 +987,25 @@ class Field(RegisterLookupMixin): def formfield(self, form_class=None, choices_form_class=None, **kwargs): """Return a django.forms.Field instance for this field.""" defaults = { - 'required': not self.blank, - 'label': capfirst(self.verbose_name), - 'help_text': self.help_text, + "required": not self.blank, + "label": capfirst(self.verbose_name), + "help_text": self.help_text, } if self.has_default(): if callable(self.default): - defaults['initial'] = self.default - defaults['show_hidden_initial'] = True + defaults["initial"] = self.default + defaults["show_hidden_initial"] = True else: - defaults['initial'] = self.get_default() + defaults["initial"] = self.get_default() if self.choices is not None: # Fields with choices get special treatment. - include_blank = (self.blank or - not (self.has_default() or 'initial' in kwargs)) - defaults['choices'] = self.get_choices(include_blank=include_blank) - defaults['coerce'] = self.to_python + include_blank = self.blank or not ( + self.has_default() or "initial" in kwargs + ) + defaults["choices"] = self.get_choices(include_blank=include_blank) + defaults["coerce"] = self.to_python if self.null: - defaults['empty_value'] = None + defaults["empty_value"] = None if choices_form_class is not None: form_class = choices_form_class else: @@ -930,9 +1014,19 @@ class Field(RegisterLookupMixin): # max_value) don't apply for choice fields, so be sure to only pass # the values that TypedChoiceField will understand. for k in list(kwargs): - if k not in ('coerce', 'empty_value', 'choices', 'required', - 'widget', 'label', 'initial', 'help_text', - 'error_messages', 'show_hidden_initial', 'disabled'): + if k not in ( + "coerce", + "empty_value", + "choices", + "required", + "widget", + "label", + "initial", + "help_text", + "error_messages", + "show_hidden_initial", + "disabled", + ): del kwargs[k] defaults.update(kwargs) if form_class is None: @@ -947,8 +1041,8 @@ class Field(RegisterLookupMixin): class BooleanField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value must be either True or False.'), - 'invalid_nullable': _('“%(value)s” value must be either True, False, or None.'), + "invalid": _("“%(value)s” value must be either True or False."), + "invalid_nullable": _("“%(value)s” value must be either True, False, or None."), } description = _("Boolean (Either True or False)") @@ -961,14 +1055,14 @@ class BooleanField(Field): if value in (True, False): # 1/0 are equal to True/False. bool() converts former to latter. return bool(value) - if value in ('t', 'True', '1'): + if value in ("t", "True", "1"): return True - if value in ('f', 'False', '0'): + if value in ("f", "False", "0"): return False raise exceptions.ValidationError( - self.error_messages['invalid_nullable' if self.null else 'invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid_nullable" if self.null else "invalid"], + code="invalid", + params={"value": value}, ) def get_prep_value(self, value): @@ -979,14 +1073,14 @@ class BooleanField(Field): def formfield(self, **kwargs): if self.choices is not None: - include_blank = not (self.has_default() or 'initial' in kwargs) - defaults = {'choices': self.get_choices(include_blank=include_blank)} + include_blank = not (self.has_default() or "initial" in kwargs) + defaults = {"choices": self.get_choices(include_blank=include_blank)} else: form_class = forms.NullBooleanField if self.null else forms.BooleanField # In HTML checkboxes, 'required' means "must be checked" which is # different from the choices case ("must select some value"). # required=False allows unchecked checkboxes. - defaults = {'form_class': form_class, 'required': False} + defaults = {"form_class": form_class, "required": False} return super().formfield(**{**defaults, **kwargs}) def select_format(self, compiler, sql, params): @@ -994,8 +1088,8 @@ class BooleanField(Field): # Filters that match everything are handled as empty strings in the # WHERE clause, but in SELECT or GROUP BY list they must use a # predicate that's always True. - if sql == '': - sql = '1' + if sql == "": + sql = "1" return sql, params @@ -1009,7 +1103,7 @@ class CharField(Field): self.validators.append(validators.MaxLengthValidator(self.max_length)) def check(self, **kwargs): - databases = kwargs.get('databases') or [] + databases = kwargs.get("databases") or [] return [ *super().check(**kwargs), *self._check_db_collation(databases), @@ -1022,16 +1116,19 @@ class CharField(Field): checks.Error( "CharFields must define a 'max_length' attribute.", obj=self, - id='fields.E120', + id="fields.E120", ) ] - elif (not isinstance(self.max_length, int) or isinstance(self.max_length, bool) or - self.max_length <= 0): + elif ( + not isinstance(self.max_length, int) + or isinstance(self.max_length, bool) + or self.max_length <= 0 + ): return [ checks.Error( "'max_length' must be a positive integer.", obj=self, - id='fields.E121', + id="fields.E121", ) ] else: @@ -1044,16 +1141,17 @@ class CharField(Field): continue connection = connections[db] if not ( - self.db_collation is None or - 'supports_collation_on_charfield' in self.model._meta.required_db_features or - connection.features.supports_collation_on_charfield + self.db_collation is None + or "supports_collation_on_charfield" + in self.model._meta.required_db_features + or connection.features.supports_collation_on_charfield ): errors.append( checks.Error( - '%s does not support a database collation on ' - 'CharFields.' % connection.display_name, + "%s does not support a database collation on " + "CharFields." % connection.display_name, obj=self, - id='fields.E190', + id="fields.E190", ), ) return errors @@ -1079,17 +1177,17 @@ class CharField(Field): # Passing max_length to forms.CharField means that the value's length # will be validated twice. This is considered acceptable since we want # the value in the form field (to pass into widget for example). - defaults = {'max_length': self.max_length} + defaults = {"max_length": self.max_length} # TODO: Handle multiple backends with different feature flags. if self.null and not connection.features.interprets_empty_strings_as_nulls: - defaults['empty_value'] = None + defaults["empty_value"] = None defaults.update(kwargs) return super().formfield(**defaults) def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.db_collation: - kwargs['db_collation'] = self.db_collation + kwargs["db_collation"] = self.db_collation return name, path, args, kwargs @@ -1097,15 +1195,15 @@ class CommaSeparatedIntegerField(CharField): default_validators = [validators.validate_comma_separated_integer_list] description = _("Comma-separated integers") system_check_removed_details = { - 'msg': ( - 'CommaSeparatedIntegerField is removed except for support in ' - 'historical migrations.' + "msg": ( + "CommaSeparatedIntegerField is removed except for support in " + "historical migrations." ), - 'hint': ( - 'Use CharField(validators=[validate_comma_separated_integer_list]) ' - 'instead.' + "hint": ( + "Use CharField(validators=[validate_comma_separated_integer_list]) " + "instead." ), - 'id': 'fields.E901', + "id": "fields.E901", } @@ -1120,7 +1218,6 @@ def _get_naive_now(): class DateTimeCheckMixin: - def check(self, **kwargs): return [ *super().check(**kwargs), @@ -1132,8 +1229,14 @@ class DateTimeCheckMixin: # auto_now, auto_now_add, and default are mutually exclusive # options. The use of more than one of these options together # will trigger an Error - mutually_exclusive_options = [self.auto_now_add, self.auto_now, self.has_default()] - enabled_options = [option not in (None, False) for option in mutually_exclusive_options].count(True) + mutually_exclusive_options = [ + self.auto_now_add, + self.auto_now, + self.has_default(), + ] + enabled_options = [ + option not in (None, False) for option in mutually_exclusive_options + ].count(True) if enabled_options > 1: return [ checks.Error( @@ -1141,7 +1244,7 @@ class DateTimeCheckMixin: "are mutually exclusive. Only one of these options " "may be present.", obj=self, - id='fields.E160', + id="fields.E160", ) ] else: @@ -1173,15 +1276,15 @@ class DateTimeCheckMixin: if lower <= value <= upper: return [ checks.Warning( - 'Fixed default value provided.', + "Fixed default value provided.", hint=( - 'It seems you set a fixed date / time / datetime ' - 'value as default for this field. This may not be ' - 'what you want. If you want to have the current date ' - 'as default, use `django.utils.timezone.now`' + "It seems you set a fixed date / time / datetime " + "value as default for this field. This may not be " + "what you want. If you want to have the current date " + "as default, use `django.utils.timezone.now`" ), obj=self, - id='fields.W161', + id="fields.W161", ) ] return [] @@ -1190,19 +1293,24 @@ class DateTimeCheckMixin: class DateField(DateTimeCheckMixin, Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value has an invalid date format. It must be ' - 'in YYYY-MM-DD format.'), - 'invalid_date': _('“%(value)s” value has the correct format (YYYY-MM-DD) ' - 'but it is an invalid date.'), + "invalid": _( + "“%(value)s” value has an invalid date format. It must be " + "in YYYY-MM-DD format." + ), + "invalid_date": _( + "“%(value)s” value has the correct format (YYYY-MM-DD) " + "but it is an invalid date." + ), } description = _("Date (without time)") - def __init__(self, verbose_name=None, name=None, auto_now=False, - auto_now_add=False, **kwargs): + def __init__( + self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs + ): self.auto_now, self.auto_now_add = auto_now, auto_now_add if auto_now or auto_now_add: - kwargs['editable'] = False - kwargs['blank'] = True + kwargs["editable"] = False + kwargs["blank"] = True super().__init__(verbose_name, name, **kwargs) def _check_fix_default_value(self): @@ -1227,12 +1335,12 @@ class DateField(DateTimeCheckMixin, Field): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.auto_now: - kwargs['auto_now'] = True + kwargs["auto_now"] = True if self.auto_now_add: - kwargs['auto_now_add'] = True + kwargs["auto_now_add"] = True if self.auto_now or self.auto_now_add: - del kwargs['editable'] - del kwargs['blank'] + del kwargs["editable"] + del kwargs["blank"] return name, path, args, kwargs def get_internal_type(self): @@ -1257,15 +1365,15 @@ class DateField(DateTimeCheckMixin, Field): return parsed except ValueError: raise exceptions.ValidationError( - self.error_messages['invalid_date'], - code='invalid_date', - params={'value': value}, + self.error_messages["invalid_date"], + code="invalid_date", + params={"value": value}, ) raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def pre_save(self, model_instance, add): @@ -1280,12 +1388,18 @@ class DateField(DateTimeCheckMixin, Field): super().contribute_to_class(cls, name, **kwargs) if not self.null: setattr( - cls, 'get_next_by_%s' % self.name, - partialmethod(cls._get_next_or_previous_by_FIELD, field=self, is_next=True) + cls, + "get_next_by_%s" % self.name, + partialmethod( + cls._get_next_or_previous_by_FIELD, field=self, is_next=True + ), ) setattr( - cls, 'get_previous_by_%s' % self.name, - partialmethod(cls._get_next_or_previous_by_FIELD, field=self, is_next=False) + cls, + "get_previous_by_%s" % self.name, + partialmethod( + cls._get_next_or_previous_by_FIELD, field=self, is_next=False + ), ) def get_prep_value(self, value): @@ -1300,25 +1414,33 @@ class DateField(DateTimeCheckMixin, Field): def value_to_string(self, obj): val = self.value_from_object(obj) - return '' if val is None else val.isoformat() + return "" if val is None else val.isoformat() def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.DateField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.DateField, + **kwargs, + } + ) class DateTimeField(DateField): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value has an invalid format. It must be in ' - 'YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format.'), - 'invalid_date': _("“%(value)s” value has the correct format " - "(YYYY-MM-DD) but it is an invalid date."), - 'invalid_datetime': _('“%(value)s” value has the correct format ' - '(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) ' - 'but it is an invalid date/time.'), + "invalid": _( + "“%(value)s” value has an invalid format. It must be in " + "YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format." + ), + "invalid_date": _( + "“%(value)s” value has the correct format " + "(YYYY-MM-DD) but it is an invalid date." + ), + "invalid_datetime": _( + "“%(value)s” value has the correct format " + "(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " + "but it is an invalid date/time." + ), } description = _("Date (with time)") @@ -1353,10 +1475,12 @@ class DateTimeField(DateField): # local time. This won't work during DST change, but we can't # do much about it, so we let the exceptions percolate up the # call stack. - warnings.warn("DateTimeField %s.%s received a naive datetime " - "(%s) while time zone support is active." % - (self.model.__name__, self.name, value), - RuntimeWarning) + warnings.warn( + "DateTimeField %s.%s received a naive datetime " + "(%s) while time zone support is active." + % (self.model.__name__, self.name, value), + RuntimeWarning, + ) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) return value @@ -1367,9 +1491,9 @@ class DateTimeField(DateField): return parsed except ValueError: raise exceptions.ValidationError( - self.error_messages['invalid_datetime'], - code='invalid_datetime', - params={'value': value}, + self.error_messages["invalid_datetime"], + code="invalid_datetime", + params={"value": value}, ) try: @@ -1378,15 +1502,15 @@ class DateTimeField(DateField): return datetime.datetime(parsed.year, parsed.month, parsed.day) except ValueError: raise exceptions.ValidationError( - self.error_messages['invalid_date'], - code='invalid_date', - params={'value': value}, + self.error_messages["invalid_date"], + code="invalid_date", + params={"value": value}, ) raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def pre_save(self, model_instance, add): @@ -1408,13 +1532,14 @@ class DateTimeField(DateField): # time. This won't work during DST change, but we can't do much # about it, so we let the exceptions percolate up the call stack. try: - name = '%s.%s' % (self.model.__name__, self.name) + name = "%s.%s" % (self.model.__name__, self.name) except AttributeError: - name = '(unbound)' - warnings.warn("DateTimeField %s received a naive datetime (%s)" - " while time zone support is active." % - (name, value), - RuntimeWarning) + name = "(unbound)" + warnings.warn( + "DateTimeField %s received a naive datetime (%s)" + " while time zone support is active." % (name, value), + RuntimeWarning, + ) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) return value @@ -1427,24 +1552,32 @@ class DateTimeField(DateField): def value_to_string(self, obj): val = self.value_from_object(obj) - return '' if val is None else val.isoformat() + return "" if val is None else val.isoformat() def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.DateTimeField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.DateTimeField, + **kwargs, + } + ) class DecimalField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value must be a decimal number.'), + "invalid": _("“%(value)s” value must be a decimal number."), } description = _("Decimal number") - def __init__(self, verbose_name=None, name=None, max_digits=None, - decimal_places=None, **kwargs): + def __init__( + self, + verbose_name=None, + name=None, + max_digits=None, + decimal_places=None, + **kwargs, + ): self.max_digits, self.decimal_places = max_digits, decimal_places super().__init__(verbose_name, name, **kwargs) @@ -1471,7 +1604,7 @@ class DecimalField(Field): checks.Error( "DecimalFields must define a 'decimal_places' attribute.", obj=self, - id='fields.E130', + id="fields.E130", ) ] except ValueError: @@ -1479,7 +1612,7 @@ class DecimalField(Field): checks.Error( "'decimal_places' must be a non-negative integer.", obj=self, - id='fields.E131', + id="fields.E131", ) ] else: @@ -1495,7 +1628,7 @@ class DecimalField(Field): checks.Error( "DecimalFields must define a 'max_digits' attribute.", obj=self, - id='fields.E132', + id="fields.E132", ) ] except ValueError: @@ -1503,7 +1636,7 @@ class DecimalField(Field): checks.Error( "'max_digits' must be a positive integer.", obj=self, - id='fields.E133', + id="fields.E133", ) ] else: @@ -1515,7 +1648,7 @@ class DecimalField(Field): checks.Error( "'max_digits' must be greater or equal to 'decimal_places'.", obj=self, - id='fields.E134', + id="fields.E134", ) ] return [] @@ -1533,9 +1666,9 @@ class DecimalField(Field): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.max_digits is not None: - kwargs['max_digits'] = self.max_digits + kwargs["max_digits"] = self.max_digits if self.decimal_places is not None: - kwargs['decimal_places'] = self.decimal_places + kwargs["decimal_places"] = self.decimal_places return name, path, args, kwargs def get_internal_type(self): @@ -1547,34 +1680,38 @@ class DecimalField(Field): if isinstance(value, float): if math.isnan(value): raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) return self.context.create_decimal_from_float(value) try: return decimal.Decimal(value) except (decimal.InvalidOperation, TypeError, ValueError): raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def get_db_prep_save(self, value, connection): - return connection.ops.adapt_decimalfield_value(self.to_python(value), self.max_digits, self.decimal_places) + return connection.ops.adapt_decimalfield_value( + self.to_python(value), self.max_digits, self.decimal_places + ) def get_prep_value(self, value): value = super().get_prep_value(value) return self.to_python(value) def formfield(self, **kwargs): - return super().formfield(**{ - 'max_digits': self.max_digits, - 'decimal_places': self.decimal_places, - 'form_class': forms.DecimalField, - **kwargs, - }) + return super().formfield( + **{ + "max_digits": self.max_digits, + "decimal_places": self.decimal_places, + "form_class": forms.DecimalField, + **kwargs, + } + ) class DurationField(Field): @@ -1584,10 +1721,13 @@ class DurationField(Field): Use interval on PostgreSQL, INTERVAL DAY TO SECOND on Oracle, and bigint of microseconds on other databases. """ + empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value has an invalid format. It must be in ' - '[DD] [[HH:]MM:]ss[.uuuuuu] format.') + "invalid": _( + "“%(value)s” value has an invalid format. It must be in " + "[DD] [[HH:]MM:]ss[.uuuuuu] format." + ) } description = _("Duration") @@ -1608,9 +1748,9 @@ class DurationField(Field): return parsed raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def get_db_prep_value(self, value, connection, prepared=False): @@ -1628,13 +1768,15 @@ class DurationField(Field): def value_to_string(self, obj): val = self.value_from_object(obj) - return '' if val is None else duration_string(val) + return "" if val is None else duration_string(val) def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.DurationField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.DurationField, + **kwargs, + } + ) class EmailField(CharField): @@ -1643,7 +1785,7 @@ class EmailField(CharField): def __init__(self, *args, **kwargs): # max_length=254 to be compliant with RFCs 3696 and 5321 - kwargs.setdefault('max_length', 254) + kwargs.setdefault("max_length", 254) super().__init__(*args, **kwargs) def deconstruct(self): @@ -1655,20 +1797,31 @@ class EmailField(CharField): def formfield(self, **kwargs): # As with CharField, this will cause email validation to be performed # twice. - return super().formfield(**{ - 'form_class': forms.EmailField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.EmailField, + **kwargs, + } + ) class FilePathField(Field): description = _("File path") - def __init__(self, verbose_name=None, name=None, path='', match=None, - recursive=False, allow_files=True, allow_folders=False, **kwargs): + def __init__( + self, + verbose_name=None, + name=None, + path="", + match=None, + recursive=False, + allow_files=True, + allow_folders=False, + **kwargs, + ): self.path, self.match, self.recursive = path, match, recursive self.allow_files, self.allow_folders = allow_files, allow_folders - kwargs.setdefault('max_length', 100) + kwargs.setdefault("max_length", 100) super().__init__(verbose_name, name, **kwargs) def check(self, **kwargs): @@ -1683,23 +1836,23 @@ class FilePathField(Field): checks.Error( "FilePathFields must have either 'allow_files' or 'allow_folders' set to True.", obj=self, - id='fields.E140', + id="fields.E140", ) ] return [] def deconstruct(self): name, path, args, kwargs = super().deconstruct() - if self.path != '': - kwargs['path'] = self.path + if self.path != "": + kwargs["path"] = self.path if self.match is not None: - kwargs['match'] = self.match + kwargs["match"] = self.match if self.recursive is not False: - kwargs['recursive'] = self.recursive + kwargs["recursive"] = self.recursive if self.allow_files is not True: - kwargs['allow_files'] = self.allow_files + kwargs["allow_files"] = self.allow_files if self.allow_folders is not False: - kwargs['allow_folders'] = self.allow_folders + kwargs["allow_folders"] = self.allow_folders if kwargs.get("max_length") == 100: del kwargs["max_length"] return name, path, args, kwargs @@ -1711,15 +1864,17 @@ class FilePathField(Field): return str(value) def formfield(self, **kwargs): - return super().formfield(**{ - 'path': self.path() if callable(self.path) else self.path, - 'match': self.match, - 'recursive': self.recursive, - 'form_class': forms.FilePathField, - 'allow_files': self.allow_files, - 'allow_folders': self.allow_folders, - **kwargs, - }) + return super().formfield( + **{ + "path": self.path() if callable(self.path) else self.path, + "match": self.match, + "recursive": self.recursive, + "form_class": forms.FilePathField, + "allow_files": self.allow_files, + "allow_folders": self.allow_folders, + **kwargs, + } + ) def get_internal_type(self): return "FilePathField" @@ -1728,7 +1883,7 @@ class FilePathField(Field): class FloatField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value must be a float.'), + "invalid": _("“%(value)s” value must be a float."), } description = _("Floating point number") @@ -1753,22 +1908,24 @@ class FloatField(Field): return float(value) except (TypeError, ValueError): raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.FloatField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.FloatField, + **kwargs, + } + ) class IntegerField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value must be an integer.'), + "invalid": _("“%(value)s” value must be an integer."), } description = _("Integer") @@ -1782,10 +1939,11 @@ class IntegerField(Field): if self.max_length is not None: return [ checks.Warning( - "'max_length' is ignored when used with %s." % self.__class__.__name__, + "'max_length' is ignored when used with %s." + % self.__class__.__name__, hint="Remove 'max_length' from field", obj=self, - id='fields.W122', + id="fields.W122", ) ] return [] @@ -1799,22 +1957,28 @@ class IntegerField(Field): min_value, max_value = connection.ops.integer_field_range(internal_type) if min_value is not None and not any( ( - isinstance(validator, validators.MinValueValidator) and ( + isinstance(validator, validators.MinValueValidator) + and ( validator.limit_value() if callable(validator.limit_value) else validator.limit_value - ) >= min_value - ) for validator in validators_ + ) + >= min_value + ) + for validator in validators_ ): validators_.append(validators.MinValueValidator(min_value)) if max_value is not None and not any( ( - isinstance(validator, validators.MaxValueValidator) and ( + isinstance(validator, validators.MaxValueValidator) + and ( validator.limit_value() if callable(validator.limit_value) else validator.limit_value - ) <= max_value - ) for validator in validators_ + ) + <= max_value + ) + for validator in validators_ ): validators_.append(validators.MaxValueValidator(max_value)) return validators_ @@ -1840,16 +2004,18 @@ class IntegerField(Field): return int(value) except (TypeError, ValueError): raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.IntegerField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.IntegerField, + **kwargs, + } + ) class BigIntegerField(IntegerField): @@ -1860,39 +2026,41 @@ class BigIntegerField(IntegerField): return "BigIntegerField" def formfield(self, **kwargs): - return super().formfield(**{ - 'min_value': -BigIntegerField.MAX_BIGINT - 1, - 'max_value': BigIntegerField.MAX_BIGINT, - **kwargs, - }) + return super().formfield( + **{ + "min_value": -BigIntegerField.MAX_BIGINT - 1, + "max_value": BigIntegerField.MAX_BIGINT, + **kwargs, + } + ) class SmallIntegerField(IntegerField): - description = _('Small integer') + description = _("Small integer") def get_internal_type(self): - return 'SmallIntegerField' + return "SmallIntegerField" class IPAddressField(Field): empty_strings_allowed = False description = _("IPv4 address") system_check_removed_details = { - 'msg': ( - 'IPAddressField has been removed except for support in ' - 'historical migrations.' + "msg": ( + "IPAddressField has been removed except for support in " + "historical migrations." ), - 'hint': 'Use GenericIPAddressField instead.', - 'id': 'fields.E900', + "hint": "Use GenericIPAddressField instead.", + "id": "fields.E900", } def __init__(self, *args, **kwargs): - kwargs['max_length'] = 15 + kwargs["max_length"] = 15 super().__init__(*args, **kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() - del kwargs['max_length'] + del kwargs["max_length"] return name, path, args, kwargs def get_prep_value(self, value): @@ -1910,14 +2078,23 @@ class GenericIPAddressField(Field): description = _("IP address") default_error_messages = {} - def __init__(self, verbose_name=None, name=None, protocol='both', - unpack_ipv4=False, *args, **kwargs): + def __init__( + self, + verbose_name=None, + name=None, + protocol="both", + unpack_ipv4=False, + *args, + **kwargs, + ): self.unpack_ipv4 = unpack_ipv4 self.protocol = protocol - self.default_validators, invalid_error_message = \ - validators.ip_address_validators(protocol, unpack_ipv4) - self.default_error_messages['invalid'] = invalid_error_message - kwargs['max_length'] = 39 + ( + self.default_validators, + invalid_error_message, + ) = validators.ip_address_validators(protocol, unpack_ipv4) + self.default_error_messages["invalid"] = invalid_error_message + kwargs["max_length"] = 39 super().__init__(verbose_name, name, *args, **kwargs) def check(self, **kwargs): @@ -1927,13 +2104,13 @@ class GenericIPAddressField(Field): ] def _check_blank_and_null_values(self, **kwargs): - if not getattr(self, 'null', False) and getattr(self, 'blank', False): + if not getattr(self, "null", False) and getattr(self, "blank", False): return [ checks.Error( - 'GenericIPAddressFields cannot have blank=True if null=False, ' - 'as blank values are stored as nulls.', + "GenericIPAddressFields cannot have blank=True if null=False, " + "as blank values are stored as nulls.", obj=self, - id='fields.E150', + id="fields.E150", ) ] return [] @@ -1941,11 +2118,11 @@ class GenericIPAddressField(Field): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.unpack_ipv4 is not False: - kwargs['unpack_ipv4'] = self.unpack_ipv4 + kwargs["unpack_ipv4"] = self.unpack_ipv4 if self.protocol != "both": - kwargs['protocol'] = self.protocol + kwargs["protocol"] = self.protocol if kwargs.get("max_length") == 39: - del kwargs['max_length'] + del kwargs["max_length"] return name, path, args, kwargs def get_internal_type(self): @@ -1957,8 +2134,10 @@ class GenericIPAddressField(Field): if not isinstance(value, str): value = str(value) value = value.strip() - if ':' in value: - return clean_ipv6_address(value, self.unpack_ipv4, self.error_messages['invalid']) + if ":" in value: + return clean_ipv6_address( + value, self.unpack_ipv4, self.error_messages["invalid"] + ) return value def get_db_prep_value(self, value, connection, prepared=False): @@ -1970,7 +2149,7 @@ class GenericIPAddressField(Field): value = super().get_prep_value(value) if value is None: return None - if value and ':' in value: + if value and ":" in value: try: return clean_ipv6_address(value, self.unpack_ipv4) except exceptions.ValidationError: @@ -1978,44 +2157,46 @@ class GenericIPAddressField(Field): return str(value) def formfield(self, **kwargs): - return super().formfield(**{ - 'protocol': self.protocol, - 'form_class': forms.GenericIPAddressField, - **kwargs, - }) + return super().formfield( + **{ + "protocol": self.protocol, + "form_class": forms.GenericIPAddressField, + **kwargs, + } + ) class NullBooleanField(BooleanField): default_error_messages = { - 'invalid': _('“%(value)s” value must be either None, True or False.'), - 'invalid_nullable': _('“%(value)s” value must be either None, True or False.'), + "invalid": _("“%(value)s” value must be either None, True or False."), + "invalid_nullable": _("“%(value)s” value must be either None, True or False."), } description = _("Boolean (Either True, False or None)") system_check_removed_details = { - 'msg': ( - 'NullBooleanField is removed except for support in historical ' - 'migrations.' + "msg": ( + "NullBooleanField is removed except for support in historical " + "migrations." ), - 'hint': 'Use BooleanField(null=True) instead.', - 'id': 'fields.E903', + "hint": "Use BooleanField(null=True) instead.", + "id": "fields.E903", } def __init__(self, *args, **kwargs): - kwargs['null'] = True - kwargs['blank'] = True + kwargs["null"] = True + kwargs["blank"] = True super().__init__(*args, **kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() - del kwargs['null'] - del kwargs['blank'] + del kwargs["null"] + del kwargs["blank"] return name, path, args, kwargs class PositiveIntegerRelDbTypeMixin: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) - if not hasattr(cls, 'integer_field_class'): + if not hasattr(cls, "integer_field_class"): cls.integer_field_class = next( ( parent @@ -2041,16 +2222,18 @@ class PositiveIntegerRelDbTypeMixin: class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, BigIntegerField): - description = _('Positive big integer') + description = _("Positive big integer") def get_internal_type(self): - return 'PositiveBigIntegerField' + return "PositiveBigIntegerField" def formfield(self, **kwargs): - return super().formfield(**{ - 'min_value': 0, - **kwargs, - }) + return super().formfield( + **{ + "min_value": 0, + **kwargs, + } + ) class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField): @@ -2060,10 +2243,12 @@ class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField): return "PositiveIntegerField" def formfield(self, **kwargs): - return super().formfield(**{ - 'min_value': 0, - **kwargs, - }) + return super().formfield( + **{ + "min_value": 0, + **kwargs, + } + ) class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, SmallIntegerField): @@ -2073,17 +2258,21 @@ class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, SmallIntegerField return "PositiveSmallIntegerField" def formfield(self, **kwargs): - return super().formfield(**{ - 'min_value': 0, - **kwargs, - }) + return super().formfield( + **{ + "min_value": 0, + **kwargs, + } + ) class SlugField(CharField): default_validators = [validators.validate_slug] description = _("Slug (up to %(max_length)s)") - def __init__(self, *args, max_length=50, db_index=True, allow_unicode=False, **kwargs): + def __init__( + self, *args, max_length=50, db_index=True, allow_unicode=False, **kwargs + ): self.allow_unicode = allow_unicode if self.allow_unicode: self.default_validators = [validators.validate_unicode_slug] @@ -2092,24 +2281,26 @@ class SlugField(CharField): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if kwargs.get("max_length") == 50: - del kwargs['max_length'] + del kwargs["max_length"] if self.db_index is False: - kwargs['db_index'] = False + kwargs["db_index"] = False else: - del kwargs['db_index'] + del kwargs["db_index"] if self.allow_unicode is not False: - kwargs['allow_unicode'] = self.allow_unicode + kwargs["allow_unicode"] = self.allow_unicode return name, path, args, kwargs def get_internal_type(self): return "SlugField" def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.SlugField, - 'allow_unicode': self.allow_unicode, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.SlugField, + "allow_unicode": self.allow_unicode, + **kwargs, + } + ) class TextField(Field): @@ -2120,7 +2311,7 @@ class TextField(Field): self.db_collation = db_collation def check(self, **kwargs): - databases = kwargs.get('databases') or [] + databases = kwargs.get("databases") or [] return [ *super().check(**kwargs), *self._check_db_collation(databases), @@ -2133,16 +2324,17 @@ class TextField(Field): continue connection = connections[db] if not ( - self.db_collation is None or - 'supports_collation_on_textfield' in self.model._meta.required_db_features or - connection.features.supports_collation_on_textfield + self.db_collation is None + or "supports_collation_on_textfield" + in self.model._meta.required_db_features + or connection.features.supports_collation_on_textfield ): errors.append( checks.Error( - '%s does not support a database collation on ' - 'TextFields.' % connection.display_name, + "%s does not support a database collation on " + "TextFields." % connection.display_name, obj=self, - id='fields.E190', + id="fields.E190", ), ) return errors @@ -2163,35 +2355,42 @@ class TextField(Field): # Passing max_length to forms.CharField means that the value's length # will be validated twice. This is considered acceptable since we want # the value in the form field (to pass into widget for example). - return super().formfield(**{ - 'max_length': self.max_length, - **({} if self.choices is not None else {'widget': forms.Textarea}), - **kwargs, - }) + return super().formfield( + **{ + "max_length": self.max_length, + **({} if self.choices is not None else {"widget": forms.Textarea}), + **kwargs, + } + ) def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.db_collation: - kwargs['db_collation'] = self.db_collation + kwargs["db_collation"] = self.db_collation return name, path, args, kwargs class TimeField(DateTimeCheckMixin, Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _('“%(value)s” value has an invalid format. It must be in ' - 'HH:MM[:ss[.uuuuuu]] format.'), - 'invalid_time': _('“%(value)s” value has the correct format ' - '(HH:MM[:ss[.uuuuuu]]) but it is an invalid time.'), + "invalid": _( + "“%(value)s” value has an invalid format. It must be in " + "HH:MM[:ss[.uuuuuu]] format." + ), + "invalid_time": _( + "“%(value)s” value has the correct format " + "(HH:MM[:ss[.uuuuuu]]) but it is an invalid time." + ), } description = _("Time") - def __init__(self, verbose_name=None, name=None, auto_now=False, - auto_now_add=False, **kwargs): + def __init__( + self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs + ): self.auto_now, self.auto_now_add = auto_now, auto_now_add if auto_now or auto_now_add: - kwargs['editable'] = False - kwargs['blank'] = True + kwargs["editable"] = False + kwargs["blank"] = True super().__init__(verbose_name, name, **kwargs) def _check_fix_default_value(self): @@ -2223,8 +2422,8 @@ class TimeField(DateTimeCheckMixin, Field): if self.auto_now_add is not False: kwargs["auto_now_add"] = self.auto_now_add if self.auto_now or self.auto_now_add: - del kwargs['blank'] - del kwargs['editable'] + del kwargs["blank"] + del kwargs["editable"] return name, path, args, kwargs def get_internal_type(self): @@ -2247,15 +2446,15 @@ class TimeField(DateTimeCheckMixin, Field): return parsed except ValueError: raise exceptions.ValidationError( - self.error_messages['invalid_time'], - code='invalid_time', - params={'value': value}, + self.error_messages["invalid_time"], + code="invalid_time", + params={"value": value}, ) raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def pre_save(self, model_instance, add): @@ -2278,13 +2477,15 @@ class TimeField(DateTimeCheckMixin, Field): def value_to_string(self, obj): val = self.value_from_object(obj) - return '' if val is None else val.isoformat() + return "" if val is None else val.isoformat() def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.TimeField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.TimeField, + **kwargs, + } + ) class URLField(CharField): @@ -2292,30 +2493,32 @@ class URLField(CharField): description = _("URL") def __init__(self, verbose_name=None, name=None, **kwargs): - kwargs.setdefault('max_length', 200) + kwargs.setdefault("max_length", 200) super().__init__(verbose_name, name, **kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() if kwargs.get("max_length") == 200: - del kwargs['max_length'] + del kwargs["max_length"] return name, path, args, kwargs def formfield(self, **kwargs): # As with CharField, this will cause URL validation to be performed # twice. - return super().formfield(**{ - 'form_class': forms.URLField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.URLField, + **kwargs, + } + ) class BinaryField(Field): description = _("Raw binary data") - empty_values = [None, b''] + empty_values = [None, b""] def __init__(self, *args, **kwargs): - kwargs.setdefault('editable', False) + kwargs.setdefault("editable", False) super().__init__(*args, **kwargs) if self.max_length is not None: self.validators.append(validators.MaxLengthValidator(self.max_length)) @@ -2330,7 +2533,7 @@ class BinaryField(Field): "BinaryField's default cannot be a string. Use bytes " "content instead.", obj=self, - id='fields.E170', + id="fields.E170", ) ] return [] @@ -2338,9 +2541,9 @@ class BinaryField(Field): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.editable: - kwargs['editable'] = True + kwargs["editable"] = True else: - del kwargs['editable'] + del kwargs["editable"] return name, path, args, kwargs def get_internal_type(self): @@ -2353,8 +2556,8 @@ class BinaryField(Field): if self.has_default() and not callable(self.default): return self.default default = super().get_default() - if default == '': - return b'' + if default == "": + return b"" return default def get_db_prep_value(self, value, connection, prepared=False): @@ -2365,29 +2568,29 @@ class BinaryField(Field): def value_to_string(self, obj): """Binary data is serialized as base64""" - return b64encode(self.value_from_object(obj)).decode('ascii') + return b64encode(self.value_from_object(obj)).decode("ascii") def to_python(self, value): # If it's a string, it should be base64-encoded data if isinstance(value, str): - return memoryview(b64decode(value.encode('ascii'))) + return memoryview(b64decode(value.encode("ascii"))) return value class UUIDField(Field): default_error_messages = { - 'invalid': _('“%(value)s” is not a valid UUID.'), + "invalid": _("“%(value)s” is not a valid UUID."), } - description = _('Universally unique identifier') + description = _("Universally unique identifier") empty_strings_allowed = False def __init__(self, verbose_name=None, **kwargs): - kwargs['max_length'] = 32 + kwargs["max_length"] = 32 super().__init__(verbose_name, **kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() - del kwargs['max_length'] + del kwargs["max_length"] return name, path, args, kwargs def get_internal_type(self): @@ -2409,29 +2612,31 @@ class UUIDField(Field): def to_python(self, value): if value is not None and not isinstance(value, uuid.UUID): - input_form = 'int' if isinstance(value, int) else 'hex' + input_form = "int" if isinstance(value, int) else "hex" try: return uuid.UUID(**{input_form: value}) except (AttributeError, ValueError): raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) return value def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.UUIDField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.UUIDField, + **kwargs, + } + ) class AutoFieldMixin: db_returning = True def __init__(self, *args, **kwargs): - kwargs['blank'] = True + kwargs["blank"] = True super().__init__(*args, **kwargs) def check(self, **kwargs): @@ -2444,9 +2649,9 @@ class AutoFieldMixin: if not self.primary_key: return [ checks.Error( - 'AutoFields must set primary_key=True.', + "AutoFields must set primary_key=True.", obj=self, - id='fields.E100', + id="fields.E100", ), ] else: @@ -2454,8 +2659,8 @@ class AutoFieldMixin: def deconstruct(self): name, path, args, kwargs = super().deconstruct() - del kwargs['blank'] - kwargs['primary_key'] = True + del kwargs["blank"] + kwargs["primary_key"] = True return name, path, args, kwargs def validate(self, value, model_instance): @@ -2502,34 +2707,35 @@ class AutoFieldMeta(type): return (BigAutoField, SmallAutoField) def __instancecheck__(self, instance): - return isinstance(instance, self._subclasses) or super().__instancecheck__(instance) + return isinstance(instance, self._subclasses) or super().__instancecheck__( + instance + ) def __subclasscheck__(self, subclass): - return issubclass(subclass, self._subclasses) or super().__subclasscheck__(subclass) + return issubclass(subclass, self._subclasses) or super().__subclasscheck__( + subclass + ) class AutoField(AutoFieldMixin, IntegerField, metaclass=AutoFieldMeta): - def get_internal_type(self): - return 'AutoField' + return "AutoField" def rel_db_type(self, connection): return IntegerField().db_type(connection=connection) class BigAutoField(AutoFieldMixin, BigIntegerField): - def get_internal_type(self): - return 'BigAutoField' + return "BigAutoField" def rel_db_type(self, connection): return BigIntegerField().db_type(connection=connection) class SmallAutoField(AutoFieldMixin, SmallIntegerField): - def get_internal_type(self): - return 'SmallAutoField' + return "SmallAutoField" def rel_db_type(self, connection): return SmallIntegerField().db_type(connection=connection) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 18900f7b85..33a1176ed6 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -24,7 +24,7 @@ class FieldFile(File): def __eq__(self, other): # Older code may be expecting FileField values to be simple strings. # By overriding the == operator, it can remain backwards compatibility. - if hasattr(other, 'name'): + if hasattr(other, "name"): return self.name == other.name return self.name == other @@ -37,12 +37,14 @@ class FieldFile(File): def _require_file(self): if not self: - raise ValueError("The '%s' attribute has no file associated with it." % self.field.name) + raise ValueError( + "The '%s' attribute has no file associated with it." % self.field.name + ) def _get_file(self): self._require_file() - if getattr(self, '_file', None) is None: - self._file = self.storage.open(self.name, 'rb') + if getattr(self, "_file", None) is None: + self._file = self.storage.open(self.name, "rb") return self._file def _set_file(self, file): @@ -70,13 +72,14 @@ class FieldFile(File): return self.file.size return self.storage.size(self.name) - def open(self, mode='rb'): + def open(self, mode="rb"): self._require_file() - if getattr(self, '_file', None) is None: + if getattr(self, "_file", None) is None: self.file = self.storage.open(self.name, mode) else: self.file.open(mode) return self + # open() doesn't alter the file's contents, but it does reset the pointer open.alters_data = True @@ -93,6 +96,7 @@ class FieldFile(File): # Save the object because it has changed, unless save is False if save: self.instance.save() + save.alters_data = True def delete(self, save=True): @@ -100,7 +104,7 @@ class FieldFile(File): return # Only close the file if it's already open, which we know by the # presence of self._file - if hasattr(self, '_file'): + if hasattr(self, "_file"): self.close() del self.file @@ -112,15 +116,16 @@ class FieldFile(File): if save: self.instance.save() + delete.alters_data = True @property def closed(self): - file = getattr(self, '_file', None) + file = getattr(self, "_file", None) return file is None or file.closed def close(self): - file = getattr(self, '_file', None) + file = getattr(self, "_file", None) if file is not None: file.close() @@ -129,12 +134,12 @@ class FieldFile(File): # the file's name. Everything else will be restored later, by # FileDescriptor below. return { - 'name': self.name, - 'closed': False, - '_committed': True, - '_file': None, - 'instance': self.instance, - 'field': self.field, + "name": self.name, + "closed": False, + "_committed": True, + "_file": None, + "instance": self.instance, + "field": self.field, } def __setstate__(self, state): @@ -156,6 +161,7 @@ class FileDescriptor(DeferredAttribute): >>> with open('/path/to/hello.world') as f: ... instance.file = File(f) """ + def __get__(self, instance, cls=None): if instance is None: return self @@ -198,7 +204,7 @@ class FileDescriptor(DeferredAttribute): # Finally, because of the (some would say boneheaded) way pickle works, # the underlying FieldFile might not actually itself have an associated # file. So we need to reset the details of the FieldFile in those cases. - elif isinstance(file, FieldFile) and not hasattr(file, 'field'): + elif isinstance(file, FieldFile) and not hasattr(file, "field"): file.instance = instance file.field = self.field file.storage = self.field.storage @@ -225,8 +231,10 @@ class FileField(Field): description = _("File") - def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): - self._primary_key_set_explicitly = 'primary_key' in kwargs + def __init__( + self, verbose_name=None, name=None, upload_to="", storage=None, **kwargs + ): + self._primary_key_set_explicitly = "primary_key" in kwargs self.storage = storage or default_storage if callable(self.storage): @@ -236,11 +244,15 @@ class FileField(Field): if not isinstance(self.storage, Storage): raise TypeError( "%s.storage must be a subclass/instance of %s.%s" - % (self.__class__.__qualname__, Storage.__module__, Storage.__qualname__) + % ( + self.__class__.__qualname__, + Storage.__module__, + Storage.__qualname__, + ) ) self.upload_to = upload_to - kwargs.setdefault('max_length', 100) + kwargs.setdefault("max_length", 100) super().__init__(verbose_name, name, **kwargs) def check(self, **kwargs): @@ -254,23 +266,24 @@ class FileField(Field): if self._primary_key_set_explicitly: return [ checks.Error( - "'primary_key' is not a valid argument for a %s." % self.__class__.__name__, + "'primary_key' is not a valid argument for a %s." + % self.__class__.__name__, obj=self, - id='fields.E201', + id="fields.E201", ) ] else: return [] def _check_upload_to(self): - if isinstance(self.upload_to, str) and self.upload_to.startswith('/'): + if isinstance(self.upload_to, str) and self.upload_to.startswith("/"): return [ checks.Error( "%s's 'upload_to' argument must be a relative path, not an " "absolute path." % self.__class__.__name__, obj=self, - id='fields.E202', - hint='Remove the leading slash.', + id="fields.E202", + hint="Remove the leading slash.", ) ] else: @@ -280,9 +293,9 @@ class FileField(Field): name, path, args, kwargs = super().deconstruct() if kwargs.get("max_length") == 100: del kwargs["max_length"] - kwargs['upload_to'] = self.upload_to + kwargs["upload_to"] = self.upload_to if self.storage is not default_storage: - kwargs['storage'] = getattr(self, '_storage_callable', self.storage) + kwargs["storage"] = getattr(self, "_storage_callable", self.storage) return name, path, args, kwargs def get_internal_type(self): @@ -329,14 +342,16 @@ class FileField(Field): if data is not None: # This value will be converted to str and stored in the # database, so leaving False as-is is not acceptable. - setattr(instance, self.name, data or '') + setattr(instance, self.name, data or "") def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.FileField, - 'max_length': self.max_length, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.FileField, + "max_length": self.max_length, + **kwargs, + } + ) class ImageFileDescriptor(FileDescriptor): @@ -344,6 +359,7 @@ class ImageFileDescriptor(FileDescriptor): Just like the FileDescriptor, but for ImageFields. The only difference is assigning the width/height to the width_field/height_field, if appropriate. """ + def __set__(self, instance, value): previous_file = instance.__dict__.get(self.field.attname) super().__set__(instance, value) @@ -364,7 +380,7 @@ class ImageFileDescriptor(FileDescriptor): class ImageFieldFile(ImageFile, FieldFile): def delete(self, save=True): # Clear the image dimensions cache - if hasattr(self, '_dimensions_cache'): + if hasattr(self, "_dimensions_cache"): del self._dimensions_cache super().delete(save) @@ -374,7 +390,14 @@ class ImageField(FileField): descriptor_class = ImageFileDescriptor description = _("Image") - def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): + def __init__( + self, + verbose_name=None, + name=None, + width_field=None, + height_field=None, + **kwargs, + ): self.width_field, self.height_field = width_field, height_field super().__init__(verbose_name, name, **kwargs) @@ -390,11 +413,13 @@ class ImageField(FileField): except ImportError: return [ checks.Error( - 'Cannot use ImageField because Pillow is not installed.', - hint=('Get Pillow at https://pypi.org/project/Pillow/ ' - 'or run command "python -m pip install Pillow".'), + "Cannot use ImageField because Pillow is not installed.", + hint=( + "Get Pillow at https://pypi.org/project/Pillow/ " + 'or run command "python -m pip install Pillow".' + ), obj=self, - id='fields.E210', + id="fields.E210", ) ] else: @@ -403,9 +428,9 @@ class ImageField(FileField): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.width_field: - kwargs['width_field'] = self.width_field + kwargs["width_field"] = self.width_field if self.height_field: - kwargs['height_field'] = self.height_field + kwargs["height_field"] = self.height_field return name, path, args, kwargs def contribute_to_class(self, cls, name, **kwargs): @@ -445,9 +470,9 @@ class ImageField(FileField): if not file and not force: return - dimension_fields_filled = not( - (self.width_field and not getattr(instance, self.width_field)) or - (self.height_field and not getattr(instance, self.height_field)) + dimension_fields_filled = not ( + (self.width_field and not getattr(instance, self.width_field)) + or (self.height_field and not getattr(instance, self.height_field)) ) # When both dimension fields have values, we are most likely loading # data from the database or updating an image field that already had @@ -475,7 +500,9 @@ class ImageField(FileField): setattr(instance, self.height_field, height) def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.ImageField, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.ImageField, + **kwargs, + } + ) diff --git a/django/db/models/fields/json.py b/django/db/models/fields/json.py index efb4e2f6ed..fdca700c9d 100644 --- a/django/db/models/fields/json.py +++ b/django/db/models/fields/json.py @@ -10,32 +10,36 @@ from django.utils.translation import gettext_lazy as _ from . import Field from .mixins import CheckFieldDefaultMixin -__all__ = ['JSONField'] +__all__ = ["JSONField"] class JSONField(CheckFieldDefaultMixin, Field): empty_strings_allowed = False - description = _('A JSON object') + description = _("A JSON object") default_error_messages = { - 'invalid': _('Value must be valid JSON.'), + "invalid": _("Value must be valid JSON."), } - _default_hint = ('dict', '{}') + _default_hint = ("dict", "{}") def __init__( - self, verbose_name=None, name=None, encoder=None, decoder=None, + self, + verbose_name=None, + name=None, + encoder=None, + decoder=None, **kwargs, ): if encoder and not callable(encoder): - raise ValueError('The encoder parameter must be a callable object.') + raise ValueError("The encoder parameter must be a callable object.") if decoder and not callable(decoder): - raise ValueError('The decoder parameter must be a callable object.') + raise ValueError("The decoder parameter must be a callable object.") self.encoder = encoder self.decoder = decoder super().__init__(verbose_name, name, **kwargs) def check(self, **kwargs): errors = super().check(**kwargs) - databases = kwargs.get('databases') or [] + databases = kwargs.get("databases") or [] errors.extend(self._check_supported(databases)) return errors @@ -46,20 +50,19 @@ class JSONField(CheckFieldDefaultMixin, Field): continue connection = connections[db] if ( - self.model._meta.required_db_vendor and - self.model._meta.required_db_vendor != connection.vendor + self.model._meta.required_db_vendor + and self.model._meta.required_db_vendor != connection.vendor ): continue if not ( - 'supports_json_field' in self.model._meta.required_db_features or - connection.features.supports_json_field + "supports_json_field" in self.model._meta.required_db_features + or connection.features.supports_json_field ): errors.append( checks.Error( - '%s does not support JSONFields.' - % connection.display_name, + "%s does not support JSONFields." % connection.display_name, obj=self.model, - id='fields.E180', + id="fields.E180", ) ) return errors @@ -67,9 +70,9 @@ class JSONField(CheckFieldDefaultMixin, Field): def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.encoder is not None: - kwargs['encoder'] = self.encoder + kwargs["encoder"] = self.encoder if self.decoder is not None: - kwargs['decoder'] = self.decoder + kwargs["decoder"] = self.decoder return name, path, args, kwargs def from_db_value(self, value, expression, connection): @@ -85,7 +88,7 @@ class JSONField(CheckFieldDefaultMixin, Field): return value def get_internal_type(self): - return 'JSONField' + return "JSONField" def get_prep_value(self, value): if value is None: @@ -104,64 +107,66 @@ class JSONField(CheckFieldDefaultMixin, Field): json.dumps(value, cls=self.encoder) except TypeError: raise exceptions.ValidationError( - self.error_messages['invalid'], - code='invalid', - params={'value': value}, + self.error_messages["invalid"], + code="invalid", + params={"value": value}, ) def value_to_string(self, obj): return self.value_from_object(obj) def formfield(self, **kwargs): - return super().formfield(**{ - 'form_class': forms.JSONField, - 'encoder': self.encoder, - 'decoder': self.decoder, - **kwargs, - }) + return super().formfield( + **{ + "form_class": forms.JSONField, + "encoder": self.encoder, + "decoder": self.decoder, + **kwargs, + } + ) def compile_json_path(key_transforms, include_root=True): - path = ['$'] if include_root else [] + path = ["$"] if include_root else [] for key_transform in key_transforms: try: num = int(key_transform) except ValueError: # non-integer - path.append('.') + path.append(".") path.append(json.dumps(key_transform)) else: - path.append('[%s]' % num) - return ''.join(path) + path.append("[%s]" % num) + return "".join(path) class DataContains(PostgresOperatorLookup): - lookup_name = 'contains' - postgres_operator = '@>' + lookup_name = "contains" + postgres_operator = "@>" def as_sql(self, compiler, connection): if not connection.features.supports_json_field_contains: raise NotSupportedError( - 'contains lookup is not supported on this database backend.' + "contains lookup is not supported on this database backend." ) lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) params = tuple(lhs_params) + tuple(rhs_params) - return 'JSON_CONTAINS(%s, %s)' % (lhs, rhs), params + return "JSON_CONTAINS(%s, %s)" % (lhs, rhs), params class ContainedBy(PostgresOperatorLookup): - lookup_name = 'contained_by' - postgres_operator = '<@' + lookup_name = "contained_by" + postgres_operator = "<@" def as_sql(self, compiler, connection): if not connection.features.supports_json_field_contains: raise NotSupportedError( - 'contained_by lookup is not supported on this database backend.' + "contained_by lookup is not supported on this database backend." ) lhs, lhs_params = self.process_lhs(compiler, connection) rhs, rhs_params = self.process_rhs(compiler, connection) params = tuple(rhs_params) + tuple(lhs_params) - return 'JSON_CONTAINS(%s, %s)' % (rhs, lhs), params + return "JSON_CONTAINS(%s, %s)" % (rhs, lhs), params class HasKeyLookup(PostgresOperatorLookup): @@ -170,11 +175,13 @@ class HasKeyLookup(PostgresOperatorLookup): def as_sql(self, compiler, connection, template=None): # Process JSON path from the left-hand side. if isinstance(self.lhs, KeyTransform): - lhs, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(compiler, connection) + lhs, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs( + compiler, connection + ) lhs_json_path = compile_json_path(lhs_key_transforms) else: lhs, lhs_params = self.process_lhs(compiler, connection) - lhs_json_path = '$' + lhs_json_path = "$" sql = template % lhs # Process JSON path from the right-hand side. rhs = self.rhs @@ -186,20 +193,27 @@ class HasKeyLookup(PostgresOperatorLookup): *_, rhs_key_transforms = key.preprocess_lhs(compiler, connection) else: rhs_key_transforms = [key] - rhs_params.append('%s%s' % ( - lhs_json_path, - compile_json_path(rhs_key_transforms, include_root=False), - )) + rhs_params.append( + "%s%s" + % ( + lhs_json_path, + compile_json_path(rhs_key_transforms, include_root=False), + ) + ) # Add condition for each key. if self.logical_operator: - sql = '(%s)' % self.logical_operator.join([sql] * len(rhs_params)) + sql = "(%s)" % self.logical_operator.join([sql] * len(rhs_params)) return sql, tuple(lhs_params) + tuple(rhs_params) def as_mysql(self, compiler, connection): - return self.as_sql(compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %%s)") + return self.as_sql( + compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %%s)" + ) def as_oracle(self, compiler, connection): - sql, params = self.as_sql(compiler, connection, template="JSON_EXISTS(%s, '%%s')") + sql, params = self.as_sql( + compiler, connection, template="JSON_EXISTS(%s, '%%s')" + ) # Add paths directly into SQL because path expressions cannot be passed # as bind variables on Oracle. return sql % tuple(params), [] @@ -213,28 +227,30 @@ class HasKeyLookup(PostgresOperatorLookup): return super().as_postgresql(compiler, connection) def as_sqlite(self, compiler, connection): - return self.as_sql(compiler, connection, template='JSON_TYPE(%s, %%s) IS NOT NULL') + return self.as_sql( + compiler, connection, template="JSON_TYPE(%s, %%s) IS NOT NULL" + ) class HasKey(HasKeyLookup): - lookup_name = 'has_key' - postgres_operator = '?' + lookup_name = "has_key" + postgres_operator = "?" prepare_rhs = False class HasKeys(HasKeyLookup): - lookup_name = 'has_keys' - postgres_operator = '?&' - logical_operator = ' AND ' + lookup_name = "has_keys" + postgres_operator = "?&" + logical_operator = " AND " def get_prep_lookup(self): return [str(item) for item in self.rhs] class HasAnyKeys(HasKeys): - lookup_name = 'has_any_keys' - postgres_operator = '?|' - logical_operator = ' OR ' + lookup_name = "has_any_keys" + postgres_operator = "?|" + logical_operator = " OR " class CaseInsensitiveMixin: @@ -244,16 +260,17 @@ class CaseInsensitiveMixin: Because utf8mb4_bin is a binary collation, comparison of JSON values is case-sensitive. """ + def process_lhs(self, compiler, connection): lhs, lhs_params = super().process_lhs(compiler, connection) - if connection.vendor == 'mysql': - return 'LOWER(%s)' % lhs, lhs_params + if connection.vendor == "mysql": + return "LOWER(%s)" % lhs, lhs_params return lhs, lhs_params def process_rhs(self, compiler, connection): rhs, rhs_params = super().process_rhs(compiler, connection) - if connection.vendor == 'mysql': - return 'LOWER(%s)' % rhs, rhs_params + if connection.vendor == "mysql": + return "LOWER(%s)" % rhs, rhs_params return rhs, rhs_params @@ -263,9 +280,9 @@ class JSONExact(lookups.Exact): def process_rhs(self, compiler, connection): rhs, rhs_params = super().process_rhs(compiler, connection) # Treat None lookup values as null. - if rhs == '%s' and rhs_params == [None]: - rhs_params = ['null'] - if connection.vendor == 'mysql': + if rhs == "%s" and rhs_params == [None]: + rhs_params = ["null"] + if connection.vendor == "mysql": func = ["JSON_EXTRACT(%s, '$')"] * len(rhs_params) rhs = rhs % tuple(func) return rhs, rhs_params @@ -285,8 +302,8 @@ JSONField.register_lookup(JSONIContains) class KeyTransform(Transform): - postgres_operator = '->' - postgres_nested_operator = '#>' + postgres_operator = "->" + postgres_nested_operator = "#>" def __init__(self, key_name, *args, **kwargs): super().__init__(*args, **kwargs) @@ -299,41 +316,41 @@ class KeyTransform(Transform): key_transforms.insert(0, previous.key_name) previous = previous.lhs lhs, params = compiler.compile(previous) - if connection.vendor == 'oracle': + if connection.vendor == "oracle": # Escape string-formatting. - key_transforms = [key.replace('%', '%%') for key in key_transforms] + key_transforms = [key.replace("%", "%%") for key in key_transforms] return lhs, params, key_transforms def as_mysql(self, compiler, connection): lhs, params, key_transforms = self.preprocess_lhs(compiler, connection) json_path = compile_json_path(key_transforms) - return 'JSON_EXTRACT(%s, %%s)' % lhs, tuple(params) + (json_path,) + return "JSON_EXTRACT(%s, %%s)" % lhs, tuple(params) + (json_path,) def as_oracle(self, compiler, connection): lhs, params, key_transforms = self.preprocess_lhs(compiler, connection) json_path = compile_json_path(key_transforms) return ( - "COALESCE(JSON_QUERY(%s, '%s'), JSON_VALUE(%s, '%s'))" % - ((lhs, json_path) * 2) + "COALESCE(JSON_QUERY(%s, '%s'), JSON_VALUE(%s, '%s'))" + % ((lhs, json_path) * 2) ), tuple(params) * 2 def as_postgresql(self, compiler, connection): lhs, params, key_transforms = self.preprocess_lhs(compiler, connection) if len(key_transforms) > 1: - sql = '(%s %s %%s)' % (lhs, self.postgres_nested_operator) + sql = "(%s %s %%s)" % (lhs, self.postgres_nested_operator) return sql, tuple(params) + (key_transforms,) try: lookup = int(self.key_name) except ValueError: lookup = self.key_name - return '(%s %s %%s)' % (lhs, self.postgres_operator), tuple(params) + (lookup,) + return "(%s %s %%s)" % (lhs, self.postgres_operator), tuple(params) + (lookup,) def as_sqlite(self, compiler, connection): lhs, params, key_transforms = self.preprocess_lhs(compiler, connection) json_path = compile_json_path(key_transforms) - datatype_values = ','.join([ - repr(datatype) for datatype in connection.ops.jsonfield_datatype_values - ]) + datatype_values = ",".join( + [repr(datatype) for datatype in connection.ops.jsonfield_datatype_values] + ) return ( "(CASE WHEN JSON_TYPE(%s, %%s) IN (%s) " "THEN JSON_TYPE(%s, %%s) ELSE JSON_EXTRACT(%s, %%s) END)" @@ -341,8 +358,8 @@ class KeyTransform(Transform): class KeyTextTransform(KeyTransform): - postgres_operator = '->>' - postgres_nested_operator = '#>>' + postgres_operator = "->>" + postgres_nested_operator = "#>>" class KeyTransformTextLookupMixin: @@ -352,14 +369,16 @@ class KeyTransformTextLookupMixin: key values to text and performing the lookup on the resulting representation. """ + def __init__(self, key_transform, *args, **kwargs): if not isinstance(key_transform, KeyTransform): raise TypeError( - 'Transform should be an instance of KeyTransform in order to ' - 'use this lookup.' + "Transform should be an instance of KeyTransform in order to " + "use this lookup." ) key_text_transform = KeyTextTransform( - key_transform.key_name, *key_transform.source_expressions, + key_transform.key_name, + *key_transform.source_expressions, **key_transform.extra, ) super().__init__(key_text_transform, *args, **kwargs) @@ -376,12 +395,12 @@ class KeyTransformIsNull(lookups.IsNull): return sql, params # Column doesn't have a key or IS NULL. lhs, lhs_params, _ = self.lhs.preprocess_lhs(compiler, connection) - return '(NOT %s OR %s IS NULL)' % (sql, lhs), tuple(params) + tuple(lhs_params) + return "(NOT %s OR %s IS NULL)" % (sql, lhs), tuple(params) + tuple(lhs_params) def as_sqlite(self, compiler, connection): - template = 'JSON_TYPE(%s, %%s) IS NULL' + template = "JSON_TYPE(%s, %%s) IS NULL" if not self.rhs: - template = 'JSON_TYPE(%s, %%s) IS NOT NULL' + template = "JSON_TYPE(%s, %%s) IS NOT NULL" return HasKey(self.lhs.lhs, self.lhs.key_name).as_sql( compiler, connection, @@ -392,26 +411,29 @@ class KeyTransformIsNull(lookups.IsNull): class KeyTransformIn(lookups.In): def resolve_expression_parameter(self, compiler, connection, sql, param): sql, params = super().resolve_expression_parameter( - compiler, connection, sql, param, + compiler, + connection, + sql, + param, ) if ( - not hasattr(param, 'as_sql') and - not connection.features.has_native_json_field + not hasattr(param, "as_sql") + and not connection.features.has_native_json_field ): - if connection.vendor == 'oracle': + if connection.vendor == "oracle": value = json.loads(param) sql = "%s(JSON_OBJECT('value' VALUE %%s FORMAT JSON), '$.value')" if isinstance(value, (list, dict)): - sql = sql % 'JSON_QUERY' + sql = sql % "JSON_QUERY" else: - sql = sql % 'JSON_VALUE' - elif connection.vendor == 'mysql' or ( - connection.vendor == 'sqlite' and - params[0] not in connection.ops.jsonfield_datatype_values + sql = sql % "JSON_VALUE" + elif connection.vendor == "mysql" or ( + connection.vendor == "sqlite" + and params[0] not in connection.ops.jsonfield_datatype_values ): sql = "JSON_EXTRACT(%s, '$')" - if connection.vendor == 'mysql' and connection.mysql_is_mariadb: - sql = 'JSON_UNQUOTE(%s)' % sql + if connection.vendor == "mysql" and connection.mysql_is_mariadb: + sql = "JSON_UNQUOTE(%s)" % sql return sql, params @@ -420,21 +442,21 @@ class KeyTransformExact(JSONExact): if isinstance(self.rhs, KeyTransform): return super(lookups.Exact, self).process_rhs(compiler, connection) rhs, rhs_params = super().process_rhs(compiler, connection) - if connection.vendor == 'oracle': + if connection.vendor == "oracle": func = [] sql = "%s(JSON_OBJECT('value' VALUE %%s FORMAT JSON), '$.value')" for value in rhs_params: value = json.loads(value) if isinstance(value, (list, dict)): - func.append(sql % 'JSON_QUERY') + func.append(sql % "JSON_QUERY") else: - func.append(sql % 'JSON_VALUE') + func.append(sql % "JSON_VALUE") rhs = rhs % tuple(func) - elif connection.vendor == 'sqlite': + elif connection.vendor == "sqlite": func = [] for value in rhs_params: if value in connection.ops.jsonfield_datatype_values: - func.append('%s') + func.append("%s") else: func.append("JSON_EXTRACT(%s, '$')") rhs = rhs % tuple(func) @@ -442,24 +464,28 @@ class KeyTransformExact(JSONExact): def as_oracle(self, compiler, connection): rhs, rhs_params = super().process_rhs(compiler, connection) - if rhs_params == ['null']: + if rhs_params == ["null"]: # Field has key and it's NULL. has_key_expr = HasKey(self.lhs.lhs, self.lhs.key_name) has_key_sql, has_key_params = has_key_expr.as_oracle(compiler, connection) - is_null_expr = self.lhs.get_lookup('isnull')(self.lhs, True) + is_null_expr = self.lhs.get_lookup("isnull")(self.lhs, True) is_null_sql, is_null_params = is_null_expr.as_sql(compiler, connection) return ( - '%s AND %s' % (has_key_sql, is_null_sql), + "%s AND %s" % (has_key_sql, is_null_sql), tuple(has_key_params) + tuple(is_null_params), ) return super().as_sql(compiler, connection) -class KeyTransformIExact(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IExact): +class KeyTransformIExact( + CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IExact +): pass -class KeyTransformIContains(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IContains): +class KeyTransformIContains( + CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IContains +): pass @@ -467,7 +493,9 @@ class KeyTransformStartsWith(KeyTransformTextLookupMixin, lookups.StartsWith): pass -class KeyTransformIStartsWith(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IStartsWith): +class KeyTransformIStartsWith( + CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IStartsWith +): pass @@ -475,7 +503,9 @@ class KeyTransformEndsWith(KeyTransformTextLookupMixin, lookups.EndsWith): pass -class KeyTransformIEndsWith(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IEndsWith): +class KeyTransformIEndsWith( + CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IEndsWith +): pass @@ -483,7 +513,9 @@ class KeyTransformRegex(KeyTransformTextLookupMixin, lookups.Regex): pass -class KeyTransformIRegex(CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IRegex): +class KeyTransformIRegex( + CaseInsensitiveMixin, KeyTransformTextLookupMixin, lookups.IRegex +): pass @@ -530,7 +562,6 @@ KeyTransform.register_lookup(KeyTransformGte) class KeyTransformFactory: - def __init__(self, key_name): self.key_name = key_name diff --git a/django/db/models/fields/mixins.py b/django/db/models/fields/mixins.py index 3afa8d9304..e7f282210e 100644 --- a/django/db/models/fields/mixins.py +++ b/django/db/models/fields/mixins.py @@ -29,22 +29,25 @@ class FieldCacheMixin: class CheckFieldDefaultMixin: - _default_hint = ('<valid default>', '<invalid default>') + _default_hint = ("<valid default>", "<invalid default>") def _check_default(self): - if self.has_default() and self.default is not None and not callable(self.default): + if ( + self.has_default() + and self.default is not None + and not callable(self.default) + ): return [ checks.Warning( "%s default should be a callable instead of an instance " - "so that it's not shared between all field instances." % ( - self.__class__.__name__, - ), + "so that it's not shared between all field instances." + % (self.__class__.__name__,), hint=( - 'Use a callable instead, e.g., use `%s` instead of ' - '`%s`.' % self._default_hint + "Use a callable instead, e.g., use `%s` instead of " + "`%s`." % self._default_hint ), obj=self, - id='fields.E010', + id="fields.E010", ) ] else: diff --git a/django/db/models/fields/proxy.py b/django/db/models/fields/proxy.py index 0ecf04a333..ac02e47a25 100644 --- a/django/db/models/fields/proxy.py +++ b/django/db/models/fields/proxy.py @@ -13,6 +13,6 @@ class OrderWrt(fields.IntegerField): """ def __init__(self, *args, **kwargs): - kwargs['name'] = '_order' - kwargs['editable'] = False + kwargs["name"] = "_order" + kwargs["editable"] = False super().__init__(*args, **kwargs) 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): diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 9c50ef16ce..3f67ed8166 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -74,7 +74,9 @@ from django.utils.functional import cached_property class ForeignKeyDeferredAttribute(DeferredAttribute): def __set__(self, instance, value): - if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(instance): + if instance.__dict__.get(self.field.attname) != value and self.field.is_cached( + instance + ): self.field.delete_cached_value(instance) instance.__dict__[self.field.attname] = value @@ -101,14 +103,16 @@ class ForwardManyToOneDescriptor: # related model might not be resolved yet; `self.field.model` might # still be a string model reference. return type( - 'RelatedObjectDoesNotExist', - (self.field.remote_field.model.DoesNotExist, AttributeError), { - '__module__': self.field.model.__module__, - '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % ( + "RelatedObjectDoesNotExist", + (self.field.remote_field.model.DoesNotExist, AttributeError), + { + "__module__": self.field.model.__module__, + "__qualname__": "%s.%s.RelatedObjectDoesNotExist" + % ( self.field.model.__qualname__, self.field.name, ), - } + }, ) def is_cached(self, instance): @@ -135,9 +139,12 @@ class ForwardManyToOneDescriptor: # The check for len(...) == 1 is a special case that allows the query # to be join-less and smaller. Refs #21760. if remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1: - query = {'%s__in' % related_field.name: {instance_attr(inst)[0] for inst in instances}} + query = { + "%s__in" + % related_field.name: {instance_attr(inst)[0] for inst in instances} + } else: - query = {'%s__in' % self.field.related_query_name(): instances} + query = {"%s__in" % self.field.related_query_name(): instances} queryset = queryset.filter(**query) # Since we're going to assign directly in the cache, @@ -146,7 +153,14 @@ class ForwardManyToOneDescriptor: for rel_obj in queryset: instance = instances_dict[rel_obj_attr(rel_obj)] remote_field.set_cached_value(rel_obj, instance) - return queryset, rel_obj_attr, instance_attr, True, self.field.get_cache_name(), False + return ( + queryset, + rel_obj_attr, + instance_attr, + True, + self.field.get_cache_name(), + False, + ) def get_object(self, instance): qs = self.get_queryset(instance=instance) @@ -173,7 +187,11 @@ class ForwardManyToOneDescriptor: rel_obj = self.field.get_cached_value(instance) except KeyError: has_value = None not in self.field.get_local_related_value(instance) - ancestor_link = instance._meta.get_ancestor_link(self.field.model) if has_value else None + ancestor_link = ( + instance._meta.get_ancestor_link(self.field.model) + if has_value + else None + ) if ancestor_link and ancestor_link.is_cached(instance): # An ancestor link will exist if this field is defined on a # multi-table inheritance parent of the instance's class. @@ -211,9 +229,12 @@ class ForwardManyToOneDescriptor: - ``value`` is the ``parent`` instance on the right of the equal sign """ # An object must be an instance of the related class. - if value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model): + if value is not None and not isinstance( + value, self.field.remote_field.model._meta.concrete_model + ): raise ValueError( - 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( + 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' + % ( value, instance._meta.object_name, self.field.name, @@ -222,11 +243,18 @@ class ForwardManyToOneDescriptor: ) elif value is not None: if instance._state.db is None: - instance._state.db = router.db_for_write(instance.__class__, instance=value) + instance._state.db = router.db_for_write( + instance.__class__, instance=value + ) if value._state.db is None: - value._state.db = router.db_for_write(value.__class__, instance=instance) + value._state.db = router.db_for_write( + value.__class__, instance=instance + ) if not router.allow_relation(value, instance): - raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value) + raise ValueError( + 'Cannot assign "%r": the current database router prevents this relation.' + % value + ) remote_field = self.field.remote_field # If we're setting the value of a OneToOneField to None, we need to clear @@ -314,12 +342,15 @@ class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor): opts = instance._meta # Inherited primary key fields from this object's base classes. inherited_pk_fields = [ - field for field in opts.concrete_fields + field + for field in opts.concrete_fields if field.primary_key and field.remote_field ] for field in inherited_pk_fields: rel_model_pk_name = field.remote_field.model._meta.pk.attname - raw_value = getattr(value, rel_model_pk_name) if value is not None else None + raw_value = ( + getattr(value, rel_model_pk_name) if value is not None else None + ) setattr(instance, rel_model_pk_name, raw_value) @@ -346,13 +377,15 @@ class ReverseOneToOneDescriptor: # The exception isn't created at initialization time for the sake of # consistency with `ForwardManyToOneDescriptor`. return type( - 'RelatedObjectDoesNotExist', - (self.related.related_model.DoesNotExist, AttributeError), { - '__module__': self.related.model.__module__, - '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % ( + "RelatedObjectDoesNotExist", + (self.related.related_model.DoesNotExist, AttributeError), + { + "__module__": self.related.model.__module__, + "__qualname__": "%s.%s.RelatedObjectDoesNotExist" + % ( self.related.model.__qualname__, self.related.name, - ) + ), }, ) @@ -370,7 +403,7 @@ class ReverseOneToOneDescriptor: rel_obj_attr = self.related.field.get_local_related_value instance_attr = self.related.field.get_foreign_related_value instances_dict = {instance_attr(inst): inst for inst in instances} - query = {'%s__in' % self.related.field.name: instances} + query = {"%s__in" % self.related.field.name: instances} queryset = queryset.filter(**query) # Since we're going to assign directly in the cache, @@ -378,7 +411,14 @@ class ReverseOneToOneDescriptor: for rel_obj in queryset: instance = instances_dict[rel_obj_attr(rel_obj)] self.related.field.set_cached_value(rel_obj, instance) - return queryset, rel_obj_attr, instance_attr, True, self.related.get_cache_name(), False + return ( + queryset, + rel_obj_attr, + instance_attr, + True, + self.related.get_cache_name(), + False, + ) def __get__(self, instance, cls=None): """ @@ -419,10 +459,8 @@ class ReverseOneToOneDescriptor: if rel_obj is None: raise self.RelatedObjectDoesNotExist( - "%s has no %s." % ( - instance.__class__.__name__, - self.related.get_accessor_name() - ) + "%s has no %s." + % (instance.__class__.__name__, self.related.get_accessor_name()) ) else: return rel_obj @@ -458,7 +496,8 @@ class ReverseOneToOneDescriptor: elif not isinstance(value, self.related.related_model): # An object must be an instance of the related class. raise ValueError( - 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( + 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' + % ( value, instance._meta.object_name, self.related.get_accessor_name(), @@ -467,13 +506,23 @@ class ReverseOneToOneDescriptor: ) else: if instance._state.db is None: - instance._state.db = router.db_for_write(instance.__class__, instance=value) + instance._state.db = router.db_for_write( + instance.__class__, instance=value + ) if value._state.db is None: - value._state.db = router.db_for_write(value.__class__, instance=instance) + value._state.db = router.db_for_write( + value.__class__, instance=instance + ) if not router.allow_relation(value, instance): - raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value) + raise ValueError( + 'Cannot assign "%r": the current database router prevents this relation.' + % value + ) - related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields) + related_pk = tuple( + getattr(instance, field.attname) + for field in self.related.field.foreign_related_fields + ) # Set the value of the related field to the value of the related object's related field for index, field in enumerate(self.related.field.local_related_fields): setattr(value, field.attname, related_pk[index]) @@ -548,13 +597,13 @@ class ReverseManyToOneDescriptor: def _get_set_deprecation_msg_params(self): return ( - 'reverse side of a related set', + "reverse side of a related set", self.rel.get_accessor_name(), ) def __set__(self, instance, value): raise TypeError( - 'Direct assignment to the %s is prohibited. Use %s.set() instead.' + "Direct assignment to the %s is prohibited. Use %s.set() instead." % self._get_set_deprecation_msg_params(), ) @@ -581,6 +630,7 @@ def create_reverse_many_to_one_manager(superclass, rel): manager = getattr(self.model, manager) manager_class = create_reverse_many_to_one_manager(manager.__class__, rel) return manager_class(self.instance) + do_not_call_in_templates = True def _apply_rel_filters(self, queryset): @@ -588,7 +638,9 @@ def create_reverse_many_to_one_manager(superclass, rel): Filter the queryset for the instance this manager is bound to. """ db = self._db or router.db_for_read(self.model, instance=self.instance) - empty_strings_as_null = connections[db].features.interprets_empty_strings_as_nulls + empty_strings_as_null = connections[ + db + ].features.interprets_empty_strings_as_nulls queryset._add_hints(instance=self.instance) if self._db: queryset = queryset.using(self._db) @@ -596,7 +648,7 @@ def create_reverse_many_to_one_manager(superclass, rel): queryset = queryset.filter(**self.core_filters) for field in self.field.foreign_related_fields: val = getattr(self.instance, field.attname) - if val is None or (val == '' and empty_strings_as_null): + if val is None or (val == "" and empty_strings_as_null): return queryset.none() if self.field.many_to_one: # Guard against field-like objects such as GenericRelation @@ -608,24 +660,32 @@ def create_reverse_many_to_one_manager(superclass, rel): except FieldError: # The relationship has multiple target fields. Use a tuple # for related object id. - rel_obj_id = tuple([ - getattr(self.instance, target_field.attname) - for target_field in self.field.path_infos[-1].target_fields - ]) + rel_obj_id = tuple( + [ + getattr(self.instance, target_field.attname) + for target_field in self.field.path_infos[-1].target_fields + ] + ) else: rel_obj_id = getattr(self.instance, target_field.attname) - queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}} + queryset._known_related_objects = { + self.field: {rel_obj_id: self.instance} + } return queryset def _remove_prefetched_objects(self): try: - self.instance._prefetched_objects_cache.pop(self.field.remote_field.get_cache_name()) + self.instance._prefetched_objects_cache.pop( + self.field.remote_field.get_cache_name() + ) except (AttributeError, KeyError): pass # nothing to clear from cache def get_queryset(self): try: - return self.instance._prefetched_objects_cache[self.field.remote_field.get_cache_name()] + return self.instance._prefetched_objects_cache[ + self.field.remote_field.get_cache_name() + ] except (AttributeError, KeyError): queryset = super().get_queryset() return self._apply_rel_filters(queryset) @@ -640,7 +700,7 @@ def create_reverse_many_to_one_manager(superclass, rel): rel_obj_attr = self.field.get_local_related_value instance_attr = self.field.get_foreign_related_value instances_dict = {instance_attr(inst): inst for inst in instances} - query = {'%s__in' % self.field.name: instances} + query = {"%s__in" % self.field.name: instances} queryset = queryset.filter(**query) # Since we just bypassed this class' get_queryset(), we must manage @@ -658,9 +718,13 @@ def create_reverse_many_to_one_manager(superclass, rel): def check_and_update_obj(obj): if not isinstance(obj, self.model): - raise TypeError("'%s' instance expected, got %r" % ( - self.model._meta.object_name, obj, - )) + raise TypeError( + "'%s' instance expected, got %r" + % ( + self.model._meta.object_name, + obj, + ) + ) setattr(obj, self.field.name, self.instance) if bulk: @@ -673,36 +737,43 @@ def create_reverse_many_to_one_manager(superclass, rel): "the object first." % obj ) pks.append(obj.pk) - self.model._base_manager.using(db).filter(pk__in=pks).update(**{ - self.field.name: self.instance, - }) + self.model._base_manager.using(db).filter(pk__in=pks).update( + **{ + self.field.name: self.instance, + } + ) else: with transaction.atomic(using=db, savepoint=False): for obj in objs: check_and_update_obj(obj) obj.save() + add.alters_data = True def create(self, **kwargs): kwargs[self.field.name] = self.instance db = router.db_for_write(self.model, instance=self.instance) return super(RelatedManager, self.db_manager(db)).create(**kwargs) + create.alters_data = True def get_or_create(self, **kwargs): kwargs[self.field.name] = self.instance db = router.db_for_write(self.model, instance=self.instance) return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs) + get_or_create.alters_data = True def update_or_create(self, **kwargs): kwargs[self.field.name] = self.instance db = router.db_for_write(self.model, instance=self.instance) return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs) + update_or_create.alters_data = True # remove() and clear() are only provided if the ForeignKey can have a value of null. if rel.field.null: + def remove(self, *objs, bulk=True): if not objs: return @@ -710,9 +781,13 @@ def create_reverse_many_to_one_manager(superclass, rel): old_ids = set() for obj in objs: if not isinstance(obj, self.model): - raise TypeError("'%s' instance expected, got %r" % ( - self.model._meta.object_name, obj, - )) + raise TypeError( + "'%s' instance expected, got %r" + % ( + self.model._meta.object_name, + obj, + ) + ) # Is obj actually part of this descriptor set? if self.field.get_local_related_value(obj) == val: old_ids.add(obj.pk) @@ -721,10 +796,12 @@ def create_reverse_many_to_one_manager(superclass, rel): "%r is not related to %r." % (obj, self.instance) ) self._clear(self.filter(pk__in=old_ids), bulk) + remove.alters_data = True def clear(self, *, bulk=True): self._clear(self, bulk) + clear.alters_data = True def _clear(self, queryset, bulk): @@ -739,6 +816,7 @@ def create_reverse_many_to_one_manager(superclass, rel): for obj in queryset: setattr(obj, self.field.name, None) obj.save(update_fields=[self.field.name]) + _clear.alters_data = True def set(self, objs, *, bulk=True, clear=False): @@ -765,6 +843,7 @@ def create_reverse_many_to_one_manager(superclass, rel): self.add(*new_objs, bulk=bulk) else: self.add(*objs, bulk=bulk) + set.alters_data = True return RelatedManager @@ -822,7 +901,8 @@ class ManyToManyDescriptor(ReverseManyToOneDescriptor): def _get_set_deprecation_msg_params(self): return ( - '%s side of a many-to-many set' % ('reverse' if self.reverse else 'forward'), + "%s side of a many-to-many set" + % ("reverse" if self.reverse else "forward"), self.rel.get_accessor_name() if self.reverse else self.field.name, ) @@ -865,41 +945,51 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): self.core_filters = {} self.pk_field_names = {} for lh_field, rh_field in self.source_field.related_fields: - core_filter_key = '%s__%s' % (self.query_field_name, rh_field.name) + core_filter_key = "%s__%s" % (self.query_field_name, rh_field.name) self.core_filters[core_filter_key] = getattr(instance, rh_field.attname) self.pk_field_names[lh_field.name] = rh_field.name self.related_val = self.source_field.get_foreign_related_value(instance) if None in self.related_val: - raise ValueError('"%r" needs to have a value for field "%s" before ' - 'this many-to-many relationship can be used.' % - (instance, self.pk_field_names[self.source_field_name])) + raise ValueError( + '"%r" needs to have a value for field "%s" before ' + "this many-to-many relationship can be used." + % (instance, self.pk_field_names[self.source_field_name]) + ) # Even if this relation is not to pk, we require still pk value. # The wish is that the instance has been already saved to DB, # although having a pk value isn't a guarantee of that. if instance.pk is None: - raise ValueError("%r instance needs to have a primary key value before " - "a many-to-many relationship can be used." % - instance.__class__.__name__) + raise ValueError( + "%r instance needs to have a primary key value before " + "a many-to-many relationship can be used." + % instance.__class__.__name__ + ) def __call__(self, *, manager): manager = getattr(self.model, manager) - manager_class = create_forward_many_to_many_manager(manager.__class__, rel, reverse) + manager_class = create_forward_many_to_many_manager( + manager.__class__, rel, reverse + ) return manager_class(instance=self.instance) + do_not_call_in_templates = True def _build_remove_filters(self, removed_vals): filters = Q((self.source_field_name, self.related_val)) # No need to add a subquery condition if removed_vals is a QuerySet without # filters. - removed_vals_filters = (not isinstance(removed_vals, QuerySet) or - removed_vals._has_filters()) + removed_vals_filters = ( + not isinstance(removed_vals, QuerySet) or removed_vals._has_filters() + ) if removed_vals_filters: - filters &= Q((f'{self.target_field_name}__in', removed_vals)) + filters &= Q((f"{self.target_field_name}__in", removed_vals)) if self.symmetrical: symmetrical_filters = Q((self.target_field_name, self.related_val)) if removed_vals_filters: - symmetrical_filters &= Q((f'{self.source_field_name}__in', removed_vals)) + symmetrical_filters &= Q( + (f"{self.source_field_name}__in", removed_vals) + ) filters |= symmetrical_filters return filters @@ -933,7 +1023,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): queryset._add_hints(instance=instances[0]) queryset = queryset.using(queryset._db or self._db) - query = {'%s__in' % self.query_field_name: instances} + query = {"%s__in" % self.query_field_name: instances} queryset = queryset._next_is_sticky().filter(**query) # M2M: need to annotate the query in order to get the primary model @@ -947,13 +1037,18 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): join_table = fk.model._meta.db_table connection = connections[queryset.db] qn = connection.ops.quote_name - queryset = queryset.extra(select={ - '_prefetch_related_val_%s' % f.attname: - '%s.%s' % (qn(join_table), qn(f.column)) for f in fk.local_related_fields}) + queryset = queryset.extra( + select={ + "_prefetch_related_val_%s" + % f.attname: "%s.%s" + % (qn(join_table), qn(f.column)) + for f in fk.local_related_fields + } + ) return ( queryset, lambda result: tuple( - getattr(result, '_prefetch_related_val_%s' % f.attname) + getattr(result, "_prefetch_related_val_%s" % f.attname) for f in fk.local_related_fields ), lambda inst: tuple( @@ -970,7 +1065,9 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): db = router.db_for_write(self.through, instance=self.instance) with transaction.atomic(using=db, savepoint=False): self._add_items( - self.source_field_name, self.target_field_name, *objs, + self.source_field_name, + self.target_field_name, + *objs, through_defaults=through_defaults, ) # If this is a symmetrical m2m relation to self, add the mirror @@ -982,30 +1079,41 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): *objs, through_defaults=through_defaults, ) + add.alters_data = True def remove(self, *objs): self._remove_prefetched_objects() self._remove_items(self.source_field_name, self.target_field_name, *objs) + remove.alters_data = True def clear(self): db = router.db_for_write(self.through, instance=self.instance) with transaction.atomic(using=db, savepoint=False): signals.m2m_changed.send( - sender=self.through, action="pre_clear", - instance=self.instance, reverse=self.reverse, - model=self.model, pk_set=None, using=db, + sender=self.through, + action="pre_clear", + instance=self.instance, + reverse=self.reverse, + model=self.model, + pk_set=None, + using=db, ) self._remove_prefetched_objects() filters = self._build_remove_filters(super().get_queryset().using(db)) self.through._default_manager.using(db).filter(filters).delete() signals.m2m_changed.send( - sender=self.through, action="post_clear", - instance=self.instance, reverse=self.reverse, - model=self.model, pk_set=None, using=db, + sender=self.through, + action="post_clear", + instance=self.instance, + reverse=self.reverse, + model=self.model, + pk_set=None, + using=db, ) + clear.alters_data = True def set(self, objs, *, clear=False, through_defaults=None): @@ -1019,7 +1127,11 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): self.clear() self.add(*objs, through_defaults=through_defaults) else: - old_ids = set(self.using(db).values_list(self.target_field.target_field.attname, flat=True)) + old_ids = set( + self.using(db).values_list( + self.target_field.target_field.attname, flat=True + ) + ) new_objs = [] for obj in objs: @@ -1035,6 +1147,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): self.remove(*old_ids) self.add(*new_objs, through_defaults=through_defaults) + set.alters_data = True def create(self, *, through_defaults=None, **kwargs): @@ -1042,26 +1155,33 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs) self.add(new_obj, through_defaults=through_defaults) return new_obj + create.alters_data = True def get_or_create(self, *, through_defaults=None, **kwargs): db = router.db_for_write(self.instance.__class__, instance=self.instance) - obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs) + obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create( + **kwargs + ) # We only need to add() if created because if we got an object back # from get() then the relationship already exists. if created: self.add(obj, through_defaults=through_defaults) return obj, created + get_or_create.alters_data = True def update_or_create(self, *, through_defaults=None, **kwargs): db = router.db_for_write(self.instance.__class__, instance=self.instance) - obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs) + obj, created = super( + ManyRelatedManager, self.db_manager(db) + ).update_or_create(**kwargs) # We only need to add() if created because if we got an object back # from get() then the relationship already exists. if created: self.add(obj, through_defaults=through_defaults) return obj, created + update_or_create.alters_data = True def _get_target_ids(self, target_field_name, objs): @@ -1069,6 +1189,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): Return the set of ids of `objs` that the target field references. """ from django.db.models import Model + target_ids = set() target_field = self.through._meta.get_field(target_field_name) for obj in objs: @@ -1076,36 +1197,42 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): if not router.allow_relation(obj, self.instance): raise ValueError( 'Cannot add "%r": instance is on database "%s", ' - 'value is on database "%s"' % - (obj, self.instance._state.db, obj._state.db) + 'value is on database "%s"' + % (obj, self.instance._state.db, obj._state.db) ) target_id = target_field.get_foreign_related_value(obj)[0] if target_id is None: raise ValueError( - 'Cannot add "%r": the value for field "%s" is None' % - (obj, target_field_name) + 'Cannot add "%r": the value for field "%s" is None' + % (obj, target_field_name) ) target_ids.add(target_id) elif isinstance(obj, Model): raise TypeError( - "'%s' instance expected, got %r" % - (self.model._meta.object_name, obj) + "'%s' instance expected, got %r" + % (self.model._meta.object_name, obj) ) else: target_ids.add(target_field.get_prep_value(obj)) return target_ids - def _get_missing_target_ids(self, source_field_name, target_field_name, db, target_ids): + def _get_missing_target_ids( + self, source_field_name, target_field_name, db, target_ids + ): """ Return the subset of ids of `objs` that aren't already assigned to this relationship. """ - vals = self.through._default_manager.using(db).values_list( - target_field_name, flat=True - ).filter(**{ - source_field_name: self.related_val[0], - '%s__in' % target_field_name: target_ids, - }) + vals = ( + self.through._default_manager.using(db) + .values_list(target_field_name, flat=True) + .filter( + **{ + source_field_name: self.related_val[0], + "%s__in" % target_field_name: target_ids, + } + ) + ) return target_ids.difference(vals) def _get_add_plan(self, db, source_field_name): @@ -1123,21 +1250,27 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): # user-defined intermediary models as they could have other fields # causing conflicts which must be surfaced. can_ignore_conflicts = ( - self.through._meta.auto_created is not False and - connections[db].features.supports_ignore_conflicts + self.through._meta.auto_created is not False + and connections[db].features.supports_ignore_conflicts ) # Don't send the signal when inserting duplicate data row # for symmetrical reverse entries. - must_send_signals = (self.reverse or source_field_name == self.source_field_name) and ( - signals.m2m_changed.has_listeners(self.through) - ) + must_send_signals = ( + self.reverse or source_field_name == self.source_field_name + ) and (signals.m2m_changed.has_listeners(self.through)) # Fast addition through bulk insertion can only be performed # if no m2m_changed listeners are connected for self.through # as they require the added set of ids to be provided via # pk_set. - return can_ignore_conflicts, must_send_signals, (can_ignore_conflicts and not must_send_signals) + return ( + can_ignore_conflicts, + must_send_signals, + (can_ignore_conflicts and not must_send_signals), + ) - def _add_items(self, source_field_name, target_field_name, *objs, through_defaults=None): + def _add_items( + self, source_field_name, target_field_name, *objs, through_defaults=None + ): # source_field_name: the PK fieldname in join table for the source object # target_field_name: the PK fieldname in join table for the target object # *objs - objects to add. Either object instances, or primary keys of object instances. @@ -1147,15 +1280,22 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): through_defaults = dict(resolve_callables(through_defaults or {})) target_ids = self._get_target_ids(target_field_name, objs) db = router.db_for_write(self.through, instance=self.instance) - can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan(db, source_field_name) + can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan( + db, source_field_name + ) if can_fast_add: - self.through._default_manager.using(db).bulk_create([ - self.through(**{ - '%s_id' % source_field_name: self.related_val[0], - '%s_id' % target_field_name: target_id, - }) - for target_id in target_ids - ], ignore_conflicts=True) + self.through._default_manager.using(db).bulk_create( + [ + self.through( + **{ + "%s_id" % source_field_name: self.related_val[0], + "%s_id" % target_field_name: target_id, + } + ) + for target_id in target_ids + ], + ignore_conflicts=True, + ) return missing_target_ids = self._get_missing_target_ids( @@ -1164,24 +1304,38 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): with transaction.atomic(using=db, savepoint=False): if must_send_signals: signals.m2m_changed.send( - sender=self.through, action='pre_add', - instance=self.instance, reverse=self.reverse, - model=self.model, pk_set=missing_target_ids, using=db, + sender=self.through, + action="pre_add", + instance=self.instance, + reverse=self.reverse, + model=self.model, + pk_set=missing_target_ids, + using=db, ) # Add the ones that aren't there already. - self.through._default_manager.using(db).bulk_create([ - self.through(**through_defaults, **{ - '%s_id' % source_field_name: self.related_val[0], - '%s_id' % target_field_name: target_id, - }) - for target_id in missing_target_ids - ], ignore_conflicts=can_ignore_conflicts) + self.through._default_manager.using(db).bulk_create( + [ + self.through( + **through_defaults, + **{ + "%s_id" % source_field_name: self.related_val[0], + "%s_id" % target_field_name: target_id, + }, + ) + for target_id in missing_target_ids + ], + ignore_conflicts=can_ignore_conflicts, + ) if must_send_signals: signals.m2m_changed.send( - sender=self.through, action='post_add', - instance=self.instance, reverse=self.reverse, - model=self.model, pk_set=missing_target_ids, using=db, + sender=self.through, + action="post_add", + instance=self.instance, + reverse=self.reverse, + model=self.model, + pk_set=missing_target_ids, + using=db, ) def _remove_items(self, source_field_name, target_field_name, *objs): @@ -1205,23 +1359,32 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): with transaction.atomic(using=db, savepoint=False): # Send a signal to the other end if need be. signals.m2m_changed.send( - sender=self.through, action="pre_remove", - instance=self.instance, reverse=self.reverse, - model=self.model, pk_set=old_ids, using=db, + sender=self.through, + action="pre_remove", + instance=self.instance, + reverse=self.reverse, + model=self.model, + pk_set=old_ids, + using=db, ) target_model_qs = super().get_queryset() if target_model_qs._has_filters(): - old_vals = target_model_qs.using(db).filter(**{ - '%s__in' % self.target_field.target_field.attname: old_ids}) + old_vals = target_model_qs.using(db).filter( + **{"%s__in" % self.target_field.target_field.attname: old_ids} + ) else: old_vals = old_ids filters = self._build_remove_filters(old_vals) self.through._default_manager.using(db).filter(filters).delete() signals.m2m_changed.send( - sender=self.through, action="post_remove", - instance=self.instance, reverse=self.reverse, - model=self.model, pk_set=old_ids, using=db, + sender=self.through, + action="post_remove", + instance=self.instance, + reverse=self.reverse, + model=self.model, + pk_set=old_ids, + using=db, ) return ManyRelatedManager diff --git a/django/db/models/fields/related_lookups.py b/django/db/models/fields/related_lookups.py index fd97757b14..1bad1cf416 100644 --- a/django/db/models/fields/related_lookups.py +++ b/django/db/models/fields/related_lookups.py @@ -1,5 +1,10 @@ from django.db.models.lookups import ( - Exact, GreaterThan, GreaterThanOrEqual, In, IsNull, LessThan, + Exact, + GreaterThan, + GreaterThanOrEqual, + In, + IsNull, + LessThan, LessThanOrEqual, ) @@ -8,16 +13,21 @@ class MultiColSource: contains_aggregate = False def __init__(self, alias, targets, sources, field): - self.targets, self.sources, self.field, self.alias = targets, sources, field, alias + self.targets, self.sources, self.field, self.alias = ( + targets, + sources, + field, + alias, + ) self.output_field = self.field def __repr__(self): - return "{}({}, {})".format( - self.__class__.__name__, self.alias, self.field) + return "{}({}, {})".format(self.__class__.__name__, self.alias, self.field) def relabeled_clone(self, relabels): - return self.__class__(relabels.get(self.alias, self.alias), - self.targets, self.sources, self.field) + return self.__class__( + relabels.get(self.alias, self.alias), self.targets, self.sources, self.field + ) def get_lookup(self, lookup): return self.output_field.get_lookup(lookup) @@ -28,12 +38,15 @@ class MultiColSource: def get_normalized_value(value, lhs): from django.db.models import Model + if isinstance(value, Model): value_list = [] sources = lhs.output_field.path_infos[-1].target_fields for source in sources: while not isinstance(value, source.model) and source.remote_field: - source = source.remote_field.model._meta.get_field(source.remote_field.field_name) + source = source.remote_field.model._meta.get_field( + source.remote_field.field_name + ) try: value_list.append(getattr(value, source.attname)) except AttributeError: @@ -56,20 +69,21 @@ class RelatedIn(In): # case ForeignKey to IntegerField given value 'abc'. The # ForeignKey itself doesn't have validation for non-integers, # so we must run validation using the target field. - if hasattr(self.lhs.output_field, 'path_infos'): + if hasattr(self.lhs.output_field, "path_infos"): # Run the target field's get_prep_value. We can safely # assume there is only one as we don't get to the direct # value branch otherwise. - target_field = self.lhs.output_field.path_infos[-1].target_fields[-1] + target_field = self.lhs.output_field.path_infos[-1].target_fields[ + -1 + ] self.rhs = [target_field.get_prep_value(v) for v in self.rhs] - elif ( - not getattr(self.rhs, 'has_select_fields', True) and - not getattr(self.lhs.field.target_field, 'primary_key', False) + elif not getattr(self.rhs, "has_select_fields", True) and not getattr( + self.lhs.field.target_field, "primary_key", False ): self.rhs.clear_select_clause() if ( - getattr(self.lhs.output_field, 'primary_key', False) and - self.lhs.output_field.model == self.rhs.model + getattr(self.lhs.output_field, "primary_key", False) + and self.lhs.output_field.model == self.rhs.model ): # A case like # Restaurant.objects.filter(place__in=restaurant_qs), where @@ -87,7 +101,10 @@ class RelatedIn(In): # This clause is either a SubqueryConstraint (for values that need to be compiled to # SQL) or an OR-combined list of (col1 = val1 AND col2 = val2 AND ...) clauses. from django.db.models.sql.where import ( - AND, OR, SubqueryConstraint, WhereNode, + AND, + OR, + SubqueryConstraint, + WhereNode, ) root_constraint = WhereNode(connector=OR) @@ -95,31 +112,41 @@ class RelatedIn(In): values = [get_normalized_value(value, self.lhs) for value in self.rhs] for value in values: value_constraint = WhereNode() - for source, target, val in zip(self.lhs.sources, self.lhs.targets, value): - lookup_class = target.get_lookup('exact') - lookup = lookup_class(target.get_col(self.lhs.alias, source), val) + for source, target, val in zip( + self.lhs.sources, self.lhs.targets, value + ): + lookup_class = target.get_lookup("exact") + lookup = lookup_class( + target.get_col(self.lhs.alias, source), val + ) value_constraint.add(lookup, AND) root_constraint.add(value_constraint, OR) else: root_constraint.add( SubqueryConstraint( - self.lhs.alias, [target.column for target in self.lhs.targets], - [source.name for source in self.lhs.sources], self.rhs), - AND) + self.lhs.alias, + [target.column for target in self.lhs.targets], + [source.name for source in self.lhs.sources], + self.rhs, + ), + AND, + ) return root_constraint.as_sql(compiler, connection) return super().as_sql(compiler, connection) class RelatedLookupMixin: def get_prep_lookup(self): - if not isinstance(self.lhs, MultiColSource) and not hasattr(self.rhs, 'resolve_expression'): + if not isinstance(self.lhs, MultiColSource) and not hasattr( + self.rhs, "resolve_expression" + ): # If we get here, we are dealing with single-column relations. self.rhs = get_normalized_value(self.rhs, self.lhs)[0] # We need to run the related field's get_prep_value(). Consider case # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself # doesn't have validation for non-integers, so we must run validation # using the target field. - if self.prepare_rhs and hasattr(self.lhs.output_field, 'path_infos'): + if self.prepare_rhs and hasattr(self.lhs.output_field, "path_infos"): # Get the target field. We can safely assume there is only one # as we don't get to the direct value branch otherwise. target_field = self.lhs.output_field.path_infos[-1].target_fields[-1] @@ -132,11 +159,15 @@ class RelatedLookupMixin: assert self.rhs_is_direct_value() self.rhs = get_normalized_value(self.rhs, self.lhs) from django.db.models.sql.where import AND, WhereNode + root_constraint = WhereNode() - for target, source, val in zip(self.lhs.targets, self.lhs.sources, self.rhs): + for target, source, val in zip( + self.lhs.targets, self.lhs.sources, self.rhs + ): lookup_class = target.get_lookup(self.lookup_name) root_constraint.add( - lookup_class(target.get_col(self.lhs.alias, source), val), AND) + lookup_class(target.get_col(self.lhs.alias, source), val), AND + ) return root_constraint.as_sql(compiler, connection) return super().as_sql(compiler, connection) diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index 6f0c788bbd..2ff66f34d0 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -36,8 +36,16 @@ class ForeignObjectRel(FieldCacheMixin): null = True empty_strings_allowed = False - def __init__(self, field, to, related_name=None, related_query_name=None, - limit_choices_to=None, parent_link=False, on_delete=None): + def __init__( + self, + field, + to, + related_name=None, + related_query_name=None, + limit_choices_to=None, + parent_link=False, + on_delete=None, + ): self.field = field self.model = to self.related_name = related_name @@ -73,14 +81,17 @@ class ForeignObjectRel(FieldCacheMixin): """ target_fields = self.path_infos[-1].target_fields if len(target_fields) > 1: - raise exceptions.FieldError("Can't use target_field for multicolumn relations.") + raise exceptions.FieldError( + "Can't use target_field for multicolumn relations." + ) return target_fields[0] @cached_property def related_model(self): if not self.field.model: raise AttributeError( - "This property can't be accessed before self.field.contribute_to_class has been called.") + "This property can't be accessed before self.field.contribute_to_class has been called." + ) return self.field.model @cached_property @@ -110,7 +121,7 @@ class ForeignObjectRel(FieldCacheMixin): return self.field.db_type def __repr__(self): - return '<%s: %s.%s>' % ( + return "<%s: %s.%s>" % ( type(self).__name__, self.related_model._meta.app_label, self.related_model._meta.model_name, @@ -147,12 +158,15 @@ class ForeignObjectRel(FieldCacheMixin): # created and doesn't exist in the .models module. # This is a reverse relation, so there is no reverse_path_infos to # delete. - state.pop('path_infos', None) + state.pop("path_infos", None) return state def get_choices( - self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, - limit_choices_to=None, ordering=(), + self, + include_blank=True, + blank_choice=BLANK_CHOICE_DASH, + limit_choices_to=None, + ordering=(), ): """ Return choices with a default blank choices included, for use @@ -165,13 +179,11 @@ class ForeignObjectRel(FieldCacheMixin): qs = self.related_model._default_manager.complex_filter(limit_choices_to) if ordering: qs = qs.order_by(*ordering) - return (blank_choice if include_blank else []) + [ - (x.pk, str(x)) for x in qs - ] + return (blank_choice if include_blank else []) + [(x.pk, str(x)) for x in qs] def is_hidden(self): """Should the related object be hidden?""" - return bool(self.related_name) and self.related_name[-1] == '+' + return bool(self.related_name) and self.related_name[-1] == "+" def get_joining_columns(self): return self.field.get_reverse_joining_columns() @@ -204,7 +216,7 @@ class ForeignObjectRel(FieldCacheMixin): return None if self.related_name: return self.related_name - return opts.model_name + ('_set' if self.multiple else '') + return opts.model_name + ("_set" if self.multiple else "") def get_path_info(self, filtered_relation=None): if filtered_relation: @@ -239,10 +251,20 @@ class ManyToOneRel(ForeignObjectRel): reverse relations into actual fields. """ - def __init__(self, field, to, field_name, related_name=None, related_query_name=None, - limit_choices_to=None, parent_link=False, on_delete=None): + def __init__( + self, + field, + to, + field_name, + related_name=None, + related_query_name=None, + limit_choices_to=None, + parent_link=False, + on_delete=None, + ): super().__init__( - field, to, + field, + to, related_name=related_name, related_query_name=related_query_name, limit_choices_to=limit_choices_to, @@ -254,7 +276,7 @@ class ManyToOneRel(ForeignObjectRel): def __getstate__(self): state = super().__getstate__() - state.pop('related_model', None) + state.pop("related_model", None) return state @property @@ -267,7 +289,9 @@ class ManyToOneRel(ForeignObjectRel): """ field = self.model._meta.get_field(self.field_name) if not field.concrete: - raise exceptions.FieldDoesNotExist("No related field named '%s'" % self.field_name) + raise exceptions.FieldDoesNotExist( + "No related field named '%s'" % self.field_name + ) return field def set_field_name(self): @@ -282,10 +306,21 @@ class OneToOneRel(ManyToOneRel): flags for the reverse relation. """ - def __init__(self, field, to, field_name, related_name=None, related_query_name=None, - limit_choices_to=None, parent_link=False, on_delete=None): + def __init__( + self, + field, + to, + field_name, + related_name=None, + related_query_name=None, + limit_choices_to=None, + parent_link=False, + on_delete=None, + ): super().__init__( - field, to, field_name, + field, + to, + field_name, related_name=related_name, related_query_name=related_query_name, limit_choices_to=limit_choices_to, @@ -304,11 +339,21 @@ class ManyToManyRel(ForeignObjectRel): flags for the reverse relation. """ - def __init__(self, field, to, related_name=None, related_query_name=None, - limit_choices_to=None, symmetrical=True, through=None, - through_fields=None, db_constraint=True): + def __init__( + self, + field, + to, + related_name=None, + related_query_name=None, + limit_choices_to=None, + symmetrical=True, + through=None, + through_fields=None, + db_constraint=True, + ): super().__init__( - field, to, + field, + to, related_name=related_name, related_query_name=related_query_name, limit_choices_to=limit_choices_to, @@ -343,7 +388,7 @@ class ManyToManyRel(ForeignObjectRel): field = opts.get_field(self.through_fields[0]) else: for field in opts.fields: - rel = getattr(field, 'remote_field', None) + rel = getattr(field, "remote_field", None) if rel and rel.model == self.model: break return field.foreign_related_fields[0] |
