Tuesday, January 27, 2015

Django Migrations - Moving Django model to another application and renaming Postgresql table and sequence

I started my Django project having just one application. The application started getting bigger and bigger and the day came when I noticed there were cohesive groups of model classes and functionality that could be splitted apart in a second app. Just moving the models and logic was tedious but quite simple, happy to have the job done I went ahead and typed

python manage.py makemigrations

only to find out a bunch of errors showed up.

Django does not automatically detect the models where moved and automatically write the proper migration, it doesn't seem smart enough to read our minds, so here is what I did.

1. First option:

    The first workaround I found was to add

    class Meta:
      db_table = 'old tablename'
      app_label = 'old app'


  To the models I moved. Although it works fine, it's an awful hack to just have the code someplace while the models still belong to the old app.

2. Stack Overflow blessing


   A more profound search led me to this reply in one post

   Which describes in detail the process needed to move a model to another app. It makes use of state_operations and database_operations separately in a migration inside the old application, as showed below

class Migration(migrations.Migration):
    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations) ]

I am not including the full code here, as you can take a look in Stack Overflow directly. It also involves a second migration in the destination application only to create the model with a state_operation and the use of SeparateDatabaseAndState again (the table is already in the database, so we trying to create it again would cause an error)

The process is simple and it does not require any data migration, which makes it delightful (thanks to ozan)

In my case I am using Postgresql, so I went and took a look into the database to see what was going on


As the picture shows I had the table ui_environments and the sequence table ui_environments_id_seq
The detail for those tables displays how ui_environmens reference the sequence, as well as sequence numbers at the moment prior the migration


Running the above migration schema (both migrations mentioned) to rename ui_environments to spatial_geom (as it's now part of the 'spatial' application, and I chose to take the opportunity to also rename the model class to Geom as it made more sense at this point), led to the following tables

We can see spatial_geom is there, ui_environments was deleted, but ui_environments_id_seq remained the same. Inspecting both tables, the table was correctly renamed, and it still points to the same sequence. This all looks fine, but  I wanted to clean up the database and rename also the sequence to avoid confusion.




Of course, I tried to do this with Django migrations, to learn a bit more about it. So here is how I did it.


Migrations' RunSql to run custom SQL to alter your database


Digging into the documentation, there's no command to just alter a table name which does not belong to a model, so RunSql seemed the best fit. As showed in the documentation, we need to pass in the SQL we want to run, and optionally, the SQL to revert the operation and the operation to reflect it in the migrations' state, if needed. 
As renaming the sequence did not require any migration state change, I just passed in the first two parameters.

I ran

python manage.py makemigrations spatial --empty


to create an empty migration in my application, and then modified it with the custom statement.


# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
    dependencies = [
        ('spatial', '0005_environmentgeom'),
    ]
    operations = [
        migrations.RunSQL(sql=
            "ALTER SEQUENCE ui_environments_id_seq RENAME TO spatial_geom_id_seq", 
            reverse_sql="ALTER SEQUENCE spatial_geom_id_seq RENAME TO ui_environments_id_seq"),
    ]


then run

python manage.py migrate spatial


and took a look inside the db



The sequence name had changed and it was correctly referenced by the spatial_geom table. Sequence numbers remained unaltered as required. The only strange thing I noticed was that sequence_name field in the sequence had the old value. Digging a bit into this I found this issue with Postgresql sequences, which states that the field is not used anywhere, so it does not really matter that the ALTER SEQUENCE statement does not change it. So we are happy to ignore.

Summary

Django Migrations perform an excellent job inside one application, but some changes across applications may require creating some custom migrations to do the job. In this post I looked into moving a model between applications, and cleaning up the database tables afterwards, for any change that Django alone might have not performed. Looking at final result, indexes also remained with the old name and a similar approach should be taken if we want to tidy up further.











No comments:

Post a Comment