9

This is an extension to this question: Move models between Django (1.8) apps with required ForeignKey references

The Nostalg.io's answer worked perfectly. But I still can't get what is a "state operation" and "database operation" and what actually is going on when using SeparateDatabaseAndState.

Ava Lord
  • 93
  • 5

1 Answers1

14

A bit tough topic and not enough clear explanations out there, so here are my 50 cents.

It's all about state of your application models. What's a state? Imagine you have an app with a model MyModel and some migrations:

# app/migrations/
    # 001.py
        CreateModel(name='MyModel')
    # 002.py
        AddField(name='total', field=models.IntegerField)
    # 003.py
        RemoveField(name='total', field=models.IntegerField)

When you call manage.py makemigrations app, Django looks through all changes in app/migrations/001.py...003.py to get what's expected to be in your model - i.e. the state of the model. So, state is roughly a combined result of your migrations. If it differs from what you have in your MyModel class in app/models.py, makemigrations creates new migration with corresponding change. Like if MyModel class currently has total field, while in last migration it was removed, Django creates a migration with AddField() operation. Unlike what people often think, makemigrations does NOT look at actual database table.

Normal migration operation changes both state and database: CreateModel() tells Django "hey Django, this migration adds new table" and performs "CREATE TABLE" on the database.

SeparateDatabaseAndState is needed when you need to do different things to the state and to the database (or may be you need to modify just one of them).

Let's look at example from Django docs, where they change existing ManyToMany relation to "through" model. They had model Author and model Book with M-M relation:

class Book(models.Model):
    authors = models.ManyToManyField(Author)

But now you want to have a through-model AuthorBook - optionally with some extra fields in it:

class AuthorBook(models.Model):
    ...

class Book(models.Model):
    authors = models.ManyToManyField(Author, through=AuthorBook)

But you don't want new table - you want to use existing core_book_authors table which Django created automatically, and you want data in it. So you create a migration with SeparateDatabaseAndState operation with database_operations and state_operations.

operations = [
    migrations.SeparateDatabaseAndState(
        database_operations=[
            migrations.RunSQL(
                sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
                reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
            ),
        ],
        state_operations=[
            migrations.CreateModel(name='AuthorBook'),
        ]
    ),
]

In database_operations they rename existing M-M table according to new model name. That's the only thing you actually need to do with the database, unless you're adding new field to AuthorBook at the same time.

state_operations with CreateModel() tells Django: "this migration adds new table" (actually it does not - as we know from database_operations, it renames existing table). But because of this state_operations our new model gets into the state and Django knows this model is "created" and will not try to create it on next makemigrations call.

So, in SeparateDatabaseAndState operation, database_operations affect the database and not the state, state_operations affect the state and not the database.

Yarrow
  • 791
  • 9
  • 10