In working with and learning about the Django framework, I have found that many people, including myself, add migrations files to.gitignore when versioning Django projects.

I’ve been wondering if this is the right approach, so I went to the official documents and found the following.

The original:

You should think of migrations as a version control system for your database schema. makemigrations is responsible for packaging up your model changes into individual migration files – analogous to commits – and migrate is responsible for applying those to your database.

The migration files for each app live in a “migrations” directory inside of that app, and are designed to be committed to, and distributed as part of, its codebase. You should be making them once on your development machine and then running the same migrations on your colleagues’ machines, your staging machines, and eventually your production machines.

英 文翻译 :

You can imagine migrations being like a version control system for your database. Makemigrations Saves changes to your model to a migration file – similar to descriptions – and Migration commits changes to the database.

The migrations file for each app is saved in the migrations folder for each corresponding app and is prepared for how to execute it as a distributed code base. You should create these files again every time you run the same migration on your development machine or on your colleague’s machine and eventually on your production machine.

According to official documentation, it was an error not to commit migrations to the warehouse.

And migrations must be preserved if unit tests are to be conducted using the packaged TestCases that come with Django.

In the next article, I will also cover the use of TestCase in Django.

The following is a brief introduction of migrations experience and problems encountered in the project.

Initialize data with migrations

We now have a Book model and I want to initialize some data after Migrate.

This Github Sample is by
elfgzp
make_good_use_of_migrations/models.py
view
raw

class Book(models.Model):
    name = models.CharField(max_length=32)Copy the code

Example: Generate three books named Hamlet, Tempest, and The Little Prince.

Py Makemigrations Migrations folder generates a file 0001_initial.py after python manage.py Makemigrations migrations.

The file contains some code for initializing the Book model.

In the context of how to initialize data with Migrations, we first introduce two common migrations operations:

RunSQL, RunPython

Execute SQL statements and Python functions, respectively, as the name implies.

Next I use RunPython from Migrations to initialize the data.

  1. Py migrations/.├ ── 0001_initial.py. ├── 0002_init_book_data.py.

  2. Then add Migration class inherits the django. Db. Migrations, Migration, and increase the need to perform in the operations of the code.

This Github Sample is by
elfgzp
make_good_use_of_migrations/migrations/0002_init_book_data.py
view
raw

from django.db import migrations

"""Make_good_use_of_migrations is the name of the App."""


def init_book_data(apps, schema_editor):
    Book = apps.get_model('make_good_use_of_migrations'.'Book')
    init_data = ['Hamlet'.'Tempest'.'The Little Prince']
    for name in init_data:
        book = Book(name=name)
        book.save()


class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations'.'0001_initial'),]Note that dependencies is the name of the previous migrations file

    operations = [
        migrations.RunPython(init_book_data)
    ]Copy the code

  1. runpython manage.py migrate, you can see that the data has been generated in the database.

Use migrations to repair data

This is often the case. For example, I need to add a foreign key field to the Book model, and this field cannot be empty, so the old data needs to be processed and fixed, so we can do this.

  1. Start by adding the fields that need to be addednullProperty set toTrueAnd then executemakemigrations
This Github Sample is by
elfgzp
make_good_use_of_migrations/models.py
view
raw

class Author(models.Model):
    name = models.CharField(max_length=32)


class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)Copy the code

  1. In the corresponding appmigrationsThe new file0004_fix_book_data.py migrations/.

    ├ ─ ─ 0001 _initial. Py.

    ├ ─ ─ 0002 _init_book_data. Py.

    ├ ─ ─ 0003 _auto_20181204_0533. Py.

    └ ─ ─ 0004 _fix_book_data. Py.
This Github Sample is by
elfgzp
make_good_use_of_migrations/migrations/0004_fix_book_data.py
view
raw

from django.db import migrations


def fix_book_data(apps, schema_editor):
    Book = apps.get_model('make_good_use_of_migrations'.'Book')
    Author = apps.get_model('make_good_use_of_migrations'.'Author')
    for book in Book.objects.all():
        author, _ = Author.objects.get_or_create(name='%s author' % book.name)
        book.author = author
        book.save()


class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations'.'0003_auto_20181204_0533'),
    ]

    operations = [
        migrations.RunPython(fix_book_data)
    ]Copy the code

  1. Finally, set Author attribute NULL in Book to False, and Hoisting. After execution, it will appear,

    You are trying to change the nullable field 'author' on book to non-nullable 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 (will be set on all existing rows with a null value for this column) 2) Ignore for now,  and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL  values in a previous data migration) 3) Quit, and let me add a default in models.py Select an option:Copy the code

    Select item 2 here, which means to ignore the null data and use RunPython or RunSQL to handle it yourself.

    After the selection, execute Python manage.py Migrate and see that the data in the database will be processed as expected.

Resolve conflicts with migrations during multiplayer development

To simulate multi-person multi-branch development, create a new branch of Master-2 that precedes the creation of the Author class, and add remark fields to the Book model.

The contents of the model.py file are as follows:

This Github Sample is by
elfgzp
make_good_use_of_migrations/models.py
view
raw

class Book(models.Model):
    name = models.CharField(max_length=32)
    remark = models.CharField(max_length=32, null=True)Copy the code

Migrations files are stored in the following directories:

├─ 0002_init_book_data.py. ├─ 0002_book_data.py.

When we merge the master-2 code into the master, we see that there is a duplicate number 0003 in migrations and they all depend on 0002_init_book_data.

├── 0001_initial.py. ├── 0002_init_book_data.py. ├── 0003_auto_20181204_0533 0004 _fix_book_data. Py └ ─ ─ 0005 _auto_20181204_0610. Py.

This is where the command comes in:

python manage.py makemigrations --merge
Copy the code

A 0006_MERge_20181204_0622.py file is then generated in the migrations directory

This Github Sample is by
elfgzp
make_good_use_of_migrations/migrations/0006_merge_20181204_0622.py
view
raw

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('make_good_use_of_migrations'.'0005_auto_20181204_0610'),
        ('make_good_use_of_migrations'.'0003_book_remark'),
    ]

    operations = [
    ]Copy the code

Python manage.py Migrate is now available.

Problems with migrations.RunPython

You cannot call a model class function from a function

Suppose two functions, print_name and print_class_name, are defined in the Book model

This Github Sample is by
elfgzp
make_good_use_of_migrations/models.py
view
raw

class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)
    remark = models.CharField(max_length=32, null=True)

    def print_name(self):
        print(self.name)

    @classmethod
    def print_class_name(cls):
        print(cls.__name__)Copy the code

Migrations cannot be called, and the author did not examine it carefully. It is presumed that the Book class initializes only the fields.

This Github Sample is by
elfgzp
make_good_use_of_migrations/migrations/0004_fix_book_data.py
view
raw

from django.db import migrations


def fix_book_data(apps, schema_editor):
    Book = apps.get_model('make_good_use_of_migrations'.'Book')
    Author = apps.get_model('make_good_use_of_migrations'.'Author')
    for book in Book.objects.all():
        author, _ = Author.objects.get_or_create(name='%s author' % book.name)
        book.author = author
        """Book.print_name () book.print_class_name() this call will report an error"""
        book.save()


class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations'.'0003_auto_20181204_0533'),
    ]

    operations = [
        migrations.RunPython(fix_book_data)
    ]Copy the code

Invalid save methods overridden by model classes in functions, including inherited save methods

None of the save methods overridden in migrations run, for example:

This Github Sample is by
elfgzp
make_good_use_of_migrations/models.py
view
raw

class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)
    remark = models.CharField(max_length=32, null=True)

    def print_name(self):
        print(self.name)

    @classmethod
    def print_class_name(cls):
        print(cls.__name__)

    def save(self, *args, **kwargs):
        if not self.remark:
            self.remark = 'This is a book.'Copy the code

The value of the remark field of the last initialized data is still empty.

All signals registered by the model in the function are invalid

Although the Book model is registered with signal, it still does not work in migrations

This Github Sample is by
elfgzp
make_good_use_of_migrations/models.py
view
raw

@receiver(pre_save, sender=Book)
def generate_book_remark(sender, instance, *args, **kwargs):
    print(instance)
    if not instance.remark:
        instance.remark = 'This is a book.'Copy the code

Do not put data processing in migrations files for model changes

When doing data repair or generating initialization data, do not put handler functions in migrations files that automatically generate changes or generate fields or models, for example:

This Github Sample is by
elfgzp
make_good_use_of_migrations/migrations/0005_auto_20181204_0610.py
view
raw

class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations'.'0004_fix_book_data'),
    ]

    operations = [
        migrations.AlterField(
            model_name='book',
            name='author',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    to='make_good_use_of_migrations.Author'),),"""Migrations.runpython (XXX) Do not put data processing into model changes"""
    ]Copy the code

The main reason not to put them together is that when the processing logic of a function in RunPython fails to execute down once an exception occurs,

Django_migrations will not record this processing, but the table structure changes have already been executed!

This is also a problem with Django migrations. The correct solution is to do a database rollback when an exception occurs.

Once this happens, you have to manually write the names of migrations, such as 0005_AUTO_20181204_0610, into the database table django_migrations, and then separate the logic from RunPython.

conclusion

That’s what I learned about migrations using the Django framework in my project. The next article will cover TestCase with the Django framework.

The source code for this article is available on Github at github.com/elfgzp/djan…

My blog address:Elfgzp. Cn / 2018/12/04 /...