Kaydet (Commit) fe9f342d authored tarafından Andrew Godwin's avatar Andrew Godwin

Allow callables as the argument to RunPython

üst 8a3e543f
import re import re
import textwrap import textwrap
from .base import Operation from .base import Operation
from django.utils import six
class SeparateDatabaseAndState(Operation): class SeparateDatabaseAndState(Operation):
...@@ -107,12 +108,17 @@ class RunPython(Operation): ...@@ -107,12 +108,17 @@ class RunPython(Operation):
reversible = False reversible = False
def __init__(self, code): def __init__(self, code):
# Trim any leading whitespace that is at the start of all code lines if isinstance(code, six.string_types):
# so users can nicely indent code in migration files # Trim any leading whitespace that is at the start of all code lines
code = textwrap.dedent(code) # so users can nicely indent code in migration files
# Run the code through a parser first to make sure it's at least code = textwrap.dedent(code)
# syntactically correct # Run the code through a parser first to make sure it's at least
self.code = compile(code, "<string>", "exec") # syntactically correct
self.code = compile(code, "<string>", "exec")
self.is_callable = False
else:
self.code = code
self.is_callable = True
def state_forwards(self, app_label, state): def state_forwards(self, app_label, state):
# RunPython objects have no state effect. To add some, combine this # RunPython objects have no state effect. To add some, combine this
...@@ -124,11 +130,14 @@ class RunPython(Operation): ...@@ -124,11 +130,14 @@ class RunPython(Operation):
# object, representing the versioned models as an AppCache. # object, representing the versioned models as an AppCache.
# We could try to override the global cache, but then people will still # We could try to override the global cache, but then people will still
# use direct imports, so we go with a documentation approach instead. # use direct imports, so we go with a documentation approach instead.
context = { if self.is_callable:
"models": from_state.render(), self.code(models=from_state.render(), schema_editor=schema_editor)
"schema_editor": schema_editor, else:
} context = {
eval(self.code, context) "models": from_state.render(),
"schema_editor": schema_editor,
}
eval(self.code, context)
def database_backwards(self, app_label, schema_editor, from_state, to_state): def database_backwards(self, app_label, schema_editor, from_state, to_state):
raise NotImplementedError("You cannot reverse this operation") raise NotImplementedError("You cannot reverse this operation")
......
...@@ -332,6 +332,15 @@ class OperationTests(MigrationTestBase): ...@@ -332,6 +332,15 @@ class OperationTests(MigrationTestBase):
# And test reversal fails # And test reversal fails
with self.assertRaises(NotImplementedError): with self.assertRaises(NotImplementedError):
operation.database_backwards("test_runpython", None, new_state, project_state) operation.database_backwards("test_runpython", None, new_state, project_state)
# Now test we can do it with a callable
def inner_method(models, schema_editor):
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)
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(), 4)
class MigrateNothingRouter(object): class MigrateNothingRouter(object):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment