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
24 changes: 15 additions & 9 deletions docs/ref/models/fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -542,18 +542,20 @@ cross-site scripting attack.

If ``True``, this field is the primary key for the model.

If you don't specify ``primary_key=True`` for any field in your model, Django
will automatically add a field to hold the primary key, so you don't need to
set ``primary_key=True`` on any of your fields unless you want to override the
default primary-key behavior. The type of auto-created primary key fields can
be specified per app in :attr:`AppConfig.default_auto_field
<django.apps.AppConfig.default_auto_field>` or globally in the
:setting:`DEFAULT_AUTO_FIELD` setting. For more, see
If you don't specify ``primary_key=True`` for any field in your model and have
not defined a composite primary key, Django will automatically add a field to
hold the primary key. So, you don't need to set ``primary_key=True`` on any of
your fields unless you want to override the default primary-key behavior. The
type of auto-created primary key fields can be specified per app in
:attr:`AppConfig.default_auto_field <django.apps.AppConfig.default_auto_field>`
or globally in the :setting:`DEFAULT_AUTO_FIELD` setting. For more, see
:ref:`automatic-primary-key-fields`.

``primary_key=True`` implies :attr:`null=False <Field.null>` and
:attr:`unique=True <Field.unique>`. Only one primary key is allowed on an
object.
:attr:`unique=True <Field.unique>`. Only one field per model can set
``primary_key=True``. Composite primary keys must be defined using
:class:`CompositePrimaryKey` instead of setting this flag to ``True`` for all
fields to maintain this invariant.

The primary key field is read-only. If you change the value of the primary
key on an existing object and then save it, a new object will be created
Expand All @@ -562,6 +564,10 @@ alongside the old one.
The primary key field is set to ``None`` when
:meth:`deleting <django.db.models.Model.delete>` an object.

.. versionchanged:: 5.2

The ``CompositePrimaryKey`` field was added.

``unique``
----------

Expand Down
42 changes: 41 additions & 1 deletion docs/ref/models/meta.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ understand the capabilities of each model. The API is accessible through
the ``_meta`` attribute of each model class, which is an instance of an
``django.db.models.options.Options`` object.

Methods that it provides can be used to:
Methods and attributes that it provides can be used to:

* Retrieve all field instances of a model
* Retrieve a single field instance of a model by name
* Retrieve all fields that compose the primary key of a model

.. _model-meta-field-api:

Expand Down Expand Up @@ -118,3 +119,42 @@ Retrieving all field instances of a model
<django.db.models.fields.DateTimeField: date_joined>,
<django.db.models.fields.related.ManyToManyField: groups>,
<django.db.models.fields.related.ManyToManyField: user_permissions>)

Retrieving fields composing the primary key of a model
------------------------------------------------------

.. versionadded:: 5.2

.. attribute:: Options.pk_fields

Returns a list of the fields composing the primary key of a model.

When a :class:`composite primary key <django.db.models.CompositePrimaryKey>`
is defined on a model it will contain all the
:class:`fields <django.db.models.Field>` referenced by it.

.. code-block:: python

from django.db import models


class TenantUser(models.Model):
pk = models.CompositePrimaryKey("tenant_id", "id")
tenant_id = models.IntegerField()
id = models.IntegerField()

.. code-block:: pycon

>>> TenantUser._meta.pk_fields
[
<django.db.models.fields.IntegerField: tenant_id>,
<django.db.models.fields.IntegerField: id>
]

Otherwise it will contain the single field declared as the
:attr:`primary key <django.db.models.Field.primary_key>` of the model.

.. code-block:: pycon

>>> User._meta.pk_fields
[<django.db.models.fields.AutoField: id>]
49 changes: 49 additions & 0 deletions docs/topics/composite-primary-key.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,52 @@ field :exc:`.FieldError`.
This is also true of composite primary keys. Hence, you may want to set
:attr:`.Field.editable` to ``False`` on all primary key fields to exclude
them from ModelForms.

Building composite primary key ready applications
=================================================

Prior to the introduction of composite primary keys, the single field composing
the primary key of a model could be retrieved by introspecting the
:attr:`primary key <django.db.models.Field.primary_key>` attribute of its
fields:

.. code-block:: pycon

>>> pk_field = None
>>> for field in Product._meta.get_fields():
... if field.primary_key:
... pk_field = field
... break
...
>>> pk_field
<django.db.models.fields.AutoField: id>

Now that a primary key can be composed of multiple fields the
:attr:`primary key <django.db.models.Field.primary_key>` attribute can no
longer be relied upon to identify members of the primary key as it will be set
to ``False`` to maintain the invariant that at most one field per model will
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "at most one" implies it can be zero

Copy link
Contributor

Choose a reason for hiding this comment

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

In the composite primary key case, there can be 0 fields with primary_key=True (which is in the example below I believe)

Copy link
Member Author

Choose a reason for hiding this comment

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

Can't it? Previously the logic would be exactly once which had two invariant at most once and at least once.

Now with the introduction of CompositePrimaryKey it is possible for that no fields have primary_key=True which means that only the at most once invariant has been preserved?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see your point

The way I always thought about this is it's still exactly one field (the pk field)

This is why I was asking previously if we should force users to set primary_key=True on CompositePrimaryKey or not explicitly

Copy link
Member Author

@charettes charettes Jan 13, 2025

Choose a reason for hiding this comment

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

Ahh I see what you mean, I was not considering CompositePrimaryKey to be a field itself given its virtual nature.

have this attribute set to ``True``:

.. code-block:: pycon

>>> pk_fields = []
>>> for field in OrderLineItem._meta.get_fields():
... if field.primary_key:
... pk_fields.append(field)
...
>>> pk_fields
[]

In order to build application code that properly handles composite primary
keys the :attr:`_meta.pk_fields <django.db.models.options.Options.pk_fields>`
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we need to add a versionchanged:: 5.2 annotation that pk_fields is newly added here?

Copy link
Member

Choose a reason for hiding this comment

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

This entire page has .. versionadded:: 5.2 annotation, so we don't need extra version annotations (IMO).

attribute should be used instead:

.. code-block:: pycon

>>> Product._meta.pk_fields
[<django.db.models.fields.AutoField: id>]
>>> OrderLineItem._meta.pk_fields
[
<django.db.models.fields.ForeignKey: product>,
<django.db.models.fields.ForeignKey: order>
]
Loading