Commit 202bf69c authored by Andrew Gorcester's avatar Andrew Gorcester Committed by Baptiste Mispelon
Browse files

Fixed #22095 -- Enabled backward migrations for RunPython operations

Added reversible property to RunPython so that migrations will not
refuse to reverse migrations including RunPython operations, so long as
reverse_code is set in the RunPython constructor. Included tests to
check the reversible property on RunPython and the similar RunSQL.
parent b8874084
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -103,7 +103,6 @@ class RunPython(Operation):
    """

    reduces_to_sql = False
    reversible = False

    def __init__(self, code, reverse_code=None):
        # Forwards code
@@ -118,6 +117,10 @@ class RunPython(Operation):
                raise ValueError("RunPython must be supplied with callable arguments")
            self.reverse_code = reverse_code

    @property
    def reversible(self):
        return self.reverse_code is not None

    def state_forwards(self, app_label, state):
        # RunPython objects have no state effect. To add some, combine this
        # with SeparateDatabaseAndState.
+20 −4
Original line number Diff line number Diff line
@@ -468,6 +468,7 @@ class OperationTests(MigrationTestBase):
            operation.database_forwards("test_runsql", editor, project_state, new_state)
        self.assertTableExists("i_love_ponies")
        # And test reversal
        self.assertTrue(operation.reversible)
        with connection.schema_editor() as editor:
            operation.database_backwards("test_runsql", editor, new_state, project_state)
        self.assertTableNotExists("i_love_ponies")
@@ -484,7 +485,11 @@ class OperationTests(MigrationTestBase):
            Pony = models.get_model("test_runpython", "Pony")
            Pony.objects.create(pink=1, weight=3.55)
            Pony.objects.create(weight=5)
        operation = migrations.RunPython(inner_method)
        def inner_method_reverse(models, schema_editor):
            Pony = models.get_model("test_runpython", "Pony")
            Pony.objects.filter(pink=1, weight=3.55).delete()
            Pony.objects.filter(weight=5).delete()
        operation = migrations.RunPython(inner_method, reverse_code=inner_method_reverse)
        # Test the state alteration does nothing
        new_state = project_state.clone()
        operation.state_forwards("test_runpython", new_state)
@@ -494,13 +499,24 @@ class OperationTests(MigrationTestBase):
        with connection.schema_editor() as editor:
            operation.database_forwards("test_runpython", editor, project_state, new_state)
        self.assertEqual(project_state.render().get_model("test_runpython", "Pony").objects.count(), 2)
        # And test reversal fails
        with self.assertRaises(NotImplementedError):
            operation.database_backwards("test_runpython", None, new_state, project_state)
        # Now test reversal
        self.assertTrue(operation.reversible)
        with connection.schema_editor() as editor:
            operation.database_backwards("test_runpython", editor, project_state, new_state)
        self.assertEqual(project_state.render().get_model("test_runpython", "Pony").objects.count(), 0)
        # Now test we can't use a string
        with self.assertRaises(ValueError):
            operation = migrations.RunPython("print 'ahahaha'")

        # Also test reversal fails, with an operation identical to above but without reverse_code set
        no_reverse_operation = migrations.RunPython(inner_method)
        self.assertFalse(no_reverse_operation.reversible)
        with connection.schema_editor() as editor:
            no_reverse_operation.database_forwards("test_runpython", editor, project_state, new_state)
            with self.assertRaises(NotImplementedError):
                no_reverse_operation.database_backwards("test_runpython", editor, new_state, project_state)



class MigrateNothingRouter(object):
    """