Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
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
rev: 5.13.2
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
10 changes: 7 additions & 3 deletions django/db/backends/base/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion django/db/backends/sqlite3/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions django/db/models/fields/related.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand All @@ -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."
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion django/db/models/sql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]
)
Expand Down
6 changes: 3 additions & 3 deletions django/db/models/sql/subqueries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 10 additions & 0 deletions tests/annotations/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
2 changes: 1 addition & 1 deletion tests/composite_pk/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def test_update_token_by_tenant_name(self):
def test_cant_update_relation(self):
msg = (
"Cannot update model field <django.db.models.fields.related.ForeignObject: "
"user> (only non-relations and foreign keys permitted)"
"user> (only concrete fields are permitted)"
)

with self.assertRaisesMessage(FieldError, msg):
Expand Down
3 changes: 2 additions & 1 deletion tests/model_fields/test_field_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

NON_CONCRETE_FIELDS = (
models.ForeignObject,
models.ManyToManyField,
GenericForeignKey,
GenericRelation,
)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/requirements/py3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 10 additions & 21 deletions tests/update/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
"<django.db.models.fields.related.ManyToManyField: m2m_foo> "
"(only non-relations and foreign keys permitted)."
)
rel = "<django.db.models.fields.related.ManyToManyField: m2m_foo>"
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 <ManyToManyRel: update.bar> "
"(only non-relations and foreign keys permitted)."
)
rel = "<ManyToManyRel: update.bar>"
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 <ManyToOneRel: update.bar> "
"(only non-relations and foreign keys permitted)."
)
rel = "<ManyToOneRel: update.bar>"
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 <OneToOneRel: update.bar> "
"(only non-relations and foreign keys permitted)."
)
rel = "<OneToOneRel: update.bar>"
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 <OneToOneRel: update.uniquenumberchild> "
"(only non-relations and foreign keys permitted)."
)
rel = "<OneToOneRel: update.uniquenumberchild>"
msg = f"Cannot update model field {rel} (only concrete fields are permitted)."
with self.assertRaisesMessage(FieldError, msg):
UniqueNumber.objects.update(uniquenumberchild="whatever")

Expand Down
Loading