Skip to main content

Django: Creating and Applying Migrations

Django: Creating and Applying Migrations

Migrations in Django are a powerful mechanism for managing database schema changes, allowing developers to evolve models while preserving data integrity. Integrated with Django’s Object-Relational Mapping (ORM), migrations translate Python model definitions into database schema updates. This tutorial explores creating and applying migrations in Django, covering migration generation, application, customization, and practical applications for maintaining robust web applications.


01. Why Use Migrations?

Migrations enable seamless database schema evolution as application requirements change, such as adding fields, modifying relationships, or creating new models. They ensure consistency across development, testing, and production environments, support version control, and prevent manual SQL errors. Migrations are critical for applications like e-commerce platforms, content management systems, or any project requiring iterative development.

Example: Basic Migration Creation

# myapp/models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)

    def __str__(self):
        return self.name
# Generate migration
python manage.py makemigrations

Output:

Migrations for 'myapp':
  myapp/migrations/0001_initial.py
    - Create model Product
# Apply migration
python manage.py migrate

Output:

Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0001_initial... OK

Explanation:

  • makemigrations - Generates migration files based on model changes.
  • migrate - Applies migrations to update the database schema.

02. Key Migration Concepts and Commands

Django’s migration system provides a suite of commands and tools to manage schema changes efficiently. The table below summarizes key concepts and their applications:

Concept/Command Description Use Case
makemigrations Generate migration files from model changes Create new schema updates
migrate Apply or unapply migrations Update database schema
showmigrations List migration status Verify applied migrations
sqlmigrate Display SQL for a migration Debug or review schema changes


2.1 Creating Migrations

Example: Adding a New Field

# myapp/models.py (Updated)
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)  # New field

    def __str__(self):
        return self.name
# Generate migration
python manage.py makemigrations

Output:

Migrations for 'myapp':
  myapp/migrations/0002_product_stock.py
    - Add field stock to product
# Apply migration
python manage.py migrate

Output:

Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0002_product_stock... OK

Explanation:

  • default=0 - Provides a default value for existing records.
  • Django detects the new field and generates a migration to add it.

2.2 Applying Migrations

Example: Applying Specific Migration

# View migration status
python manage.py showmigrations

Output:

myapp
 [X] 0001_initial
 [X] 0002_product_stock
# Apply a specific migration
python manage.py migrate myapp 0001_initial

Output:

Operations to perform:
  Target specific migrations: myapp 0001_initial
Running migrations:
  Rendering model states... DONE
  Unapplying myapp.0002_product_stock... OK

Explanation:

  • showmigrations - Shows which migrations are applied (marked with [X]).
  • migrate myapp 0001_initial - Reverts to a specific migration, unapplying later ones.

2.3 Data Migrations

Example: Populating a New Field

# myapp/models.py (Updated)
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)
    category = models.CharField(max_length=50, blank=True)  # New field

    def __str__(self):
        return self.name
# Generate empty migration for data
python manage.py makemigrations --empty myapp

Output:

Migrations for 'myapp':
  myapp/migrations/0003_auto_20250514_2203.py
# myapp/migrations/0003_auto_20250514_2203.py (Edited)
from django.db import migrations

def populate_category(apps, schema_editor):
    Product = apps.get_model('myapp', 'Product')
    for product in Product.objects.all():
        product.category = 'General'  # Default category
        product.save()

class Migration(migrations.Migration):
    dependencies = [
        ('myapp', '0002_product_stock'),
    ]
    operations = [
        migrations.AddField(
            model_name='Product',
            name='category',
            field=models.CharField(max_length=50, blank=True),
        ),
        migrations.RunPython(populate_category),
    ]
# Apply migration
python manage.py migrate

Output:

Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0003_auto_20250514_2203... OK

Explanation:

  • --empty - Creates a blank migration for custom operations.
  • RunPython - Executes a function to populate the new category field.

2.4 Inspecting Migration SQL

Example: Viewing SQL for a Migration

# View SQL for a migration
python manage.py sqlmigrate myapp 0002_product_stock

Output:

BEGIN;
--
-- Add field stock to product
--
ALTER TABLE "myapp_product" ADD COLUMN "stock" integer NOT NULL DEFAULT 0;
COMMIT;

Explanation:

  • sqlmigrate - Displays the SQL commands Django will execute.
  • Useful for debugging or understanding schema changes.

2.5 Incorrect Migration Usage

Example: Missing Default for Non-Nullable Field

# myapp/models.py (Incorrect)
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    stock = models.PositiveIntegerField()  # No default, non-nullable
python manage.py makemigrations

Output:

You are trying to add a non-nullable field 'stock' to product without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now
 2) Quit, and add a default in models.py

Explanation:

  • Non-nullable fields require a default for existing records.
  • Solution: Add default=0 or allow null=True temporarily.

03. Effective Usage

3.1 Recommended Practices

  • Commit migration files to version control for team collaboration.

Example: Comprehensive Migration Workflow

# myapp/models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)

    def __str__(self):
        return self.name
# Generate migrations
python manage.py makemigrations

Output:

Migrations for 'myapp':
  myapp/migrations/0004_category_product_category.py
    - Create model Category
    - Add field category to product
# myapp/migrations/0004_category_product_category.py (Auto-generated)
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    dependencies = [
        ('myapp', '0003_auto_20250514_2203'),
    ]
    operations = [
        migrations.CreateModel(
            name='Category',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=50)),
            ],
        ),
        migrations.AddField(
            model_name='Product',
            name='category',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='myapp.Category'),
        ),
    ]
# Apply migrations
python manage.py migrate

Output:

Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0004_category_product_category... OK
  • Version control migration files to track schema changes.
  • Use null=True initially for new relationships to avoid data migration issues.

3.2 Practices to Avoid

  • Avoid editing applied migrations in production without careful planning.

Example: Modifying Applied Migration

# myapp/migrations/0001_initial.py (Incorrectly edited after applying)
from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='Product',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=100)),
                ('price', models.DecimalField(decimal_places=2, max_digits=8)),
                ('new_field', models.CharField(max_length=50)),  # Added after applying
            ],
        ),
    ]
python manage.py migrate

Output:

No migrations to apply.
  • Editing applied migrations causes inconsistencies, as Django tracks applied migrations in the database.
  • Solution: Create a new migration with makemigrations for schema changes.

04. Common Use Cases

4.1 E-Commerce Schema Evolution

Add a category model and link it to products.

Example: Adding a Relationship

# myapp/models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return self.name
python manage.py makemigrations

Output:

Migrations for 'myapp':
  myapp/migrations/0001_initial.py
    - Create model Category
    - Create model Product
python manage.py migrate

Output:

Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0001_initial... OK

Explanation:

  • null=True - Allows existing products to have no category initially.
  • Migration creates the new model and relationship.

4.2 Data Migration for Legacy Data

Populate a new field with data based on existing records.

Example: Data Migration for New Field

# myapp/models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    is_expensive = models.BooleanField(default=False)  # New field

    def __str__(self):
        return self.name
python manage.py makemigrations

Output:

Migrations for 'myapp':
  myapp/migrations/0002_product_is_expensive.py
    - Add field is_expensive to product
# myapp/migrations/0002_product_is_expensive.py (Edited)
from django.db import migrations, models

def set_expensive_flag(apps, schema_editor):
    Product = apps.get_model('myapp', 'Product')
    for product in Product.objects.all():
        product.is_expensive = product.price > 500
        product.save()

class Migration(migrations.Migration):
    dependencies = [
        ('myapp', '0001_initial'),
    ]
    operations = [
        migrations.AddField(
            model_name='Product',
            name='is_expensive',
            field=models.BooleanField(default=False),
        ),
        migrations.RunPython(set_expensive_flag),
    ]
python manage.py migrate

Output:

Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0002_product_is_expensive... OK

Explanation:

  • RunPython - Sets is_expensive based on price.
  • Migration ensures existing records are updated correctly.

Conclusion

Django’s migration system provides a robust framework for managing database schema and data changes in web applications. Key takeaways:

  • Use makemigrations and migrate to generate and apply schema changes.
  • Handle data migrations with RunPython for existing records.
  • Inspect migrations with showmigrations and sqlmigrate for debugging.
  • Avoid manual edits to applied migrations and ensure defaults for non-nullable fields.

With Django migrations, you can evolve your database schema confidently, ensuring consistency and scalability for dynamic web applications!

Comments