Writing Custom Migrations in Django
Tags: django
Published: 2022-04-03 11:25:01


Migrations in Django are auto-generated files from the models of an application that apply changes to a stricture of a Table whether it's the creation, the modification, or the deletion of a field. 

There are some situations when you are adding a new field or doing some modifications that we'll require you to write custom migrations.🤔

Let's see. 

## Problem

We have an e-commerce application selling shoes and computers. (Weird example but that will do the work😂)

In the database, we have the two following tables.

```python
from django.db import models


class Shoe(models.Model):
    name = models.CharField(max_length=100)
    brand = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    price = models.IntegerField()
    in_stock = models.BooleanField(default=True)
    description = models.TextField()
    image = models.ImageField(upload_to='images/')

    def __str__(self):
        return self.name
    
class Computer(models.Model):
    name = models.CharField(max_length=100)
    brand = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    price = models.IntegerField()
    in_stock = models.BooleanField(default=True)
    description = models.TextField()
    image = models.ImageField(upload_to='images/')

    def __str__(self):
        return self.name
```

We have the `Computer` table and the `Shoe` table with basically the same fields. A way to improve this and avoid DRY will be to create an `AbstractModel` class but well in the database that won't count that much.

Let's create a class called `Product` that will have the same fields and a `product_type` field to know the type of product, `computer` or `shoe`.

```python
class Product(models.Model):
    
    PRODUCT_TYPE_CHOICES = (
        ('shoe', 'shoe'),
        ('computer', 'shoe'),
    )
    
    name = models.CharField(max_length=100)
    brand = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    price = models.IntegerField()
    in_stock = models.BooleanField(default=True)
    description = models.TextField()
    image = models.ImageField(upload_to='images/')
    product_type = models.CharField(max_length=100, choices=PRODUCT_TYPE_CHOICES)

    def __str__(self):
        return self.name
```

The Product class is created. Now what? You will probably run the `python manage.py makemigrations` and the `python manage.py migrate` command to apply the changes in the database. 
The `Product` table will be created but we also want to have the `Shoe` and the `Computer` table data in this new table so we can get rid of them. 
This will require writing custom migrations. 

Supposing that we have created a separated Django application called `product` in the Django project, we can use the following command to generate a new empty migrations file in the `product` application.

```shell
python manage.py makemigrations product --empty 
```
We'll have a file similar to this. 

```python
# Generated by Django 4.0.2 on 2022-04-03 10:36

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('product', '0001_initial'),
    ]

    operations = [
    ]
```
Great! We need now to import the `Shoe` and `Computer` models and write a function to migrate `Shoe` and `Computer` table data to `Product` table.

```python
def migrate_to_product_model(apps, schema_editor):
    Product = apps.get_model('product', 'Product')
    Shoes = apps.get_model('shoe', 'Shoes')
    Computer = apps.get_model('computer', 'Computer')
    
    for shoe in Shoes.objects.all():
        Product.objects.create(
            name=shoe.name,
            brand=shoe.brand,
            color=shoe.color,
            in_stock=shoe.in_stock,
            description=shoe.description,
            image=shoe.image,
            product_type="shoe"
        )
    
    for computer in Computer.objects.all():
        Product.objects.create(
            name=computer.name,
            brand=computer.brand,
            color=computer.color,
            in_stock=computer.in_stock,
            description=computer.description,
            image=computer.image,
            product_type="computer"
        )
```
And here's the final migration code.

```python
# Generated by Django 4.0.2 on 2022-04-03 10:36

from django.db import migrations

def migrate_to_product_model(apps, schema_editor):
    Product = apps.get_model('product', 'Product')
    Shoes = apps.get_model('shoe', 'Shoes')
    Computer = apps.get_model('computer', 'Computer')
    
    for shoe in Shoes.objects.all():
        Product.objects.create(
            name=shoe.name,
            brand=shoe.brand,
            color=shoe.color,
            in_stock=shoe.in_stock,
            description=shoe.description,
            image=shoe.image,
            product_type="shoe"
        )
    
    for computer in Computer.objects.all():
        Product.objects.create(
            name=computer.name,
            brand=computer.brand,
            color=computer.color,
            in_stock=computer.in_stock,
            description=computer.description,
            image=computer.image,
            product_type="computer"
        )


class Migration(migrations.Migration):

    dependencies = [
        ('product', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(migrate_to_product_model)
    ]
```

This is an example of a custom migration. No need to worry about if the migrations fail and the database is messed up. Migrations transactions are atomic by default, then if something goes south, the changes won't be committed to the database.
🚀

For more reading, check the [official documentation of migrations](https://docs.djangoproject.com/fr/4.0/topics/migrations/#more-advanced-migrations) in Django.

*Article posted using [bloggu.io](https://bloggu.io). Try it for free.*