How to handle exceptions in a Django migration?
Question: How do I catch an exception in a Django migration?

I have a migration that, because of various legacy reasons, I expect to fail
sometimes. I want to be able to catch that error and run some error handling
code in that case.

Specifically, I'm renaming a table, and sometimes the destination table
already exists and I want to merge the contents of the old and new tables,
then delete the old one.

I'm running Django 1.7 ( :( ) and we're planning on upgrading to 1.8 but it
hasn't happened yet.

My migration is:

    
    
    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    
    from django.db import models, migrations
    
    
    class Migration(migrations.Migration):
    
        dependencies = [
            ('main', '0007_migration_name'),
        ]
    
        operations = [
            migrations.AlterModelTable(
                name='table_name',
                table='LegacyTableName',
            ),
        ]
    

When I run this, I get

    
    
    Traceback (most recent call last):
      File "manage.py", line 10, in <module>
        execute_from_command_line(sys.argv)
      File ".../django/core/management/__init__.py", line 385, in execute_from_command_line
        utility.execute()
      File ".../django/core/management/__init__.py", line 377, in execute
        self.fetch_command(subcommand).run_from_argv(self.argv)
      File ".../django/core/management/base.py", line 288, in run_from_argv
        self.execute(*args, **options.__dict__)
      File ".../django/core/management/base.py", line 338, in execute
        output = self.handle(*args, **options)
      File ".../django/core/management/commands/migrate.py", line 161, in handle
        executor.migrate(targets, plan, fake=options.get("fake", False))
      File ".../django/db/migrations/executor.py", line 68, in migrate
        self.apply_migration(migration, fake=fake)
      File ".../django/db/migrations/executor.py", line 102, in apply_migration
        migration.apply(project_state, schema_editor)
      File ".../django/db/migrations/migration.py", line 108, in apply
        operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
      File ".../django/db/migrations/operations/models.py", line 236, in database_forwards
        new_model._meta.db_table,
      File ".../django/db/backends/schema.py", line 350, in alter_db_table
        "new_table": self.quote_name(new_db_table),
      File ".../django/db/backends/schema.py", line 111, in execute
        cursor.execute(sql, params)
      File ".../django/db/backends/utils.py", line 81, in execute
        return super(CursorDebugWrapper, self).execute(sql, params)
      File ".../django/db/backends/utils.py", line 65, in execute
        return self.cursor.execute(sql, params)
      File ".../django/db/utils.py", line 94, in __exit__
        six.reraise(dj_exc_type, dj_exc_value, traceback)
      File ".../django/db/backends/utils.py", line 65, in execute
        return self.cursor.execute(sql, params)
      File ".../django/db/backends/mysql/base.py", line 129, in execute
        return self.cursor.execute(query, args)
      File ".../MySQLdb/cursors.py", line 226, in execute
        self.errorhandler(self, exc, value)
      File ".../MySQLdb/connections.py", line 36, in defaulterrorhandler
        raise errorvalue
    django.db.utils.OperationalError: (1050, "Table 'LegacyTableName' already exists")
    

All that's provided in the migration itself is the `operations` list, and
there doesn't seem to be an optional error-handling parameter in [the
docs](https://docs.djangoproject.com/en/1.9/ref/migration-
operations/#altermodeltable).

How do I catch the OperationalError so I can run some Python to merge the
tables?


Answer: The problem with trying to catch database exceptions in Python is that they
may not be specific enough - e.g., `OperationalError` could arise for various
reasons (only one of which is that the table name has already been changed).

I would suggest that rather than trying to catch exceptions you write your own
migration function that does whatever checks/modifications are necessary. See
the [documentation on
`RunPython`](https://docs.djangoproject.com/en/1.9/ref/migration-
operations/#runpython).

> This is generally the operation you would use to create data migrations, run
> custom data updates and alterations, and anything else you need access to an
> ORM and/or Python code for.

In your case you would write a function that checks whether the table exists
and performs some actions for either case.

There are some database-specific issues to be aware of when writing these
functions, e.g., :

> on PostgreSQL, for example, you should avoid combining schema changes and
> RunPython operations in the same migration or you may hit errors.

