python / expert
Snippet
Django Database Migrations with Custom Operations
Django migrations support custom operations for complex data transformations that go beyond simple field changes. This pattern is essential when you need to migrate data between tables, perform batch processing during migrations, or implement zero-downtime migration strategies. The MoveDataToNewTable operation demonstrates how to iterate through large datasets in batches, perform custom logic on each record, and maintain database consistency with atomic transactions.
snippet.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from django.db import migrationsclass MoveDataToNewTable(migrations.Operation):atomic = Truedef __init__(self, model_name, field_name, batch_size=1000):self.model_name = model_nameself.field_name = field_nameself.batch_size = batch_sizedef state_forwards(self, app_label, state):passdef database_forwards(self, app_label, schema_editor, from_state, to_state):model = state.get_model(app_label, self.model_name)field = model._meta.get_field(self.field_name)queryset = model.objects.filter(**{f'{self.field_name}__isnull': False})total = queryset.count()for offset in range(0, total, self.batch_size):batch = list(queryset[offset:offset + self.batch_size])for obj in batch:field.save(obj, obj)schema_editor.execute('SELECT 1')def describe(self):return f'Migrating {self.field_name} data in {self.model_name}'class Migration(migrations.Migration):dependencies = [('myapp', '0001_initial')]operations = [migrations.AddField(model_name='product',name='cached_slug',field=models.SlugField(max_length=150, null=True),),MoveDataToNewTable('product', 'cached_slug'),migrations.AlterField(model_name='product',name='cached_slug',field=models.SlugField(max_length=150),),]
django
Breakdown
1
class MoveDataToNewTable(migrations.Operation):
Custom migration operation class inheriting from migrations.Operation
2
atomic = True
Class attribute ensuring operation runs in a transaction
3
def database_forwards(self, app_label, schema_editor, from_state, to_state):
Main method called during migration execution
4
queryset.filter(**{f'{self.field_name}__isnull': False})
Dynamic field filtering using dictionary unpacking
5
for offset in range(0, total, self.batch_size):
Batch iteration pattern to process large datasets memory-efficiently
6
schema_editor.execute('SELECT 1')
Dummy query to trigger batch commit in PostgreSQL/MySQL