Fixed #24630 -- Clarified docs about RunPython transactions.

Thanks Markus Holtermann for review.
This commit is contained in:
Tim Graham 2015-05-13 08:19:51 -04:00
parent fc1eea59c0
commit 307acc745a
2 changed files with 70 additions and 35 deletions

View File

@ -57,6 +57,7 @@ Then, to leverage this in your migrations, do the following::
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
# Your migration code goes here # Your migration code goes here
...
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -83,14 +84,50 @@ 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 non-nullable :class:`~django.db.models.UUIDField` with a default value. Modify
the respective field according to your needs. the respective field according to your needs.
* Add the field on your model with ``default=...`` and ``unique=True`` * Add the field on your model with ``default=uuid.uuid4`` and ``unique=True``
arguments. In the example, we use ``uuid.uuid4`` for the default. 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.
The generated migration class should look similar to this:: * 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
class Migration(migrations.Migration):
dependencies = [
('myapp', '0005_populate_uuid_values'),
]
operations = [
migrations.AlterField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
* Edit the first migration file. The generated migration class should look
similar to this:
.. snippet::
:filename: 0004_add_uuid_field.py
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -102,25 +139,21 @@ the respective field according to your needs.
migrations.AddField( migrations.AddField(
model_name='mymodel', model_name='mymodel',
name='uuid', 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: 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.
* Add a second :class:`~django.db.migrations.operations.AddField` operation * In the first empty migration file, add a
copied from the generated one and change it to :class:`~django.db.migrations.operations.RunPython` or
:class:`~django.db.migrations.operations.AlterField`. :class:`~django.db.migrations.operations.RunSQL` operation to generate a
unique value (UUID in the example) for each existing row. For example:
* On the first operation (``AddField``), change ``unique=True`` to .. snippet::
``null=True`` -- this will create the intermediary null field. :filename: 0005_populate_uuid_values.py
* Between the two operations, 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.
The resulting migration should look similar to this::
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
@ -137,25 +170,15 @@ the respective field according to your needs.
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('myapp', '0003_auto_20150129_1705'), ('myapp', '0004_add_uuid_field'),
] ]
operations = [ 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. # omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), 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 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 migration is running. Objects created after the ``AddField`` and before

View File

@ -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 reflect your changes to the model state - otherwise, the versioned ORM and
the autodetector will stop working correctly. the autodetector will stop working correctly.
By default, ``RunPython`` will run its contents inside a transaction even By default, ``RunPython`` will run its contents inside a transaction on
on databases that do not support DDL transactions (for example, MySQL and 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 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 the ``schema_editor`` provided on these backends; in this case, pass
set ``atomic=False``. ``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:: .. warning::