Slide 1

Slide 1 text

Don’t be afraid of writing migrations

Slide 2

Slide 2 text

I’m Markus Holtermann @m_holtermann • github.com/MarkusH • markusholtermann.eu • Traveller • Django Core Developer

Slide 3

Slide 3 text

Don’t be afraid of writing migrations

Slide 4

Slide 4 text

3 Recipes

Slide 5

Slide 5 text

General layout

Slide 6

Slide 6 text

from django.db import migrations class Migration(migrations.Migration):

Slide 7

Slide 7 text

from django.db import migrations class Migration(migrations.Migration): dependencies = [ ]

Slide 8

Slide 8 text

from django.db import migrations class Migration(migrations.Migration): dependencies = [ ] operations = [ ]

Slide 9

Slide 9 text

Recipe 1 optimizing makemigrations output

Slide 10

Slide 10 text

from django.db import models class Book(models.Model): title = models.CharField(max_length=200) library = models.ForeignKey('Library') class Library(models.Model): name = models.CharField(max_length=200)

Slide 11

Slide 11 text

$ python manage.py makemigrations Migrations for 'optimize_makemigrations': 0001_initial.py: - Create model Book - Create model Library - Add field library to book

Slide 12

Slide 12 text

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel(name='Book', fields=[ ('id', models.AutoField(...)), ('title', models.CharField(max_length=200))])

Slide 13

Slide 13 text

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel(name='Book', fields=[ ('id', models.AutoField(...)), ('title', models.CharField(max_length=200))]), migrations.CreateModel(name='Library', fields=[ ('id', models.AutoField(...)), ('name', models.CharField(max_length=200))])

Slide 14

Slide 14 text

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel(name='Book', fields=[ ('id', models.AutoField(...)), ('title', models.CharField(max_length=200))]), migrations.CreateModel(name='Library', fields=[ ('id', models.AutoField(...)), ('name', models.CharField(max_length=200))]), migrations.AddField(model_name='book', name='library', field=models.ForeignKey(to='app.Library')) ]

Slide 15

Slide 15 text

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [] operations = [ ]

Slide 16

Slide 16 text

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel(name='Library', fields=[ ('id', models.AutoField(...)), ('name', models.CharField(max_length=200)) ]), migrations.CreateModel(name='Book', fields=[ ('id', models.AutoField(...)), ('title', models.CharField(max_length=200)), ('library', models.ForeignKey(to='app.Library')) ]), ]

Slide 17

Slide 17 text

Recipe 2 adding a non-nullable column

Slide 18

Slide 18 text

from django.db import models class Author(models.Model): name = models.CharField(max_length=50) @classmethod def create(cls, name): return cls.objects.create(name=name)

Slide 19

Slide 19 text

$ python manage.py makemigrations Migrations for 'non_nullable_field': 0001_initial.py: - Create model Author $ python manage.py makemigrations non_nullable_field --empty --name initial_data Migrations for 'non_nullable_field': 0002_initial_data.py:

Slide 20

Slide 20 text

from django.db import migrations def forwards(apps, schema_editor): Author = apps.get_model('non_nullable_field', 'Author') Author.objects.create(name='Author 1') Author.objects.create(name='Author 2') def backwards(apps, schema_editor): Author = apps.get_model('non_nullable_field', 'Author') Author.objects.filter(name='Author 1').delete() Author.objects.filter(name='Author 2').delete() class Migration(migrations.Migration): dependencies = [('non_nullable_field', '0001_initial')] operations = [migrations.RunPython(forwards, backwards)]

Slide 21

Slide 21 text

$ python manage.py migrate non_nullable_field Operations to perform: Apply all migrations: non_nullable_field Running migrations: Rendering model states... DONE Applying non_nullable_field.0001_initial... OK Applying non_nullable_field.0002_initial_data... OK

Slide 22

Slide 22 text

from django.db import models class Author(models.Model): name = models.CharField(max_length=50) homepage = models.URLField(null=True) @classmethod def create(cls, name): return cls.objects.create(name=name)

Slide 23

Slide 23 text

$ python manage.py makemigrations Migrations for 'non_nullable_field': 0003_author_homepage.py: - Add field homepage to author $ python manage.py migrate non_nullable_field Operations to perform: Apply all migrations: non_nullable_field Running migrations: Rendering model states... DONE Applying non_nullable_field.0003_author_homepage... OK

Slide 24

Slide 24 text

from django.db import models from django.utils import html, safestring class Author(models.Model): name = models.CharField(max_length=50) homepage = models.URLField(null=True) @classmethod def create(cls, name, homepage): return cls.objects.create(name=name, homepage=homepage) @property def homepage_tag(self): if self.homepage: return html.format_html('{u}', u=self.homepage) return safestring.mark_safe('No homepage')

Slide 25

Slide 25 text

$ python manage.py makemigrations non_nullable_field --empty --name populate_data Migrations for 'non_nullable_field': 0004_populate_data.py:

Slide 26

Slide 26 text

from django.db import migrations LOOKUP_DATA = {'Author 1': 'http://example.com', 'Author 2': 'http://other.org'} def forwards(apps, schema_editor): Author = apps.get_model('non_nullable_field', 'Author') for author in Author.objects.filter(homepage__isnull=True): author.homepage = LOOKUP_DATA[author.name] author.save(update_fields=['homepage']) class Migration(migrations.Migration): dependencies = [('non_nullable_field', '0003_homepage')] operations = [ migrations.RunPython(forwards, migrations.RunPython.noop) ]

Slide 27

Slide 27 text

$ python manage.py migrate non_nullable_field Operations to perform: Apply all migrations: non_nullable_field Running migrations: Rendering model states... DONE Applying non_nullable_field.0004_populate_data... OK

Slide 28

Slide 28 text

from django.db import models from django.utils import html, safestring class Author(models.Model): name = models.CharField(max_length=50) homepage = models.URLField() @classmethod def create(cls, name, homepage): return cls.objects.create(name=name, homepage=homepage) @property def homepage_tag(self): if self.homepage: return html.format_html('{u}', u=self.homepage) return safestring.mark_safe('No homepage')

Slide 29

Slide 29 text

$ python manage.py makemigrations --name not_null_constraint You are trying to change the nullable field to non-nullable without a default ... Please select a fix: 1) Provide a one-off value ... 2) Ignore for now ... 3) Quit ... Select an option: 2 Migrations for 'non_nullable_field': 0005_not_null_constraint.py: - Alter field homepage on author

Slide 30

Slide 30 text

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [('non_nullable_field', '0004_populate')] operations = [ migrations.AlterField(model_name='author', name='homepage', field=models.URLField()) ]

Slide 31

Slide 31 text

$ python manage.py migrate non_nullable_field Operations to perform: Apply all migrations: non_nullable_field Running migrations: Rendering model states... DONE Applying non_nullable_field.0005_not_null_constraint... OK

Slide 32

Slide 32 text

from django.db import models from django.utils import html class Author(models.Model): name = models.CharField(max_length=50) homepage = models.URLField() @classmethod def create(cls, name, homepage): return cls.objects.create(name=name, homepage=homepage) @property def homepage_tag(self): return html.format_html('{u}', u=self.homepage)

Slide 33

Slide 33 text

Recipe 3 rename an app without dependencies

Slide 34

Slide 34 text

# rename_app/models.py from django.db import models class Author(models.Model): name = models.CharField(max_length=50) class Book(models.Model): title = models.CharField(max_length=50) author = models.ForeignKey('rename_app.Author')

Slide 35

Slide 35 text

$ python manage.py makemigrations $ python manage.py makemigrations rename_app --empty --name initial_data Edit rename_app/migrations/0002_initial_data.py $ python manage.py migrate rename_app

Slide 36

Slide 36 text

# rename_app/models.py from django.db import models class Author(models.Model): name = models.CharField(max_length=50) class Meta: db_table = 'rename_app_author' class Book(models.Model): title = models.CharField(max_length=50) author = models.ForeignKey('rename_app.Author') class Meta: db_table = 'rename_app_book'

Slide 37

Slide 37 text

$ python manage.py makemigrations --name pin_db_tables # rename_app/migrations/0003_pin_db_tables.py from django.db import migrations, models class Migration(migrations.Migration): dependencies = [('rename_app', '0002_initial_data')] operations = [ migrations.AlterModelTable(name='author', table='rename_app_author'), migrations.AlterModelTable(name='book', table='rename_app_book'), ] $ python manage.py migrate

Slide 38

Slide 38 text

$ python manage.py migrate rename_app zero --fake Operations to perform: Unapply all migrations: rename_app Running migrations: Rendering model states... DONE Unapplying rename_app.0003_pin_db_tables... FAKED Unapplying rename_app.0002_initial_data... FAKED Unapplying rename_app.0001_initial... FAKED

Slide 39

Slide 39 text

# in settings.py INSTALLED_APPS = [ # ... 'new_app_name.apps.NewAppNameConfig', ] # in new_app_name/models.py author = models.ForeignKey('new_app_name.Author') class Meta: db_table = 'rename_app_book' # Keep as is for now! # in new_app_name/migrations/0003_pin_db_tables.py and others dependencies = [('new_app_name', '0002_initial_data')]

Slide 40

Slide 40 text

$ python manage.py migrate new_app_name --fake Operations to perform: Apply all migrations: new_app_name Running migrations: Rendering model states... DONE Applying new_app_name.0001_initial... FAKED Applying new_app_name.0002_initial_data... FAKED Applying new_app_name.0003_pin_db_tables... FAKED

Slide 41

Slide 41 text

# new_app_name/models.py from django.db import models class Author(models.Model): name = models.CharField(max_length=50) class Book(models.Model): title = models.CharField(max_length=50) author = models.ForeignKey('new_app_name.Author')

Slide 42

Slide 42 text

$ python manage.py makemigrations --name rename_tables # new_app_name/migrations/0004_rename_tables.py from django.db import migrations, models class Migration(migrations.Migration): dependencies = [('new_app_name', '0003_pin_db_tables')] operations = [ migrations.AlterModelTable(name='author', table=None), migrations.AlterModelTable(name='book', table=None), ] $ python manage.py migrate

Slide 43

Slide 43 text

Thank you @m_holtermann • github.com/MarkusH • markusholtermann.eu