2013-06-08 00:56:43 +08:00
|
|
|
from __future__ import unicode_literals
|
2013-12-12 06:31:34 +08:00
|
|
|
|
2013-06-07 22:28:38 +08:00
|
|
|
import datetime
|
2013-08-10 00:36:16 +08:00
|
|
|
from importlib import import_module
|
2013-12-12 06:31:34 +08:00
|
|
|
import os
|
|
|
|
import types
|
|
|
|
|
2013-12-22 18:35:17 +08:00
|
|
|
from django.apps import app_cache
|
2013-06-07 22:36:31 +08:00
|
|
|
from django.db import models
|
2013-06-19 23:23:52 +08:00
|
|
|
from django.db.migrations.loader import MigrationLoader
|
2013-09-01 03:11:37 +08:00
|
|
|
from django.utils.encoding import force_text
|
|
|
|
from django.utils.functional import Promise
|
2013-12-12 04:44:27 +08:00
|
|
|
from django.utils import six
|
2013-06-07 22:28:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
class MigrationWriter(object):
|
|
|
|
"""
|
|
|
|
Takes a Migration instance and is able to produce the contents
|
|
|
|
of the migration file from it.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, migration):
|
|
|
|
self.migration = migration
|
|
|
|
|
|
|
|
def as_string(self):
|
|
|
|
"""
|
|
|
|
Returns a string of the file contents.
|
|
|
|
"""
|
|
|
|
items = {
|
|
|
|
"dependencies": repr(self.migration.dependencies),
|
2013-10-16 19:00:07 +08:00
|
|
|
"replaces_str": "",
|
2013-06-07 22:28:38 +08:00
|
|
|
}
|
|
|
|
imports = set()
|
|
|
|
# Deconstruct operations
|
|
|
|
operation_strings = []
|
|
|
|
for operation in self.migration.operations:
|
|
|
|
name, args, kwargs = operation.deconstruct()
|
|
|
|
arg_strings = []
|
|
|
|
for arg in args:
|
|
|
|
arg_string, arg_imports = self.serialize(arg)
|
|
|
|
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 imports nicely
|
2013-06-08 00:56:43 +08:00
|
|
|
imports.discard("from django.db import models")
|
2013-06-07 22:28:38 +08:00
|
|
|
if not imports:
|
|
|
|
items["imports"] = ""
|
|
|
|
else:
|
|
|
|
items["imports"] = "\n".join(imports) + "\n"
|
2013-10-16 19:00:07 +08:00
|
|
|
# If there's a replaces, make a string for it
|
|
|
|
if self.migration.replaces:
|
|
|
|
items['replaces_str'] = "\n replaces = %s\n" % repr(self.migration.replaces)
|
2013-06-08 00:56:43 +08:00
|
|
|
return (MIGRATION_TEMPLATE % items).encode("utf8")
|
2013-06-07 22:28:38 +08:00
|
|
|
|
|
|
|
@property
|
|
|
|
def filename(self):
|
|
|
|
return "%s.py" % self.migration.name
|
|
|
|
|
2013-06-19 23:23:52 +08:00
|
|
|
@property
|
|
|
|
def path(self):
|
2013-10-19 08:24:38 +08:00
|
|
|
migrations_package_name = MigrationLoader.migrations_module(self.migration.app_label)
|
2013-06-19 23:23:52 +08:00
|
|
|
# See if we can import the migrations module directly
|
|
|
|
try:
|
2013-10-19 08:24:38 +08:00
|
|
|
migrations_module = import_module(migrations_package_name)
|
2013-06-19 23:23:52 +08:00
|
|
|
basedir = os.path.dirname(migrations_module.__file__)
|
|
|
|
except ImportError:
|
2013-12-14 02:28:42 +08:00
|
|
|
app_config = app_cache.get_app_config(self.migration.app_label)
|
2013-10-19 08:24:38 +08:00
|
|
|
migrations_package_basename = migrations_package_name.split(".")[-1]
|
|
|
|
|
2013-06-19 23:23:52 +08:00
|
|
|
# Alright, see if it's a direct submodule of the app
|
2013-12-14 02:28:42 +08:00
|
|
|
if '%s.%s' % (app_config.name, migrations_package_basename) == migrations_package_name:
|
|
|
|
basedir = os.path.join(app_config.path, migrations_package_basename)
|
2013-06-19 23:23:52 +08:00
|
|
|
else:
|
2013-10-19 08:24:38 +08:00
|
|
|
raise ImportError("Cannot open migrations module %s for app %s" % (migrations_package_name, self.migration.app_label))
|
2013-06-19 23:23:52 +08:00
|
|
|
return os.path.join(basedir, self.filename)
|
|
|
|
|
2013-09-02 14:02:07 +08:00
|
|
|
@classmethod
|
|
|
|
def serialize_deconstructed(cls, path, args, kwargs):
|
|
|
|
module, name = path.rsplit(".", 1)
|
|
|
|
if module == "django.db.models":
|
|
|
|
imports = set(["from django.db import models"])
|
|
|
|
name = "models.%s" % name
|
|
|
|
else:
|
|
|
|
imports = set(["import %s" % module])
|
|
|
|
name = path
|
|
|
|
arg_strings = []
|
|
|
|
for arg in args:
|
|
|
|
arg_string, arg_imports = cls.serialize(arg)
|
|
|
|
arg_strings.append(arg_string)
|
|
|
|
imports.update(arg_imports)
|
|
|
|
for kw, arg in kwargs.items():
|
|
|
|
arg_string, arg_imports = cls.serialize(arg)
|
|
|
|
imports.update(arg_imports)
|
|
|
|
arg_strings.append("%s=%s" % (kw, arg_string))
|
|
|
|
return "%s(%s)" % (name, ", ".join(arg_strings)), imports
|
|
|
|
|
2013-06-07 22:28:38 +08:00
|
|
|
@classmethod
|
|
|
|
def serialize(cls, value):
|
|
|
|
"""
|
|
|
|
Serializes the value to a string that's parsable by Python, along
|
|
|
|
with any needed imports to make that string work.
|
|
|
|
More advanced than repr() as it can encode things
|
|
|
|
like datetime.datetime.now.
|
|
|
|
"""
|
|
|
|
# Sequences
|
|
|
|
if isinstance(value, (list, set, tuple)):
|
|
|
|
imports = set()
|
|
|
|
strings = []
|
|
|
|
for item in value:
|
|
|
|
item_string, item_imports = cls.serialize(item)
|
|
|
|
imports.update(item_imports)
|
|
|
|
strings.append(item_string)
|
|
|
|
if isinstance(value, set):
|
|
|
|
format = "set([%s])"
|
|
|
|
elif isinstance(value, tuple):
|
|
|
|
format = "(%s,)"
|
|
|
|
else:
|
|
|
|
format = "[%s]"
|
|
|
|
return format % (", ".join(strings)), imports
|
|
|
|
# Dictionaries
|
|
|
|
elif isinstance(value, dict):
|
|
|
|
imports = set()
|
|
|
|
strings = []
|
|
|
|
for k, v in value.items():
|
|
|
|
k_string, k_imports = cls.serialize(k)
|
|
|
|
v_string, v_imports = cls.serialize(v)
|
|
|
|
imports.update(k_imports)
|
|
|
|
imports.update(v_imports)
|
|
|
|
strings.append((k_string, v_string))
|
2013-08-30 07:20:00 +08:00
|
|
|
return "{%s}" % (", ".join("%s: %s" % (k, v) for k, v in strings)), imports
|
2013-06-07 22:28:38 +08:00
|
|
|
# Datetimes
|
|
|
|
elif isinstance(value, (datetime.datetime, datetime.date)):
|
|
|
|
return repr(value), set(["import datetime"])
|
|
|
|
# Simple types
|
2013-07-27 00:08:12 +08:00
|
|
|
elif isinstance(value, six.integer_types + (float, six.binary_type, six.text_type, bool, type(None))):
|
2013-06-07 22:28:38 +08:00
|
|
|
return repr(value), set()
|
2013-09-01 03:11:37 +08:00
|
|
|
# Promise
|
|
|
|
elif isinstance(value, Promise):
|
|
|
|
return repr(force_text(value)), set()
|
2013-06-07 22:36:31 +08:00
|
|
|
# Django fields
|
|
|
|
elif isinstance(value, models.Field):
|
|
|
|
attr_name, path, args, kwargs = value.deconstruct()
|
2013-09-02 14:02:07 +08:00
|
|
|
return cls.serialize_deconstructed(path, args, kwargs)
|
2013-10-22 01:33:57 +08:00
|
|
|
# Anything that knows how to deconstruct itself.
|
|
|
|
elif hasattr(value, 'deconstruct'):
|
|
|
|
return cls.serialize_deconstructed(*value.deconstruct())
|
2013-06-07 22:28:38 +08:00
|
|
|
# Functions
|
|
|
|
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
|
2013-10-19 01:14:01 +08:00
|
|
|
# @classmethod?
|
|
|
|
if getattr(value, "__self__", None) and isinstance(value.__self__, type):
|
|
|
|
klass = value.__self__
|
2013-06-07 22:28:38 +08:00
|
|
|
module = klass.__module__
|
|
|
|
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
2013-09-05 11:36:31 +08:00
|
|
|
elif value.__name__ == '<lambda>':
|
|
|
|
raise ValueError("Cannot serialize function: lambda")
|
|
|
|
elif value.__module__ is None:
|
|
|
|
raise ValueError("Cannot serialize function %r: No module" % value)
|
2013-06-07 22:28:38 +08:00
|
|
|
else:
|
|
|
|
module = value.__module__
|
|
|
|
return "%s.%s" % (module, value.__name__), set(["import %s" % module])
|
2013-06-19 23:23:52 +08:00
|
|
|
# Classes
|
|
|
|
elif isinstance(value, type):
|
|
|
|
special_cases = [
|
|
|
|
(models.Model, "models.Model", []),
|
|
|
|
]
|
|
|
|
for case, string, imports in special_cases:
|
|
|
|
if case is value:
|
|
|
|
return string, set(imports)
|
|
|
|
if hasattr(value, "__module__"):
|
|
|
|
module = value.__module__
|
|
|
|
return "%s.%s" % (module, value.__name__), set(["import %s" % module])
|
2013-06-07 22:28:38 +08:00
|
|
|
# Uh oh.
|
|
|
|
else:
|
|
|
|
raise ValueError("Cannot serialize: %r" % value)
|
|
|
|
|
|
|
|
|
|
|
|
MIGRATION_TEMPLATE = """# encoding: utf8
|
|
|
|
from django.db import models, migrations
|
|
|
|
%(imports)s
|
|
|
|
|
|
|
|
class Migration(migrations.Migration):
|
2013-10-16 19:00:07 +08:00
|
|
|
%(replaces_str)s
|
2013-06-07 22:28:38 +08:00
|
|
|
dependencies = %(dependencies)s
|
|
|
|
|
|
|
|
operations = %(operations)s
|
|
|
|
"""
|