Commit 307acc74 authored by Tim Graham's avatar Tim Graham
Browse files

Fixed #24630 -- Clarified docs about RunPython transactions.

Thanks Markus Holtermann for review.
parent fc1eea59
Loading
Loading
Loading
Loading
+54 −31
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ Then, to leverage this in your migrations, do the following::

    def forwards(apps, schema_editor):
        # Your migration code goes here
        ...

    class Migration(migrations.Migration):

@@ -83,44 +84,76 @@ Therefore, the following steps should be taken. In this example, we'll add a
non-nullable :class:`~django.db.models.UUIDField` with a default value. Modify
the respective field according to your needs.

* Add the field on your model with ``default=...`` and ``unique=True``
  arguments. In the example, we use ``uuid.uuid4`` for the default.
* Add the field on your model with ``default=uuid.uuid4`` and ``unique=True``
  arguments (choose an appropriate default for the type of the field you're
  adding).

* Run the :djadmin:`makemigrations` command.
* Run the :djadmin:`makemigrations` command. This should generate a migration
  with an ``AddField`` operation.

* Edit the created migration file.
* Generate two empty migration files for the same app by running
  ``makemigrations myapp --empty`` twice. We've renamed the migration files to
  give them meaningful names in the examples below.

* Copy the ``AddField`` operation from the auto-generated migration (the first
  of the three new files) to the last migration and change ``AddField`` to
  ``AlterField``. For example:

  .. snippet::
    :filename: 0006_remove_uuid_null.py

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals

    from django.db import migrations, models
    import uuid

  The generated migration class should look similar to this::

    class Migration(migrations.Migration):

        dependencies = [
            ('myapp', '0003_auto_20150129_1705'),
            ('myapp', '0005_populate_uuid_values'),
        ]

        operations = [
            migrations.AddField(
            migrations.AlterField(
                model_name='mymodel',
                name='uuid',
                field=models.UUIDField(max_length=32, unique=True, default=uuid.uuid4),
                field=models.UUIDField(default=uuid.uuid4, unique=True),
            ),
        ]

  You will need to make three changes:
* Edit the first migration file. The generated migration class should look
  similar to this:

  .. snippet::
    :filename: 0004_add_uuid_field.py

  * Add a second :class:`~django.db.migrations.operations.AddField` operation
    copied from the generated one and change it to
    :class:`~django.db.migrations.operations.AlterField`.
    class Migration(migrations.Migration):

  * On the first operation (``AddField``), change ``unique=True`` to
    ``null=True`` -- this will create the intermediary null field.
        dependencies = [
            ('myapp', '0003_auto_20150129_1705'),
        ]

  * Between the two operations, add a
        operations = [
            migrations.AddField(
                model_name='mymodel',
                name='uuid',
                field=models.UUIDField(default=uuid.uuid4, unique=True),
            ),
        ]

  Change ``unique=True`` to ``null=True`` -- this will create the intermediary
  null field and defer creating the unique constraint until we've populated
  unique values on all the rows.

* In the first empty migration file, add a
  :class:`~django.db.migrations.operations.RunPython` or
  :class:`~django.db.migrations.operations.RunSQL` operation to generate a
    unique value (UUID in the example) for each existing row.
  unique value (UUID in the example) for each existing row. For example:

  The resulting migration should look similar to this::
  .. snippet::
    :filename: 0005_populate_uuid_values.py

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
@@ -137,25 +170,15 @@ the respective field according to your needs.
    class Migration(migrations.Migration):

        dependencies = [
            ('myapp', '0003_auto_20150129_1705'),
            ('myapp', '0004_add_uuid_field'),
        ]

        operations = [
            migrations.AddField(
                model_name='mymodel',
                name='uuid',
                field=models.UUIDField(default=uuid.uuid4, null=True),
            ),
            # omit reverse_code=... if you don't want the migration to be reversible.
            migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
            migrations.AlterField(
                model_name='mymodel',
                name='uuid',
                field=models.UUIDField(default=uuid.uuid4, unique=True),
            ),
        ]

* Now you can apply the migration as usual with the :djadmin:`migrate` command.
* Now you can apply the migrations as usual with the :djadmin:`migrate` command.

  Note there is a race condition if you allow objects to be created while this
  migration is running. Objects created after the ``AddField`` and before
+16 −4
Original line number Diff line number Diff line
@@ -322,11 +322,23 @@ or that you use :class:`SeparateDatabaseAndState` to add in operations that will
reflect your changes to the model state - otherwise, the versioned ORM and
the autodetector will stop working correctly.

By default, ``RunPython`` will run its contents inside a transaction even
on databases that do not support DDL transactions (for example, MySQL and
By default, ``RunPython`` will run its contents inside a transaction on
databases that do not support DDL transactions (for example, MySQL and
Oracle). This should be safe, but may cause a crash if you attempt to use
the ``schema_editor`` provided on these backends; in this case, please
set ``atomic=False``.
the ``schema_editor`` provided on these backends; in this case, pass
``atomic=False`` to the ``RunPython`` operation.

On databases that do support DDL transactions (SQLite and PostgreSQL),
``RunPython`` operations do not have any transactions automatically added
besides the transactions created for each migration (the ``atomic`` parameter
has no effect on these databases). Thus, on PostgreSQL, for example, you should
avoid combining schema changes and ``RunPython`` operations in the same
migration or you may hit errors like ``OperationalError: cannot ALTER TABLE
"mytable" because it has pending trigger events``.

If you have a different database and aren't sure if it supports DDL
transactions, check the ``django.db.connection.features.can_rollback_ddl``
attribute.

.. warning::