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.