diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1faf024d4f1..e1d8cec10bbd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,15 @@ repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.1.0 + rev: 25.9.0 hooks: - id: black exclude: \.py-tpl$ - repo: https://github.com/adamchainz/blacken-docs - rev: 1.19.1 + rev: 1.20.0 hooks: - id: blacken-docs additional_dependencies: - - black==25.1.0 + - black==25.9.0 files: 'docs/.*\.txt$' args: ["--rst-literal-block"] - repo: https://github.com/PyCQA/isort @@ -17,10 +17,10 @@ repos: hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 7.2.0 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v9.24.0 + rev: v9.36.0 hooks: - id: eslint diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index cc33740195d9..96d555f86234 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -1660,7 +1660,9 @@ def _field_indexes_sql(self, model, field): return output def _field_should_be_altered(self, old_field, new_field, ignore=None): - if not old_field.concrete and not new_field.concrete: + if (not (old_field.concrete or old_field.many_to_many)) and ( + not (new_field.concrete or new_field.many_to_many) + ): return False ignore = ignore or set() _, old_path, old_args, old_kwargs = old_field.deconstruct() @@ -1692,8 +1694,10 @@ def _field_should_be_altered(self, old_field, new_field, ignore=None): ): old_kwargs.pop("db_default") new_kwargs.pop("db_default") - return self.quote_name(old_field.column) != self.quote_name( - new_field.column + return ( + old_field.concrete + and new_field.concrete + and (self.quote_name(old_field.column) != self.quote_name(new_field.column)) ) or (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs) def _field_should_be_indexed(self, model, field): diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index ac6ae5efbd29..077a53bf55cf 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -144,7 +144,7 @@ def is_self_referential(f): # Choose a default and insert it into the copy map if ( not create_field.has_db_default() - and not (create_field.many_to_many or create_field.generated) + and not create_field.generated and create_field.concrete ): mapping[create_field.column] = self.prepare_default( diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 9ad440128ed7..a71ae2f4011f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1849,6 +1849,10 @@ def deconstruct(self): ) return name, path, args, kwargs + def get_attname_column(self): + attname, _ = super().get_attname_column() + return attname, None + def _get_path_info(self, direct=False, filtered_relation=None): """Called by both direct and indirect m2m traversal.""" int_model = self.remote_field.through diff --git a/django/db/models/query.py b/django/db/models/query.py index bd79e4bf36a1..721bf33e57db 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -725,7 +725,7 @@ def _check_bulk_create_options( "Unique fields that can trigger the upsert must be provided." ) # Updating primary keys and non-concrete fields is forbidden. - if any(not f.concrete or f.many_to_many for f in update_fields): + if any(not f.concrete for f in update_fields): raise ValueError( "bulk_create() can only be used with concrete fields in " "update_fields." @@ -736,7 +736,7 @@ def _check_bulk_create_options( "update_fields." ) if unique_fields: - if any(not f.concrete or f.many_to_many for f in unique_fields): + if any(not f.concrete for f in unique_fields): raise ValueError( "bulk_create() can only be used with concrete fields " "in unique_fields." @@ -916,7 +916,7 @@ def bulk_update(self, objs, fields, batch_size=None): raise ValueError("All bulk_update() objects must have a primary key set.") opts = self.model._meta fields = [opts.get_field(name) for name in fields] - if any(not f.concrete or f.many_to_many for f in fields): + if any(not f.concrete for f in fields): raise ValueError("bulk_update() can only be used with concrete fields.") all_pk_fields = set(opts.pk_fields) for parent in opts.all_parents: diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index fb1f83104274..39ecab2e91ea 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1817,7 +1817,7 @@ def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False): available = sorted( [ *get_field_names_from_opts(opts), - *self.annotation_select, + *self.annotations, *self._filtered_relations, ] ) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 9936f7ff42ca..9302247648dd 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -92,10 +92,10 @@ def add_update_values(self, values): raise FieldError( "Composite primary key fields must be updated individually." ) - if not field.concrete or (field.is_relation and field.many_to_many): + if not field.concrete: raise FieldError( - "Cannot update model field %r (only non-relations and " - "foreign keys permitted)." % field + "Cannot update model field %r (only concrete fields are permitted)." + % field ) if model is not self.get_meta().concrete_model: self.add_related_update(model, field, val) diff --git a/package.json b/package.json index aead71128781..396ffd8653f5 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "npm": ">=1.3.0" }, "devDependencies": { - "eslint": "^9.24.0", - "puppeteer": "^24.6.1", - "globals": "^16.0.0", + "eslint": "^9.36.0", + "puppeteer": "^24.22.0", + "globals": "^16.4.0", "grunt": "^1.6.1", "grunt-cli": "^1.5.0", "grunt-contrib-qunit": "^10.1.1", diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py index 8091498908a8..cf1eebf8d7a3 100644 --- a/tests/annotations/tests.py +++ b/tests/annotations/tests.py @@ -1539,3 +1539,13 @@ def test_alias_filtered_relation_sql_injection(self): ) with self.assertRaisesMessage(ValueError, msg): Book.objects.alias(**{crafted_alias: FilteredRelation("authors")}) + + def test_values_wrong_alias(self): + expected_message = ( + "Cannot resolve keyword 'alias_typo' into field. Choices are: %s" + ) + alias_fields = ", ".join( + sorted(["my_alias"] + list(get_field_names_from_opts(Book._meta))) + ) + with self.assertRaisesMessage(FieldError, expected_message % alias_fields): + Book.objects.alias(my_alias=F("pk")).order_by("alias_typo") diff --git a/tests/composite_pk/test_update.py b/tests/composite_pk/test_update.py index 8d786e8afb5a..4f3068f22886 100644 --- a/tests/composite_pk/test_update.py +++ b/tests/composite_pk/test_update.py @@ -172,7 +172,7 @@ def test_update_token_by_tenant_name(self): def test_cant_update_relation(self): msg = ( "Cannot update model field (only non-relations and foreign keys permitted)" + "user> (only concrete fields are permitted)" ) with self.assertRaisesMessage(FieldError, msg): diff --git a/tests/model_fields/test_field_flags.py b/tests/model_fields/test_field_flags.py index 33f3334567f9..778a43eb63db 100644 --- a/tests/model_fields/test_field_flags.py +++ b/tests/model_fields/test_field_flags.py @@ -6,6 +6,7 @@ NON_CONCRETE_FIELDS = ( models.ForeignObject, + models.ManyToManyField, GenericForeignKey, GenericRelation, ) @@ -209,7 +210,7 @@ def test_hidden_flag(self): def test_model_and_reverse_model_should_equal_on_relations(self): for field in AllFieldsModel._meta.get_fields(): is_concrete_forward_field = field.concrete and field.related_model - if is_concrete_forward_field: + if is_concrete_forward_field or field.many_to_many: reverse_field = field.remote_field self.assertEqual(field.model, reverse_field.related_model) self.assertEqual(field.related_model, reverse_field.model) diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt index 7ecc3ad0442d..1a16cc044093 100644 --- a/tests/requirements/py3.txt +++ b/tests/requirements/py3.txt @@ -2,7 +2,7 @@ aiosmtpd >= 1.4.5 asgiref >= 3.9.1 argon2-cffi >= 23.1.0 bcrypt >= 4.1.1 -black >= 25.1.0 +black >= 25.9.0 docutils >= 0.19 geoip2 >= 4.8.0 jinja2 >= 2.11.0 diff --git a/tests/update/tests.py b/tests/update/tests.py index af5939a2ef10..eb5d80219c9f 100644 --- a/tests/update/tests.py +++ b/tests/update/tests.py @@ -157,43 +157,32 @@ def test_update_respects_to_field(self): self.assertEqual(bar_qs[0].foo_id, b_foo.target) def test_update_m2m_field(self): - msg = ( - "Cannot update model field " - " " - "(only non-relations and foreign keys permitted)." - ) + rel = "" + msg = f"Cannot update model field {rel} (only concrete fields are permitted)." with self.assertRaisesMessage(FieldError, msg): Bar.objects.update(m2m_foo="whatever") def test_update_reverse_m2m_descriptor(self): - msg = ( - "Cannot update model field " - "(only non-relations and foreign keys permitted)." - ) + rel = "" + msg = f"Cannot update model field {rel} (only concrete fields are permitted)." with self.assertRaisesMessage(FieldError, msg): Foo.objects.update(m2m_foo="whatever") def test_update_reverse_fk_descriptor(self): - msg = ( - "Cannot update model field " - "(only non-relations and foreign keys permitted)." - ) + rel = "" + msg = f"Cannot update model field {rel} (only concrete fields are permitted)." with self.assertRaisesMessage(FieldError, msg): Foo.objects.update(bar="whatever") def test_update_reverse_o2o_descriptor(self): - msg = ( - "Cannot update model field " - "(only non-relations and foreign keys permitted)." - ) + rel = "" + msg = f"Cannot update model field {rel} (only concrete fields are permitted)." with self.assertRaisesMessage(FieldError, msg): Foo.objects.update(o2o_bar="whatever") def test_update_reverse_mti_parent_link_descriptor(self): - msg = ( - "Cannot update model field " - "(only non-relations and foreign keys permitted)." - ) + rel = "" + msg = f"Cannot update model field {rel} (only concrete fields are permitted)." with self.assertRaisesMessage(FieldError, msg): UniqueNumber.objects.update(uniquenumberchild="whatever")