summaryrefslogtreecommitdiff
path: root/docs/topics
diff options
context:
space:
mode:
Diffstat (limited to 'docs/topics')
-rw-r--r--docs/topics/composite-primary-key.txt183
-rw-r--r--docs/topics/index.txt1
2 files changed, 184 insertions, 0 deletions
diff --git a/docs/topics/composite-primary-key.txt b/docs/topics/composite-primary-key.txt
new file mode 100644
index 0000000000..9e5234ca9f
--- /dev/null
+++ b/docs/topics/composite-primary-key.txt
@@ -0,0 +1,183 @@
+======================
+Composite primary keys
+======================
+
+.. versionadded:: 5.2
+
+In Django, each model has a primary key. By default, this primary key consists
+of a single field.
+
+In most cases, a single primary key should suffice. In database design,
+however, defining a primary key consisting of multiple fields is sometimes
+necessary.
+
+To use a composite primary key, when creating a model set the ``pk`` field to
+be a :class:`.CompositePrimaryKey`::
+
+ class Product(models.Model):
+ name = models.CharField(max_length=100)
+
+
+ class Order(models.Model):
+ reference = models.CharField(max_length=20, primary_key=True)
+
+
+ class OrderLineItem(models.Model):
+ pk = models.CompositePrimaryKey("product_id", "order_id")
+ product = models.ForeignKey(Product, on_delete=models.CASCADE)
+ order = models.ForeignKey(Order, on_delete=models.CASCADE)
+ quantity = models.IntegerField()
+
+This will instruct Django to create a composite primary key
+(``PRIMARY KEY (product_id, order_id)``) when creating the table.
+
+A composite primary key is represented by a ``tuple``:
+
+.. code-block:: pycon
+
+ >>> product = Product.objects.create(name="apple")
+ >>> order = Order.objects.create(reference="A755H")
+ >>> item = OrderLineItem.objects.create(product=product, order=order, quantity=1)
+ >>> item.pk
+ (1, "A755H")
+
+You can assign a ``tuple`` to a composite primary key. This sets the associated
+field values.
+
+.. code-block:: pycon
+
+ >>> item = OrderLineItem(pk=(2, "B142C"))
+ >>> item.pk
+ (2, "B142C")
+ >>> item.product_id
+ 2
+ >>> item.order_id
+ "B142C"
+
+A composite primary key can also be filtered by a ``tuple``:
+
+.. code-block:: pycon
+
+ >>> OrderLineItem.objects.filter(pk=(1, "A755H")).count()
+ 1
+
+We're still working on composite primary key support for
+:ref:`relational fields <cpk-and-relations>`, including
+:class:`.GenericForeignKey` fields, and the Django admin. Models with composite
+primary keys cannot be registered in the Django admin at this time. You can
+expect to see this in future releases.
+
+Migrating to a composite primary key
+====================================
+
+Django doesn't support migrating to, or from, a composite primary key after the
+table is created. It also doesn't support adding or removing fields from the
+composite primary key.
+
+If you would like to migrate an existing table from a single primary key to a
+composite primary key, follow your database backend's instructions to do so.
+
+Once the composite primary key is in place, add the ``CompositePrimaryKey``
+field to your model. This allows Django to recognize and handle the composite
+primary key appropriately.
+
+While migration operations (e.g. ``AddField``, ``AlterField``) on primary key
+fields are not supported, ``makemigrations`` will still detect changes.
+
+In order to avoid errors, it's recommended to apply such migrations with
+``--fake``.
+
+Alternatively, :class:`.SeparateDatabaseAndState` may be used to execute the
+backend-specific migrations and Django-generated migrations in a single
+operation.
+
+.. _cpk-and-relations:
+
+Composite primary keys and relations
+====================================
+
+:ref:`Relationship fields <relationship-fields>`, including
+:ref:`generic relations <generic-relations>` do not support composite primary
+keys.
+
+For example, given the ``OrderLineItem`` model, the following is not
+supported::
+
+ class Foo(models.Model):
+ item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)
+
+Because ``ForeignKey`` currently cannot reference models with composite primary
+keys.
+
+To work around this limitation, ``ForeignObject`` can be used as an
+alternative::
+
+ class Foo(models.Model):
+ item_order_id = models.IntegerField()
+ item_product_id = models.CharField(max_length=20)
+ item = models.ForeignObject(
+ OrderLineItem,
+ on_delete=models.CASCADE,
+ from_fields=("item_order_id", "item_product_id"),
+ to_fields=("order_id", "product_id"),
+ )
+
+``ForeignObject`` is much like ``ForeignKey``, except that it doesn't create
+any columns (e.g. ``item_id``), foreign key constraints or indexes in the
+database.
+
+.. warning::
+
+ ``ForeignObject`` is an internal API. This means it is not covered by our
+ :ref:`deprecation policy <internal-release-deprecation-policy>`.
+
+Composite primary keys and database functions
+=============================================
+
+Many database functions only accept a single expression.
+
+.. code-block:: sql
+
+ MAX("order_id") -- OK
+ MAX("product_id", "order_id") -- ERROR
+
+As a consequence, they cannot be used with composite primary key references as
+they are composed of multiple column expressions.
+
+.. code-block:: python
+
+ Max("order_id") # OK
+ Max("pk") # ERROR
+
+Composite primary keys in forms
+===============================
+
+As a composite primary key is a virtual field, a field which doesn't represent
+a single database column, this field is excluded from ModelForms.
+
+For example, take the following form::
+
+ class OrderLineItemForm(forms.ModelForm):
+ class Meta:
+ model = OrderLineItem
+ fields = "__all__"
+
+This form does not have a form field ``pk`` for the composite primary key:
+
+.. code-block:: pycon
+
+ >>> OrderLineItemForm()
+ <OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>
+
+Setting the primary composite field ``pk`` as a form field raises an unknown
+field :exc:`.FieldError`.
+
+.. admonition:: Primary key fields are read only
+
+ If you change the value of a primary key on an existing object and then
+ save it, a new object will be created alongside the old one (see
+ :attr:`.Field.primary_key`).
+
+ 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.
diff --git a/docs/topics/index.txt b/docs/topics/index.txt
index ffb9fa9d92..4f837c81e2 100644
--- a/docs/topics/index.txt
+++ b/docs/topics/index.txt
@@ -19,6 +19,7 @@ Introductions to all the key parts of Django you'll need to know:
auth/index
cache
conditional-view-processing
+ composite-primary-key
signing
email
i18n/index