Squashing two apps in Django

When moving logic between apps in your Django project, you will usually encounter one impediment: the models.

Moving your models from one app to another app is a bit of a hassle, but there are several ways to do it. This is probably the best way, but you can find several methods on the web.

What these methods have in common is the fact that both apps will stay in the project. So what if we want to squash two apps into one, effectively deleting one app, including its migrations?

The following approach has been tested on Django 1.8.

Case: App A will be moved into App B

Problems to solve:

  • Migrations need to keep working, both on existing deployments and on new (local) deployments.
  • All migrations should be executable from start to finish on a new environment.
  • The migration state of App A’s models should be appended to App B’s state.
  • The migration state of App A needs to be cleaned up.

Renaming tables or not?

One variable in the approach below is whether you want to rename you existing database tables or not. As you might know, Django has a way of converting your model names to database tables in the format of “myapp_my_model, where app “myapp” its model class is named “MyModel”.

You can “pin” the database name by setting the db_table Meta attribute on your models.

Arguably, when moving App A’s models into App B, it’s not very clean if the actual database tables will stil be prefixed with ‘appa’ and ideally the database tables will be renamed. However, renaming database tables can be risky when they have relations, as most tables tend to have.

Renaming the tables using Django migrations is outside the scope of this post, so please refer to Google on how to do that.

Step 1

  1. Add ‘db_table’ definitions to your model Meta classes in App A. Make sure they’re equal to the current table names.
    class MyModel(models.Model):
        class Meta:
            db_table = 'appa_my_first_model'
  2. Don’t forget to run makemigrations. It will generate a new migration that will not actually change anything when executed, but it’s necessary to update Django’s migration state.
  3. Now, move all code except the models and migrations from App A to app B. Obviously, merge any code that needs merging, like views and urls.
  4. In App B’s models.py, import all App A’s models. Update all model imports throughout your project, and make sure that there are no imports from App A (You could also do this part at the end).
  5. Run your migrations.

Step 2

When you’ve chosen to rename your tables, this is where you should do it (by changing the db_table entries) and create a migration.

We will now remove App A’s tables from Django’s migration state using a state migration.

  1. Create a new empty migration in App A: ./manage.py makemigrations --empty
  2. Edit the new migration and refer to the next example. Note the SeparateDatabaseAndState operation where we tell the migration runner not to make any database changes.
    from django.db import migrations, models
    
    class Migration(migrations.Migration):
    
       dependencies = [
           ('appa', 'xxxx_auto_xxxxxxxx_xxxx'),
       ]
    
        state_operations = [
            migrations.DeleteModel('MyFirstModel'),
            migrations.DeleteModel('MySecondModel'),
        ]
    
        operations = [
            migrations.SeparateDatabaseAndState(
               database_operations=None,
               state_operations=state_operations)
        ]
  3. Move App A’s models to App B and create an automatic migration for App B (./manage.py makemigrations appb)
  4. Edit App B’s new state migration for App B. Edit the following items:
    1. Add dependency on App A’s final migration.
    2. Rename operations list to state_operations
    3. Add new operations list:
      operations = [
          migrations.SeparateDatabaseAndState(
              database_operations=None,
              state_operations=state_operations)
      ]
  5. Run the migrations, but make sure to use --fake-initial.

Step 3

The hardest part is over!

  1. Edit App B’s last migration (the one you just created):
    • Remove App A’s dependency from App B’s last migration
      • If App B’s last migration is an initial migration, just keep the dependencies list empty.
      • Otherwise, point to App B’s second-last migration.
  2. Remove the operations list and rename state_operations back to operations.
  3. Remove everything that’s left of App A.
  4. Disable App A in your project settings.

Done!