diff options
Diffstat (limited to 'docs/topics')
| -rw-r--r-- | docs/topics/composite-primary-key.txt | 183 | ||||
| -rw-r--r-- | docs/topics/index.txt | 1 |
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 |
