summaryrefslogtreecommitdiff
path: root/docs/howto
diff options
context:
space:
mode:
authorAdam Johnson <me@adamj.eu>2020-03-03 17:51:39 +0000
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-03-10 11:07:36 +0100
commita9ee6872bd9e1bacc2da827dbd5b9093f724e4a5 (patch)
treefac8f1ada4463752f305612d977c23390528a778 /docs/howto
parenta2f554249ec07d4643643773a995579f98564ac1 (diff)
Clarified SeparateDatabaseAndState docs and added example of changing ManyToManyField.
Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com> Co-Authored-By: Carlton Gibson <carlton.gibson@noumenal.es> Co-Authored-By: René Fleschenberg <rene@fleschenberg.net>
Diffstat (limited to 'docs/howto')
-rw-r--r--docs/howto/writing-migrations.txt86
1 files changed, 86 insertions, 0 deletions
diff --git a/docs/howto/writing-migrations.txt b/docs/howto/writing-migrations.txt
index 084513b312..ab1a897aa0 100644
--- a/docs/howto/writing-migrations.txt
+++ b/docs/howto/writing-migrations.txt
@@ -318,6 +318,92 @@ could either do nothing (as in the example above) or remove some or all of the
data from the new application. Adjust the second argument of the
:mod:`~django.db.migrations.operations.RunPython` operation accordingly.
+.. _changing-a-manytomanyfield-to-use-a-through-model:
+
+Changing a ``ManyToManyField`` to use a ``through`` model
+=========================================================
+
+If you change a :class:`~django.db.models.ManyToManyField` to use a ``through``
+model, the default migration will delete the existing table and create a new
+one, losing the existing relations. To avoid this, you can use
+:class:`.SeparateDatabaseAndState` to rename the existing table to the new
+table name whilst telling the migration autodetector that the new model has
+been created. You can check the existing table name through
+:djadmin:`sqlmigrate` or :djadmin:`dbshell`. You can check the new table name
+with the through model's ``_meta.db_table`` property. Your new ``through``
+model should use the same names for the ``ForeignKey``\s as Django did. Also if
+it needs any extra fields, they should be added in operations after
+:class:`.SeparateDatabaseAndState`.
+
+For example, if we had a ``Book`` model with a ``ManyToManyField`` linking to
+``Author``, we could add a through model ``AuthorBook`` with a new field
+``is_primary``, like so::
+
+ from django.db import migrations, models
+ import django.db.models.deletion
+
+
+ class Migration(migrations.Migration):
+ dependencies = [
+ ('core', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.SeparateDatabaseAndState(
+ database_operations=[
+ # Old table name from checking with sqlmigrate, new table
+ # name from AuthorBook._meta.db_table.
+ 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',
+ fields=[
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'author',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ to='core.Author',
+ ),
+ ),
+ (
+ 'book',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ to='core.Book',
+ ),
+ ),
+ ],
+ ),
+ migrations.AlterField(
+ model_name='book',
+ name='authors',
+ field=models.ManyToManyField(
+ to='core.Author',
+ through='core.AuthorBook',
+ ),
+ ),
+ ],
+ ),
+ migrations.AddField(
+ model_name='authorbook',
+ name='is_primary',
+ field=models.BooleanField(default=False),
+ ),
+ ]
+
Changing an unmanaged model to managed
======================================