#24630 closed Bug (fixed)
Misleading/incorrect docs about RunPython and transactions
| Reported by: | Luca Corti | Owned by: | priidukull |
|---|---|---|---|
| Component: | Documentation | Version: | 1.8 |
| Severity: | Normal | Keywords: | uuidfiled migrations |
| Cc: | priidukull | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
As per #23932, I added an UUIDField to a model following the documentation at https://docs.djangoproject.com/en/1.8/howto/writing-migrations/. The database is PostgreSQL.
When running the migration I get this error:
File "manage.py", line 10, in <module>
execute_from_command_line(sys.argv)
File "venv/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
utility.execute()
File "venv/lib/python2.7/site-packages/django/core/management/__init__.py", line 330, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "venv/lib/python2.7/site-packages/django/core/management/base.py", line 390, in run_from_argv
self.execute(*args, **cmd_options)
File "venv/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
output = self.handle(*args, **options)
File "venv/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 221, in handle
executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial)
File "venv/lib/python2.7/site-packages/django/db/migrations/executor.py", line 110, in migrate
self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial)
File "venv/lib/python2.7/site-packages/django/db/migrations/executor.py", line 147, in apply_migration
state = migration.apply(state, schema_editor)
File "venv/lib/python2.7/site-packages/django/db/migrations/migration.py", line 115, in apply
operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
File "venv/lib/python2.7/site-packages/django/db/migrations/operations/fields.py", line 201, in database_forwards
schema_editor.alter_field(from_model, from_field, to_field)
File "venv/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 484, in alter_field
old_db_params, new_db_params, strict)
File "venv/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 637, in _alter_field
params,
File "venv/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 107, in execute
cursor.execute(sql, params)
File "venv/lib/python2.7/site-packages/django/db/backends/utils.py", line 79, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "venv/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
return self.cursor.execute(sql, params)
File "venv/lib/python2.7/site-packages/django/db/utils.py", line 97, in __exit__
six.reraise(dj_exc_type, dj_exc_value, traceback)
File "venv/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
return self.cursor.execute(sql, params)
django.db.utils.OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events
Change History (15)
comment:1 by , 11 years ago
| Description: | modified (diff) |
|---|
comment:4 by , 11 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
comment:5 by , 11 years ago
I tried to reproduce the bug with Python 2.7 and Django 1.7/1.8. Did not manage to reproduce the issue. I used the following migrations.
0001_initial.py:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations class Migration(migrations.Migration): dependencies = [ ] operations = [ migrations.CreateModel( name='Choice', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('choice_text', models.CharField(max_length=200)), ('votes', models.IntegerField(default=0)), ], ), migrations.CreateModel( name='Question', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('question_text', models.CharField(max_length=200)), ('pub_date', models.DateTimeField(verbose_name=b'date published')), ], ), migrations.AddField( model_name='choice', name='question', field=models.ForeignKey(to='polls.Question'), ), ]
0002_second.py:
# -*- coding: utf-8 -*- from __future__ import unicode_literals import random from django.db import migrations def populate_old_field(apps, schema_editor): Question = apps.get_model('polls', 'Question') for i in range(1, 100): q = Question(i, random.getrandbits(128), '2015-09-09') q.save() class Migration(migrations.Migration): dependencies = [ ('polls', '0001_initial'), ] operations = [ migrations.RunPython( populate_old_field ), ]
0003_third.py:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations def populate_new_field(apps, schema_editor): Question = apps.get_model('polls', 'Question') for q in Question.objects.all(): q.question_text_new = q.question_text q.save() class Migration(migrations.Migration): dependencies = [ ('polls', '0002_second'), ] operations = [ migrations.AddField( model_name='question', name='question_text_new', field=models.CharField(max_length=300, null=True), ), migrations.RunPython( populate_new_field, ), migrations.RemoveField( model_name='question', name='question_text', ), ]
comment:6 by , 11 years ago
| Resolution: | → needsinfo |
|---|---|
| Status: | assigned → closed |
comment:7 by , 11 years ago
| Cc: | added |
|---|
comment:8 by , 11 years ago
Hi, I've stumbled across this issue as well.
I'm not getting the error with your example either priidukull, so I reduced the code I had to a small example that still exhibits the bug (in both django 1.7 and 1.8, but only in psql, not sqlite)
0001_initial.py :
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='Parent',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
),
migrations.CreateModel(
name='Child',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('boolfield', models.BooleanField(default=True)),
('parent', models.ForeignKey(to='example.Parent')),
],
),
]
0002_add_data.py:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def add_data(apps, schema_editor):
Parent = apps.get_model("example", "Parent")
p = Parent(1)
p.save()
Child = apps.get_model("example", "Child")
c = Child(1, parent=p, boolfield=True)
c.save()
class Migration(migrations.Migration):
dependencies = [
('example', '0001_initial'),
]
operations = [
migrations.RunPython(
add_data
)
]
0003_change_field_type.py:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def convert_field(apps, schema_editor):
Child = apps.get_model("example", "Child")
for c in Child.objects.all():
c.textchoicefield = 'true' if c.boolfield else 'false'
c.save()
class Migration(migrations.Migration):
dependencies = [
('example', '0002_add_data'),
]
operations = [
migrations.AddField(
model_name='child',
name='textchoicefield',
field=models.TextField(default=b'none', choices=[(b'true', b'True'), (b'false', b'False'), (b'none', b'None')]),
preserve_default=True,
),
migrations.RunPython(convert_field),
migrations.RemoveField(
model_name='child',
name='boolfield',
),
]
If I move RemoveField out of the last migration and into its own, it all works fine.
comment:9 by , 11 years ago
| Resolution: | needsinfo |
|---|---|
| Status: | closed → new |
comment:10 by , 11 years ago
| Component: | Migrations → Documentation |
|---|---|
| Severity: | Normal → Release blocker |
| Summary: | UUIDField migration → Misleading/incorrect docs about RunPython and transactions |
| Triage Stage: | Unreviewed → Accepted |
It seems to me that the documentation that says, "By default, RunPython will run its contents inside a transaction" is misleading (added in 5a917cfef319df33ca30a3b27bd0b0533b8e63bb).
Each *migration* is run in its own transaction (if the database backend has can_rollback_ddl = True) because we use a SchemaEditor context manager when applying each migration, which creates a transaction.
I'm not sure we can make any changes code-wise to support mixing schema changes and RunPython operations in the same migration, but we should correct the documentation for current version of Django anyway.
Docs to fix:
- The second example migration needs to be split into at least two migrations. https://docs.djangoproject.com/en/1.8/howto/writing-migrations/#migrations-that-add-unique-fields
- "By default, RunPython will run its contents..." https://docs.djangoproject.com/en/1.8/ref/migration-operations/#runpython
comment:12 by , 10 years ago
| Severity: | Release blocker → Normal |
|---|
comment:13 by , 10 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
I've run into what I believe the same issue with Django 1.7.x. I have a model where I wanted to add and remove a new field.
The migration had 3 operations:
... operations = [ migrations.AddField( model_name='model', name='new_field', field=models.BooleanField(default=False), preserve_default=True, ), migrations.RunPython( populate_new_field, populate_old_field, ), migrations.RemoveField( model_name='model', name='old_field', ), ]The stacktrace looks exactly the same as quote in the original description. Not sure if this is a behavior expected from Django or not. The documentation on 1.7 and 1.8 states that
RunPythonshould run into its own transaction.This is happening when the table has already some data but not when the table is empty.