Kaydet (Commit) cc4ee062 authored tarafından Tim Graham's avatar Tim Graham

[1.8.x] Fixed #24630 -- Clarified docs about RunPython transactions.

Thanks Markus Holtermann for review.

Backport of 307acc74 from master
üst 6108febe
...@@ -57,6 +57,7 @@ Then, to leverage this in your migrations, do the following:: ...@@ -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,44 +84,76 @@ Therefore, the following steps should be taken. In this example, we'll add a ...@@ -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 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.
* 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('myapp', '0003_auto_20150129_1705'), ('myapp', '0005_populate_uuid_values'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AlterField(
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: * 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):
dependencies = [
('myapp', '0003_auto_20150129_1705'),
]
* Add a second :class:`~django.db.migrations.operations.AddField` operation operations = [
copied from the generated one and change it to migrations.AddField(
:class:`~django.db.migrations.operations.AlterField`. model_name='mymodel',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
* On the first operation (``AddField``), change ``unique=True`` to Change ``unique=True`` to ``null=True`` -- this will create the intermediary
``null=True`` -- this will create the intermediary null field. null field and defer creating the unique constraint until we've populated
unique values on all the rows.
* Between the two operations, add a * In the first empty migration file, add a
:class:`~django.db.migrations.operations.RunPython` or :class:`~django.db.migrations.operations.RunPython` or
:class:`~django.db.migrations.operations.RunSQL` operation to generate a :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 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
...@@ -137,25 +170,15 @@ the respective field according to your needs. ...@@ -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
......
...@@ -331,11 +331,23 @@ or that you use :class:`SeparateDatabaseAndState` to add in operations that will ...@@ -331,11 +331,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::
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment