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-09-26 15:25:35 +08:00
import inspect
2014-01-20 03:27:10 +08:00
import decimal
2014-01-22 16:06:06 +08:00
import collections
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-24 19:25:17 +08:00
from django . apps import apps
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
2014-01-15 22:20:47 +08:00
class SettingsReference ( str ) :
"""
Special subclass of string which actually references a current settings
value . It ' s treated as the value in memory, but serializes out to a
settings . NAME attribute reference .
"""
def __new__ ( self , value , setting_name ) :
return str . __new__ ( self , value )
def __init__ ( self , value , setting_name ) :
self . setting_name = setting_name
2013-09-26 15:25:35 +08:00
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 )
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 = {
2013-10-16 19:00:07 +08:00
" replaces_str " : " " ,
2013-06-07 22:28:38 +08:00
}
2013-09-26 15:25:35 +08:00
2013-06-07 22:28:38 +08:00
imports = set ( )
2013-09-26 15:25:35 +08:00
2013-06-07 22:28:38 +08:00
# Deconstruct operations
2013-09-26 15:25:35 +08:00
operations = [ ]
2013-06-07 22:28:38 +08:00
for operation in self . migration . operations :
2013-09-26 15:25:35 +08:00
operation_string , operation_imports = OperationWriter ( operation ) . serialize ( )
imports . update ( operation_imports )
operations . append ( operation_string )
items [ " operations " ] = " \n " . join ( operations ) + " \n " if operations else " "
2014-01-15 22:20:47 +08:00
# Format dependencies and write out swappable dependencies right
2013-09-26 15:25:35 +08:00
dependencies = [ ]
2014-01-15 22:20:47 +08:00
for dependency in self . migration . dependencies :
if dependency [ 0 ] == " __setting__ " :
2013-09-26 15:25:35 +08:00
dependencies . append ( " migrations.swappable_dependency(settings. %s ), " % dependency [ 1 ] )
2014-01-15 22:20:47 +08:00
imports . add ( " from django.conf import settings " )
else :
2013-09-26 15:25:35 +08:00
dependencies . append ( " %s , " % repr ( dependency ) )
items [ " dependencies " ] = " \n " . join ( dependencies ) + " \n " if dependencies else " "
2013-06-07 22:28:38 +08:00
# Format imports nicely
2013-06-08 00:56:43 +08:00
imports . discard ( " from django.db import models " )
2013-09-26 15:25:35 +08:00
items [ " imports " ] = " \n " . join ( imports ) + " \n " if imports else " "
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-09-26 15:25:35 +08:00
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-24 19:25:17 +08:00
app_config = apps . 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
2013-09-26 15:25:35 +08:00
strings = [ ]
2013-09-02 14:02:07 +08:00
for arg in args :
arg_string , arg_imports = cls . serialize ( arg )
2013-09-26 15:25:35 +08:00
strings . append ( arg_string )
2013-09-02 14:02:07 +08:00
imports . update ( arg_imports )
for kw , arg in kwargs . items ( ) :
arg_string , arg_imports = cls . serialize ( arg )
imports . update ( arg_imports )
2013-09-26 15:25:35 +08:00
strings . append ( " %s = %s " % ( kw , arg_string ) )
return " %s ( %s ) " % ( name , " , " . join ( strings ) ) , imports
2013-09-02 14:02:07 +08:00
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 ) :
2014-01-20 02:35:49 +08:00
format = " ( %s ) " if len ( value ) > 1 else " ( %s ,) "
2013-06-07 22:28:38 +08:00
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
2014-02-09 19:17:13 +08:00
elif isinstance ( value , datetime . datetime ) :
if value . tzinfo is not None :
raise ValueError ( " Cannot serialize datetime values with timezones. Either use a callable value for default or remove the timezone. " )
return repr ( value ) , set ( [ " import datetime " ] )
# Dates
elif isinstance ( value , datetime . date ) :
2013-06-07 22:28:38 +08:00
return repr ( value ) , set ( [ " import datetime " ] )
2014-01-15 22:20:47 +08:00
# Settings references
elif isinstance ( value , SettingsReference ) :
return " settings. %s " % value . setting_name , set ( [ " from django.conf import settings " ] )
2013-06-07 22:28:38 +08:00
# 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 ( )
2014-01-20 03:27:10 +08:00
# Decimal
elif isinstance ( value , decimal . Decimal ) :
return repr ( value ) , set ( [ " from decimal import Decimal " ] )
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 ] )
2014-01-22 16:06:06 +08:00
# Other iterables
elif isinstance ( value , collections . Iterable ) :
imports = set ( )
strings = [ ]
for item in value :
item_string , item_imports = cls . serialize ( item )
imports . update ( item_imports )
strings . append ( item_string )
format = " ( %s ) " if len ( strings ) > 1 else " ( %s ,) "
return format % ( " , " . join ( strings ) ) , imports
2013-06-07 22:28:38 +08:00
# Uh oh.
else :
2014-01-20 03:27:10 +08:00
raise ValueError ( " Cannot serialize: %r \n There are some values Django cannot serialize into migration files. \n For more, see https://docs.djangoproject.com/en/dev/topics/migrations/#migration-serializing " % value )
2013-06-07 22:28:38 +08:00
2013-09-26 15:25:35 +08:00
MIGRATION_TEMPLATE = """ \
# encoding: utf8
2013-06-07 22:28:38 +08:00
from django . db import models , migrations
% ( imports ) s
class Migration ( migrations . Migration ) :
2014-01-29 23:53:47 +08:00
% ( replaces_str ) s
2013-09-26 15:25:35 +08:00
dependencies = [
% ( dependencies ) s \
]
2013-06-07 22:28:38 +08:00
2013-09-26 15:25:35 +08:00
operations = [
% ( operations ) s \
]
2013-06-07 22:28:38 +08:00
"""