From e8e4f978dd4b7a3d0c689c6e3301e3c6f9e50003 Mon Sep 17 00:00:00 2001
From: Marten Kenbeek <marten.knbk@gmail.com>
Date: Sun, 5 Apr 2015 15:59:23 +0200
Subject: [PATCH] Fixed #24278 -- Fixed serialization of migration operations.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixed MigrationWriter.serialize() to correctly handle migration
operations by utilizing OperationWriter.

Thanks Piotr Maliński for the report.
---
 AUTHORS                         |  1 +
 django/db/migrations/writer.py  |  5 ++++
 docs/releases/1.8.1.txt         |  4 +++
 tests/migrations/test_writer.py | 44 +++++++++++++++++++++++++++++++++
 4 files changed, 54 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 268f5a24ae..c1e00923f6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -441,6 +441,7 @@ answer newbie questions, and generally made Django that much better:
     Mark Lavin <markdlavin@gmail.com>
     Mark Sandstrom <mark@deliciouslynerdy.com>
     Markus Holtermann <https://markusholtermann.eu>
+    Marten Kenbeek <marten.knbk+django@gmail.com>
     martin.glueck@gmail.com
     Martin Green
     Martin Kosír <martin@martinkosir.net>
diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py
index 83cea065ab..4102b79697 100644
--- a/django/db/migrations/writer.py
+++ b/django/db/migrations/writer.py
@@ -13,6 +13,7 @@ from importlib import import_module
 from django.apps import apps
 from django.db import migrations, models
 from django.db.migrations.loader import MigrationLoader
+from django.db.migrations.operations.base import Operation
 from django.utils import datetime_safe, six
 from django.utils._os import upath
 from django.utils.encoding import force_text
@@ -423,6 +424,10 @@ class MigrationWriter(object):
                 return "%s.as_manager()" % name, imports
             else:
                 return cls.serialize_deconstructed(manager_path, args, kwargs)
+        elif isinstance(value, Operation):
+            string, imports = OperationWriter(value, indentation=0).serialize()
+            # Nested operation, trailing comma is handled in upper OperationWriter._write()
+            return string.rstrip(','), imports
         # Anything that knows how to deconstruct itself.
         elif hasattr(value, 'deconstruct'):
             return cls.serialize_deconstructed(*value.deconstruct())
diff --git a/docs/releases/1.8.1.txt b/docs/releases/1.8.1.txt
index c848fc2c30..303a793be2 100644
--- a/docs/releases/1.8.1.txt
+++ b/docs/releases/1.8.1.txt
@@ -17,3 +17,7 @@ Bugfixes
 
 * Prevented ``TypeError`` in translation functions ``check_for_language()`` and
   ``get_language_bidi()`` when translations are deactivated (:ticket:`24569`).
+
+* Fixed :djadmin:`squashmigrations` command when using
+  :class:`~django.db.migrations.operations.SeparateDatabaseAndState`
+  (:ticket:`24278`).
diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py
index bbe81ad58d..46531df791 100644
--- a/tests/migrations/test_writer.py
+++ b/tests/migrations/test_writer.py
@@ -81,6 +81,27 @@ class OperationWriterTests(SimpleTestCase):
             '),'
         )
 
+    def test_nested_args_signature(self):
+        operation = custom_migration_operations.operations.ArgsOperation(
+            custom_migration_operations.operations.ArgsOperation(1, 2),
+            custom_migration_operations.operations.KwargsOperation(kwarg1=3, kwarg2=4)
+        )
+        buff, imports = OperationWriter(operation, indentation=0).serialize()
+        self.assertEqual(imports, {'import custom_migration_operations.operations'})
+        self.assertEqual(
+            buff,
+            'custom_migration_operations.operations.ArgsOperation(\n'
+            '    arg1=custom_migration_operations.operations.ArgsOperation(\n'
+            '        arg1=1,\n'
+            '        arg2=2,\n'
+            '    ),\n'
+            '    arg2=custom_migration_operations.operations.KwargsOperation(\n'
+            '        kwarg1=3,\n'
+            '        kwarg2=4,\n'
+            '    ),\n'
+            '),'
+        )
+
     def test_multiline_args_signature(self):
         operation = custom_migration_operations.operations.ArgsOperation("test\n    arg1", "test\narg2")
         buff, imports = OperationWriter(operation, indentation=0).serialize()
@@ -107,6 +128,29 @@ class OperationWriterTests(SimpleTestCase):
             '),'
         )
 
+    def test_nested_operation_expand_args_signature(self):
+        operation = custom_migration_operations.operations.ExpandArgsOperation(
+            arg=[
+                custom_migration_operations.operations.KwargsOperation(
+                    kwarg1=1,
+                    kwarg2=2,
+                ),
+            ]
+        )
+        buff, imports = OperationWriter(operation, indentation=0).serialize()
+        self.assertEqual(imports, {'import custom_migration_operations.operations'})
+        self.assertEqual(
+            buff,
+            'custom_migration_operations.operations.ExpandArgsOperation(\n'
+            '    arg=[\n'
+            '        custom_migration_operations.operations.KwargsOperation(\n'
+            '            kwarg1=1,\n'
+            '            kwarg2=2,\n'
+            '        ),\n'
+            '    ],\n'
+            '),'
+        )
+
 
 class WriterTests(TestCase):
     """