Fixed #21323 -- Improved readability of serialized Operation.

This commit is contained in:
Loic Bistuer 2013-09-26 14:25:35 +07:00
parent c9de1b4a55
commit 374faa4721
4 changed files with 108 additions and 32 deletions

View File

@ -21,6 +21,8 @@ class Operation(object):
# Can this migration be represented as SQL? (things like RunPython cannot) # Can this migration be represented as SQL? (things like RunPython cannot)
reduces_to_sql = True reduces_to_sql = True
serialization_expand_args = []
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
# We capture the arguments to make returning them trivial # We capture the arguments to make returning them trivial
self = object.__new__(cls) self = object.__new__(cls)

View File

@ -1,8 +1,8 @@
from .base import Operation
from django.utils import six
from django.db import models, router from django.db import models, router
from django.db.models.options import normalize_unique_together from django.db.models.options import normalize_unique_together
from django.db.migrations.state import ModelState from django.db.migrations.state import ModelState
from django.db.migrations.operations.base import Operation
from django.utils import six
class CreateModel(Operation): class CreateModel(Operation):
@ -10,6 +10,8 @@ class CreateModel(Operation):
Create a model's table. Create a model's table.
""" """
serialization_expand_args = ['fields', 'options']
def __init__(self, name, fields, options=None, bases=None): def __init__(self, name, fields, options=None, bases=None):
self.name = name self.name = name
self.fields = fields self.fields = fields

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import inspect
from importlib import import_module from importlib import import_module
import os import os
import types import types
@ -27,6 +28,64 @@ class SettingsReference(str):
self.setting_name = setting_name self.setting_name = setting_name
class OperationWriter(object):
indentation = 2
def __init__(self, operation):
self.operation = operation
self.buff = []
def serialize(self):
imports = set()
name, args, kwargs = self.operation.deconstruct()
argspec = inspect.getargspec(self.operation.__init__)
normalized_kwargs = inspect.getcallargs(self.operation.__init__, *args, **kwargs)
self.feed('migrations.%s(' % name)
self.indent()
for arg_name in argspec.args[1:]:
arg_value = normalized_kwargs[arg_name]
if (arg_name in self.operation.serialization_expand_args and
isinstance(arg_value, (list, tuple, dict))):
if isinstance(arg_value, dict):
self.feed('%s={' % arg_name)
self.indent()
for key, value in arg_value.items():
arg_string, arg_imports = MigrationWriter.serialize(value)
self.feed('%s: %s,' % (repr(key), arg_string))
imports.update(arg_imports)
self.unindent()
self.feed('},')
else:
self.feed('%s=[' % arg_name)
self.indent()
for item in arg_value:
arg_string, arg_imports = MigrationWriter.serialize(item)
self.feed('%s,' % arg_string)
imports.update(arg_imports)
self.unindent()
self.feed('],')
else:
arg_string, arg_imports = MigrationWriter.serialize(arg_value)
self.feed('%s=%s,' % (arg_name, arg_string))
imports.update(arg_imports)
self.unindent()
self.feed('),')
return self.render(), imports
def indent(self):
self.indentation += 1
def unindent(self):
self.indentation -= 1
def feed(self, line):
self.buff.append(' ' * (self.indentation * 4) + line)
def render(self):
return '\n'.join(self.buff)
class MigrationWriter(object): class MigrationWriter(object):
""" """
Takes a Migration instance and is able to produce the contents Takes a Migration instance and is able to produce the contents
@ -43,40 +102,35 @@ class MigrationWriter(object):
items = { items = {
"replaces_str": "", "replaces_str": "",
} }
imports = set() imports = set()
# Deconstruct operations # Deconstruct operations
operation_strings = [] operations = []
for operation in self.migration.operations: for operation in self.migration.operations:
name, args, kwargs = operation.deconstruct() operation_string, operation_imports = OperationWriter(operation).serialize()
arg_strings = [] imports.update(operation_imports)
for arg in args: operations.append(operation_string)
arg_string, arg_imports = self.serialize(arg) items["operations"] = "\n".join(operations) + "\n" if operations else ""
arg_strings.append(arg_string)
imports.update(arg_imports)
for kw, arg in kwargs.items():
arg_string, arg_imports = self.serialize(arg)
imports.update(arg_imports)
arg_strings.append("%s = %s" % (kw, arg_string))
operation_strings.append("migrations.%s(%s\n )" % (name, "".join("\n %s," % arg for arg in arg_strings)))
items["operations"] = "[%s\n ]" % "".join("\n %s," % s for s in operation_strings)
# Format dependencies and write out swappable dependencies right # Format dependencies and write out swappable dependencies right
items["dependencies"] = "[" dependencies = []
for dependency in self.migration.dependencies: for dependency in self.migration.dependencies:
if dependency[0] == "__setting__": if dependency[0] == "__setting__":
items["dependencies"] += "\n migrations.swappable_dependency(settings.%s)," % dependency[1] dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1])
imports.add("from django.conf import settings") imports.add("from django.conf import settings")
else: else:
items["dependencies"] += "\n %s," % repr(dependency) dependencies.append(" %s," % repr(dependency))
items["dependencies"] += "\n ]" items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else ""
# Format imports nicely # Format imports nicely
imports.discard("from django.db import models") imports.discard("from django.db import models")
if not imports: items["imports"] = "\n".join(imports) + "\n" if imports else ""
items["imports"] = ""
else:
items["imports"] = "\n".join(imports) + "\n"
# If there's a replaces, make a string for it # If there's a replaces, make a string for it
if self.migration.replaces: if self.migration.replaces:
items['replaces_str'] = "\n replaces = %s\n" % repr(self.migration.replaces) items['replaces_str'] = "\n replaces = %s\n" % repr(self.migration.replaces)
return (MIGRATION_TEMPLATE % items).encode("utf8") return (MIGRATION_TEMPLATE % items).encode("utf8")
@property @property
@ -110,16 +164,16 @@ class MigrationWriter(object):
else: else:
imports = set(["import %s" % module]) imports = set(["import %s" % module])
name = path name = path
arg_strings = [] strings = []
for arg in args: for arg in args:
arg_string, arg_imports = cls.serialize(arg) arg_string, arg_imports = cls.serialize(arg)
arg_strings.append(arg_string) strings.append(arg_string)
imports.update(arg_imports) imports.update(arg_imports)
for kw, arg in kwargs.items(): for kw, arg in kwargs.items():
arg_string, arg_imports = cls.serialize(arg) arg_string, arg_imports = cls.serialize(arg)
imports.update(arg_imports) imports.update(arg_imports)
arg_strings.append("%s=%s" % (kw, arg_string)) strings.append("%s=%s" % (kw, arg_string))
return "%s(%s)" % (name, ", ".join(arg_strings)), imports return "%s(%s)" % (name, ", ".join(strings)), imports
@classmethod @classmethod
def serialize(cls, value): def serialize(cls, value):
@ -140,7 +194,7 @@ class MigrationWriter(object):
if isinstance(value, set): if isinstance(value, set):
format = "set([%s])" format = "set([%s])"
elif isinstance(value, tuple): elif isinstance(value, tuple):
format = "(%s,)" format = "(%s)" if len(value) else "(%s,)"
else: else:
format = "[%s]" format = "[%s]"
return format % (", ".join(strings)), imports return format % (", ".join(strings)), imports
@ -204,13 +258,18 @@ class MigrationWriter(object):
raise ValueError("Cannot serialize: %r" % value) raise ValueError("Cannot serialize: %r" % value)
MIGRATION_TEMPLATE = """# encoding: utf8 MIGRATION_TEMPLATE = """\
# encoding: utf8
from django.db import models, migrations from django.db import models, migrations
%(imports)s %(imports)s
class Migration(migrations.Migration): class Migration(migrations.Migration):
%(replaces_str)s %(replaces_str)s
dependencies = %(dependencies)s dependencies = [
%(dependencies)s\
]
operations = %(operations)s operations = [
%(operations)s\
]
""" """

View File

@ -107,10 +107,23 @@ class WriterTests(TestCase):
""" """
Tests serializing a simple migration. Tests serializing a simple migration.
""" """
fields = {
'charfield': models.DateTimeField(default=datetime.datetime.utcnow),
'datetimefield': models.DateTimeField(default=datetime.datetime.utcnow),
}
options = {
'verbose_name': 'My model',
'verbose_name_plural': 'My models',
}
migration = type(str("Migration"), (migrations.Migration,), { migration = type(str("Migration"), (migrations.Migration,), {
"operations": [ "operations": [
migrations.CreateModel("MyModel", tuple(fields.items()), options, (models.Model,)),
migrations.CreateModel("MyModel2", tuple(fields.items()), bases=(models.Model,)),
migrations.CreateModel(name="MyModel3", fields=tuple(fields.items()), options=options, bases=(models.Model,)),
migrations.DeleteModel("MyModel"), migrations.DeleteModel("MyModel"),
migrations.AddField("OtherModel", "field_name", models.DateTimeField(default=datetime.datetime.utcnow)) migrations.AddField("OtherModel", "datetimefield", fields["datetimefield"]),
], ],
"dependencies": [("testapp", "some_other_one")], "dependencies": [("testapp", "some_other_one")],
}) })