Fixed #35957 -- Allowed auto fields to be used in composite primary keys. by shangxiao · Pull Request #19585 · django/django · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

Fixed #35957 -- Allowed auto fields to be used in composite primary keys. #19585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

shangxiao
Copy link
Contributor

@shangxiao shangxiao commented Jun 23, 2025

Trac ticket number

ticket-35957

Branch description

Allow auto fields to be used as part of a composite PK:

  • It appears all that was necessary was to remove the check and to remove setting the primary_key from the deconstruction...
  • SQLite does not allow this, ref https://www.sqlite.org/autoinc.html - whilst not explicitly stated it only talks about using AUTOINCREMENT after PRIMARY KEY on the column declaration

Checklist

  • This PR targets the main branch.
  • The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • I have checked the "Has patch" ticket flag in the Trac system.
  • I have added or updated relevant tests.
  • I have added or updated relevant docs, including release notes if applicable.
  • I have attached screenshots in both light and dark modes for any UI changes.

@@ -2792,14 +2792,24 @@ def __init__(self, *args, **kwargs):
def check(self, **kwargs):
return [
*super().check(**kwargs),
*self._check_primary_key(),
*self._check_primary_key(kwargs.get("databases", ["default"])),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the tests I saw was passing databases kwarg into check() but in another test it was absent. Is this a correct assumption here that databases may be passed in?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should default to an empty list, see how CharField.check does it

def check(self, **kwargs):
databases = kwargs.get("databases") or []
return [
*super().check(**kwargs),
*self._check_db_collation(databases),
*self._check_max_length_attribute(**kwargs),
]

Comment on lines 2802 to 2805
all(
connections[db].features.supports_autofields_in_composite_pk
for db in databases
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simon had recently mentioned that only checking the default connection's features was harmful in a multi-engine setup. Is this what we want to do moving forward or just continue to use the default connection? If so we may need to abstract this as it will catch folks out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose what I should instead be doing here is iterating over connections in full 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also just wonder whether we may want to add something like this to Field:

    @cached_property
    def part_of_pk(self):
        return self.primary_key or self in getattr(self.model._meta.pk, "pk", [])

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what we want to do moving forward or just continue to use the default connection? If so we may need to abstract this as it will catch folks out.

We should most definitely not use the default connection. If not specified the check should not be performed.

Comment on lines +65 to +71
class AutoId(models.Model):
pk = models.CompositePrimaryKey("id", "name")
id = models.BigAutoField()
name = models.CharField(max_length=255)

class Meta:
required_db_features = ["supports_autofields_in_composite_pk"]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does someone know how to skip checks when required_db_features is set? My tests were failing because it was still failing the check when run with SQLite.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to how Constraint.check is implemented

e.g.

if not (
connection.features.supports_table_check_constraints
or "supports_table_check_constraints" in model._meta.required_db_features
):

@@ -220,6 +220,12 @@ def test_model_forms(self):
):
self.assertIsNone(modelform_factory(Comment, fields=["pk"]))

def test_autoid_in_composite_pk(self):
obj = AutoId.objects.create(name="Automatically generated ID")
self.assertEqual(obj.id, 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the correct way to check an auto field's initial value?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert it's not None?

connections[db].features.supports_autofields_in_composite_pk
for db in databases
)
and self in getattr(self.model._meta.pk, "fields", [])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
and self in getattr(self.model._meta.pk, "fields", [])
and self in self.model._meta.pk_fields

@@ -2792,14 +2792,24 @@ def __init__(self, *args, **kwargs):
def check(self, **kwargs):
return [
*super().check(**kwargs),
*self._check_primary_key(),
*self._check_primary_key(kwargs.get("databases", ["default"])),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should default to an empty list, see how CharField.check does it

def check(self, **kwargs):
databases = kwargs.get("databases") or []
return [
*super().check(**kwargs),
*self._check_db_collation(databases),
*self._check_max_length_attribute(**kwargs),
]

Comment on lines 2802 to 2805
all(
connections[db].features.supports_autofields_in_composite_pk
for db in databases
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what we want to do moving forward or just continue to use the default connection? If so we may need to abstract this as it will catch folks out.

We should most definitely not use the default connection. If not specified the check should not be performed.

Comment on lines +65 to +71
class AutoId(models.Model):
pk = models.CompositePrimaryKey("id", "name")
id = models.BigAutoField()
name = models.CharField(max_length=255)

class Meta:
required_db_features = ["supports_autofields_in_composite_pk"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to how Constraint.check is implemented

e.g.

if not (
connection.features.supports_table_check_constraints
or "supports_table_check_constraints" in model._meta.required_db_features
):

@@ -220,6 +220,12 @@ def test_model_forms(self):
):
self.assertIsNone(modelform_factory(Comment, fields=["pk"]))

def test_autoid_in_composite_pk(self):
obj = AutoId.objects.create(name="Automatically generated ID")
self.assertEqual(obj.id, 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert it's not None?

@shangxiao
Copy link
Contributor Author

Thanks Simon, have applied your suggestions 👍

Copy link
Member

@charettes charettes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that even when primary_key=True was not specified on AutoField initialization it effectively was always marked as is due to the deconstruct implementation.

I suspect we'll have to adjust __init__ to default primary_key to True and force an explicit primary_key=False to be specified when it's meant to be used in a composite pk.

(
all(
connections[db].features.supports_autofields_in_composite_pk
for db in databases
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for db in databases
for db in databases
if router.allow_migrate_model(db, self.model):

@@ -2810,7 +2825,6 @@ def _check_primary_key(self):
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
del kwargs["blank"]
kwargs["primary_key"] = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might cause some issues for already generated migrations as for cases where primary_key was not specified they'll now turn from True to False and be detected as changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.