Browse Source

Fixed #36075 -- Documented how to introspect composite primary keys.

Document _meta.pk_fields and interactions between Field.primary_key and
CompositePrimaryKey.

Thanks Mariusz for the review.
Simon Charette 2 months ago
parent
commit
e580926d74
3 changed files with 105 additions and 10 deletions
  1. 15 9
      docs/ref/models/fields.txt
  2. 41 1
      docs/ref/models/meta.txt
  3. 49 0
      docs/topics/composite-primary-key.txt

+ 15 - 9
docs/ref/models/fields.txt

@@ -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
@@ -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``
 ----------
 

+ 41 - 1
docs/ref/models/meta.txt

@@ -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:
 
@@ -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 - 0
docs/topics/composite-primary-key.txt

@@ -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
+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>`
+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>
+    ]