# How to safely move a model to another app in Django

<h4 id="section-intro">Intro</h4>

<p>
In most cases, we start building our Django projects with a small number of Django <a href="https://docs.djangoproject.com/en/3.0/ref/applications/#projects-and-applications" target="_blank" rel="noopener">apps</a>. At some point, our projects grow and apps become larger which complicates the code maintenance. And we start thinking about refactoring - splitting a big Django app into smaller logically separated apps.
</p>

<p>We create a new app and move the corresponding model(s) there.</p>

<p>Taking into consideration the fact that under the hood Django automatically derives the name of the database table from the <strong>name of your model class</strong> and the <strong>app that contains it</strong>, we decide to <strong>make migrations</strong> (moving model(s) into another app will force Django to look into different table name than it was before). 
</p>

<blockquote>
A model’s database table name is constructed by joining the model’s <code>app label</code> – the name you used in <code>manage.py startapp</code> – to the model’s class name, with an underscore between them(<code>%app label%_%model name%</code>).
<br>
<br>
If you have an called app <strong>cars</strong> (<code>manage.py startapp cars</code>), a model defined as class <code>Car</code> will have a database table named <code>cars_car</code>.
</blockquote>

<p>After taking a look into newly generated migration files, we see that instead of detecting a database table name change, Django detects a new model(table) in the new app that needs to be created and a model(table) that needs to be deleted in the old app. That means, if we apply newly generated migrations, we will lose the table and the data corresponding to the <strong>old-app-model</strong> and we will have a new and <strong>empty</strong> table which represent the <strong>new-app-model</strong> in database.
</p>

<p>
Often we are facing this problem when we have some important data in the moved model, even more, we have other models that are dependents(have foreign keys) of the moved model. In this case, auto-generated migrations won't help you to solve this problem yet(Django 3.0.5), unless you are really bored and looking for some crazy adventures 🌶.
</p>

<p>
In this article, we will discuss two migration strategies that will help us to move model(s) into other apps without deleting or creating new tables and/or foreign keys.
</p>

<h4 id="section-getting-started">Getting started</h4>

<p>To demonstrate the strategy, we are going to create a simple project:</p>

<pre><code>$ django-admin startproject mobilestore</code></pre>

<p>with one django app:</p>

<pre><code>mobilestore/$ django-admin startapp phones</code></pre>

<p>and with a couple of models in it:</p>

<p><i><strong>phones/models.py</strong></i></p>

<pre><code>from django.db import models

class Brand(models.Model):
    name = models.CharField(max_length=128)

class Phone(models.Model):
    model = models.CharField(max_length=128)
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
</code></pre>

<p>registering <code>phones</code> app:</p>

<p><i><strong>mobilestore/settings.py</strong></i></p>

<pre><code class="hljs python">INSTALLED_APPS = [
    ...
    'phones',
    ...
]
</code></pre>

<p>generating migrations:</p>

<pre><code>mobilestore/$ python manage.py makemigrations phones
Migrations for 'phones':
  phones/migrations/0001_initial.py
    - Create model Brand
    - Create model Phone
</code></pre>

<p>outputting SQL behind the new migration file:</p>

<pre><code class="hljs sql">mobilestore/$ python manage.py sqlmigrate phones 0001
BEGIN;
--
-- Create model Brand
--
CREATE TABLE "phones_brand" -- <<< name of the table
("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(128) NOT NULL);
--
-- Create model Phone
--
CREATE TABLE "phones_phone"  -- <<< name of the table
("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "model" varchar(128) NOT NULL, "brand_id" integer NOT NULL REFERENCES "phones_brand" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "phones_phone_brand_id_b4e25eb0" ON "phones_phone" ("brand_id");
COMMIT;
</code></pre>

<p>As we expected, Django generated names for new tables using the combination of app_label and model class name.</p>

<p>Now, let's apply them:</p>

<pre><code>mobilestore/$ python manage.py migrate phones
Operations to perform:
  Apply all migrations: phones
Running migrations:
  Applying phones.0001_initial... OK
</code></pre>

<h4 id="section-problem">Problem</h4>

<p>After a while, for decoupling purposes, we decide to create a new **brands** app and move the <code>Brand</code> model there:</p>

<pre><code>mobilestore/$ django-admin startapp brands</code></pre>

<p><i>brands/models.py</i></p>

<pre><code class="hljs python">from django.db import models

class Brand(models.Model):
    name = models.CharField(max_length=128)
</code></pre>

<p>then we replace also <code>to</code> parameter value in <code>Phone</code> model with <code>'brands.Brand'</code> lazy reference.</p>
<p><i>phones/models.py</i></p>

<pre><code class="hljs python">from django.db import models

class Phone(models.Model):
    model = models.CharField(max_length=128)
    brand = models.ForeignKey('brands.Brand', on_delete=models.CASCADE)
</code></pre>

<p>registering <code>brands</code> app:</p>

<p><i>mobilestore/settings.py</i></p>

<pre><code class="hljs python">INSTALLED_APPS = [
    ...
    'phones',
    'brands',  # list the brands app
]
</code></pre>

<p>making migrations:</p>

<pre><code>mobilestore/$ python manage.py makemigrations
Migrations for 'brands':
  brands/migrations/0001_initial.py
    - Create model Brand
Migrations for 'phones':
  phones/migrations/0002_auto_20200418_1348.py
    - Alter field brand on phone
    - Delete model Brand <<< will delete the table
</code></pre>

<p>Now we have two new migration files - one in the <code>brands</code> app, the other in <code>phones</code> app. The new migration in the <code>phones</code> app depends on the one in the <code>brands</code> app, so let's check the migration file in the <code>brands</code> app first:</p>

<p><i>brands/migrations/0001_initial.py</i></p>
<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models

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

    operations = [
        migrations.CreateModel(
            name='Brand',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=128)),
            ],
        ),
    ]
</code></pre>

<p>corresponding SQL:</p>

<pre><code class="hljs sql">mobilestore/$ python manage.py sqlmigrate brands 0001
BEGIN;
--
-- Create model Brand
--
CREATE TABLE "brands_brand" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(128) NOT NULL);
COMMIT;
</code></pre>

<p>As you can see, Django is proposing to create a new table for the already moved <code>Brand</code> model instead of renaming the name of the table for it. </p>

<p>Now let's take a look at the other migration file:</p>

<p><i>phones/migrations/0002_auto_20200418_1348.py</i></p>
<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    dependencies = [
        ('brands', '0001_initial'),
        ('phones', '0001_initial'),
    ]

    operations = [
        migrations.AlterField(
            model_name='phone',
            name='brand',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='brands.Brand'),
        ),
        migrations.DeleteModel(
            name='Brand',
        ),
    ]

</code></pre>

<p>corresponding SQL:</p>

<pre><code class="hljs sql">mobilestore/$ python manage.py sqlmigrate phones 0002
BEGIN;
--
-- Alter field brand on phone
--
CREATE TABLE "new__phones_phone" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "brand_id" integer NOT NULL REFERENCES "brands_brand" ("id") DEFERRABLE INITIALLY DEFERRED, "model" varchar(128) NOT NULL);
INSERT INTO "new__phones_phone" ("id", "model", "brand_id") SELECT "id", "model", "brand_id" FROM "phones_phone";
DROP TABLE "phones_phone";
ALTER TABLE "new__phones_phone" RENAME TO "phones_phone";
CREATE INDEX "phones_phone_brand_id_b4e25eb0" ON "phones_phone" ("brand_id");
--
-- Delete model Brand
--
DROP TABLE "phones_brand";
COMMIT;
</code></pre>

<p>If you look at the first part of the SQL output, you'll find that Django is proposing to <code>CREATE</code> a new temporary <code>new__phones_phone</code> table with a foreign key to the <code>brands__brand</code> table, then <code>INSERT</code> data from existing <code>phones_phone</code> to the <code>new__phones_phone</code> table, after <code>DROP</code> <code>phones_phone</code> table and finally, <code>RENAME</code> the name of the table <code>new__phones_phone</code> to <code>phones_phone</code>(as it was before).</p>

<p>In the second part of the SQL we see <code>DROP</code> query for <code>phones_brand</code> table. </p>

<p>Well that's not what we want. How we can solve this problem? See in the next two sections. 👇</p>

<h4 id="section-solution">Solution #1</h4>

<p>
To solve this problem, we are going to modify the last 2 generated migration files that we saw above.
We will use <a href="https://docs.djangoproject.com/en/3.0/ref/migration-operations/#django.db.migrations.operations.SeparateDatabaseAndState" target="_blank" rel="noopener">SeparateDatabaseAndState</a> class to add operations that will reflect our changes to the model state, so we can avoid breaking Django's auto-detection system.
</p>

<p>Let's start from <code>brands</code> app:</p>

<p><i>brands/migrations/0001_initial.py</i></p>

<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models

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

    # renamed operations
    state_operations = [
        migrations.CreateModel(
            name='Brand',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=128)),
            ],
        ),
    ]
    # defined new `operations` list
    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations),
    ]
</code></pre>

<p>All we did is renamed existing <code>operations</code> list variable to <code>state_operations</code> and defined a new <code>operations</code> list variable with the <code>SeparateDatabaseAndState</code> class object in it.</p>

<p>After this change, Django's auto detector won't be confused and it won't try to create a new table cause we don't have any database operation. Let's prove that:</p>

<pre><code>mobilestore/$ python manage.py sqlmigrate brands 0001
BEGIN;
--
-- Custom state/database change combination
--
COMMIT;
</code></pre>

<p>Now, we are ready with the <code>brands</code> app, but things a bit complicated in the <code>phones</code> app.</p>

<p>
With the help of <a href="https://docs.djangoproject.com/en/3.0/ref/migration-operations/#altermodeltable" target="_blank" rel="noopener">AlterModelTable</a> class we will rename the table name for model <code>Brand</code>, cause we moved it to a new app. We will define it in a new <code>database_operations</code> list variable.
</p>

<p><i>phones/migrations/0002_auto_20200418_1348.py</i></p>

<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('brands', '0001_initial'),
        ('phones', '0001_initial'),
    ]

    database_operations = [
        migrations.AlterModelTable(
            name='Brand', 
            table='brands_brand',
        ),
    ]

    operations = [
        migrations.AlterField(
            model_name='phone',
            name='brand',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='brands.Brand'),
        ),
        migrations.DeleteModel(
            name='Brand',
        ),
    ]
</code></pre>

<p>next change will be moving auto generated <code>AlterField</code> operation from <code>operations</code> to the <code>database_operations</code> list variable:</p>

<p><i>phones/migrations/0002_auto_20200418_1348.py</i></p>

<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('brands', '0001_initial'),
        ('phones', '0001_initial'),
    ]

   database_operations = [
        migrations.AlterModelTable(
            name='Brand',
            table='brands_brand',
        ),
        migrations.AlterField(
            model_name='phone',
            name='brand',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='brands.Brand'),
        ),
    ]

    operations = [
        migrations.DeleteModel(
            name='Brand',
        ),
    ]
</code></pre>

<p>The last thing that is left to do is to tell Django(state) that we are going to "delete" the <code>Brand</code> model that was in the <code>phones</code> app. We will just rename existing <code>operations</code> variable to <code>state_operations</code> and will define a final <code>operations</code> variable combining database and state operations from the Migration class:</p>

<p><i>phones/migrations/0002_auto_20200418_1348.py</i></p>

<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models

class Migration(migrations.Migration):
    atomic = False. # SQLite only
    dependencies = [
        ('brands', '0001_initial'),
        ('phones', '0001_initial'),
    ]

   database_operations = [
        migrations.AlterModelTable(
            name='Brand',
            table='brands_brand',
        ),
        migrations.AlterField(
            model_name='phone',
            name='brand',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='brands.Brand'),
        ),
    ]

    state_operations = [
        migrations.DeleteModel(
            name='Brand',
        ),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations,
        ),
    ]
</code></pre>

<p>What it will do in terms of SQL:</p>

<pre><code>mobilestore/$ python manage.py sqlmigrate phones 0002
ALTER TABLE "phones_brand" RENAME TO "brands_brand";
CREATE TABLE "new__phones_phone" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "model" varchar(128) NOT NULL, "brand_id" integer NOT NULL REFERENCES "brands_brand" ("id") DEFERRABLE INITIALLY DEFERRED);
INSERT INTO "new__phones_phone" ("id", "model", "brand_id") SELECT "id", "model", "brand_id" FROM "phones_phone";
DROP TABLE "phones_phone";
ALTER TABLE "new__phones_phone" RENAME TO "phones_phone";
CREATE INDEX "phones_phone_brand_id_b4e25eb0" ON "phones_phone" ("brand_id");
</code></pre>

<p><strong>IMPORTANT</strong>! Everything is <strong>almost</strong> as we need. But the problem with this solution is that we will have some downtime during data transfer. As we saw, the SQL queries for creating a temporary table for <code>Phone</code> and moving data there will take some time, and this may cause some problems. To see how to avoid them let's just into the second solution.</p>

<h4 id="section-solution2">Solution #2</h4>

<p>
Instead of renaming the name of the table for <code>Brand</code> model we will specify <a href="https://docs.djangoproject.com/en/3.0/ref/models/options/#db-table" target="_blank" rel="noopener">db_table</a> model Meta option for it:
</p>

<p><i>brands/models.py</i></p>
<pre><code class="hljs python">from django.db import models

class Brand(models.Model):
    name = models.CharField(max_length=128)
    class Meta:
        db_table = 'phones_brand'
</code></pre>

<p>By this change we are telling Django to continue using <code>phones_brand</code> as a table for <code>Brand</code> model.</p>

<p>Now all changes in auto-generated migrations must be defined as <strong>state_operations</strong>. After all changes, migrations files should look like below:</p>

<p><i>brands/migrations/0001_initial.py</i></p>

<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models

class Migration(migrations.Migration):
    initial = True
    dependencies = [
    ]
    # renamed operations
    state_operations = [
        migrations.CreateModel(
            name='Brand',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=128)),
            ],
            # some meta data
            options={
                'db_table': 'phones_brand',
            },
        ),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations),
    ]
</code></pre>

<p><i>phones/migrations/0002_auto_20200418_1348.py</i></p>
<pre><code class="hljs python"># Generated by Django 3.0.5 once upon a time :)
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    dependencies = [
        ('brands', '0001_initial'),
        ('phones', '0001_initial'),
    ]
    # renamed operations
    state_operations = [
        migrations.AlterField(
            model_name='phone',
            name='brand',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='brands.Brand'),
        ),
        migrations.DeleteModel(
            name='Brand',
        ),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations),
    ]
</code></pre>

<p>Now we have no database operations and everything is as we want. ORM is also happy with that. We can apply the migrations without any doubts.</p>

<h4 id="section-conclustion">Conclusion</h4>

<p>
As we have noticed, the way Django generates a name for tables is not perfect. Specifying <code>db_table</code>s beforehand will be the best options in this case:
</p>

<p><i>phones/models.py</i></p>

<pre><code class="hljs python">from django.db import models

class Brand(models.Model):
    name = models.CharField(max_length=128)

    class Meta:
        # name is independent of app_label
        db_table = 'brands'  

class Phone(models.Model):
    model = models.CharField(max_length=128)
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE)

    class Meta:
        # name is independent of app_label
        db_table = 'phones'
</code></pre>
