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
2014-07-05 00:48:13 +08:00
import re
2014-05-30 06:23:09 +08:00
import sys
2013-12-12 06:31:34 +08:00
import types
2013-12-24 19:25:17 +08:00
from django . apps import apps
2014-06-11 21:00:52 +08:00
from django . db import models , migrations
2013-06-19 23:23:52 +08:00
from django . db . migrations . loader import MigrationLoader
2014-06-08 09:10:44 +08:00
from django . utils import datetime_safe , six
2013-09-01 03:11:37 +08:00
from django . utils . encoding import force_text
from django . utils . functional import Promise
2014-09-07 04:42:36 +08:00
from django . utils . timezone import utc
2013-06-07 22:28:38 +08:00
2014-07-05 00:48:13 +08:00
COMPILED_REGEX_TYPE = type ( re . compile ( ' ' ) )
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 )
2014-06-11 21:00:52 +08:00
# See if this operation is in django.db.migrations. If it is,
# We can just use the fact we already have that imported,
# otherwise, we need to add an import for the operation class.
if getattr ( migrations , name , None ) == self . operation . __class__ :
self . feed ( ' migrations. %s ( ' % name )
else :
imports . add ( ' import %s ' % ( self . operation . __class__ . __module__ ) )
self . feed ( ' %s . %s ( ' % ( self . operation . __class__ . __module__ , name ) )
2013-09-26 15:25:35 +08:00
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 ( ) :
2014-04-01 03:25:08 +08:00
key_string , key_imports = MigrationWriter . serialize ( key )
2013-09-26 15:25:35 +08:00
arg_string , arg_imports = MigrationWriter . serialize ( value )
2014-04-01 03:25:08 +08:00
self . feed ( ' %s : %s , ' % ( key_string , arg_string ) )
imports . update ( key_imports )
2013-09-26 15:25:35 +08:00
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
2014-07-12 09:59:21 +08:00
self . needs_manual_porting = False
2013-06-07 22:28:38 +08:00
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 :
2014-05-06 02:39:26 +08:00
# No need to output bytestrings for dependencies
dependency = tuple ( [ force_text ( s ) for s in dependency ] )
2014-04-01 03:25:08 +08:00
dependencies . append ( " %s , " % self . serialize ( dependency ) [ 0 ] )
2013-09-26 15:25:35 +08:00
items [ " dependencies " ] = " \n " . join ( dependencies ) + " \n " if dependencies else " "
2014-07-12 09:59:21 +08:00
# Format imports nicely, swapping imports of functions from migration files
# for comments
migration_imports = set ( )
for line in list ( imports ) :
if re . match ( " ^import (.*) \ . \ d+[^ \ s]*$ " , line ) :
migration_imports . add ( line . split ( " import " ) [ 1 ] . strip ( ) )
imports . remove ( line )
self . needs_manual_porting = True
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 " "
2014-07-12 09:59:21 +08:00
if migration_imports :
items [ " imports " ] + = " \n \n # Functions from the following migrations need manual copying. \n # Move them and any dependencies into this file, then update the \n # RunPython operations to refer to the local versions: \n # %s " % (
" \n # " . join ( migration_imports )
)
2013-09-26 15:25:35 +08:00
2013-10-16 19:00:07 +08:00
# If there's a replaces, make a string for it
if self . migration . replaces :
2014-04-01 03:25:08 +08:00
items [ ' replaces_str ' ] = " \n replaces = %s \n " % self . serialize ( self . migration . replaces ) [ 0 ]
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
2014-09-07 04:42:36 +08:00
@staticmethod
def serialize_datetime ( value ) :
"""
Returns a serialized version of a datetime object that is valid ,
executable python code . It converts timezone - aware values to utc with
an ' executable ' utc representation of tzinfo .
"""
if value . tzinfo is not None and value . tzinfo != utc :
value = value . astimezone ( utc )
value_repr = repr ( value ) . replace ( " <UTC> " , " utc " )
if isinstance ( value , datetime_safe . datetime ) :
value_repr = " datetime. %s " % value_repr
return value_repr
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 )
2014-06-17 23:50:31 +08:00
# Python 3 fails when the migrations directory does not have a
# __init__.py file
if not hasattr ( migrations_module , ' __file__ ' ) :
raise ImportError
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 :
2014-05-30 06:23:09 +08:00
# In case of using MIGRATION_MODULES setting and the custom
# package doesn't exist, create one.
package_dirs = migrations_package_name . split ( " . " )
create_path = os . path . join ( sys . path [ 0 ] , * package_dirs )
if not os . path . isdir ( create_path ) :
os . makedirs ( create_path )
for i in range ( 1 , len ( package_dirs ) + 1 ) :
init_dir = os . path . join ( sys . path [ 0 ] , * package_dirs [ : i ] )
init_path = os . path . join ( init_dir , " __init__.py " )
if not os . path . isfile ( init_path ) :
open ( init_path , " w " ) . close ( )
return os . path . join ( create_path , self . filename )
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 " :
2014-09-26 20:31:50 +08:00
imports = { " from django.db import models " }
2013-09-02 14:02:07 +08:00
name = " models. %s " % name
else :
2014-09-26 20:31:50 +08:00
imports = { " import %s " % module }
2013-09-02 14:02:07 +08:00
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 .
"""
2014-04-01 03:25:08 +08:00
# FIXME: Ideally Promise would be reconstructible, but for now we
# use force_text on them and defer to the normal string serialization
# process.
if isinstance ( value , Promise ) :
value = force_text ( value )
2013-06-07 22:28:38 +08:00
# 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 ) :
2014-09-26 20:31:50 +08:00
# Don't use the literal "{%s}" as it doesn't support empty set
2013-06-07 22:28:38 +08:00
format = " set([ %s ]) "
elif isinstance ( value , tuple ) :
2014-05-22 19:29:30 +08:00
# When len(value)==0, the empty tuple should be serialized as
# "()", not "(,)" because (,) is invalid Python syntax.
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 ) :
2014-09-07 04:42:36 +08:00
value_repr = cls . serialize_datetime ( value )
imports = [ " import datetime " ]
2014-02-09 19:17:13 +08:00
if value . tzinfo is not None :
2014-09-07 04:42:36 +08:00
imports . append ( " from django.utils.timezone import utc " )
return value_repr , set ( imports )
2014-02-09 19:17:13 +08:00
# Dates
elif isinstance ( value , datetime . date ) :
2014-04-01 04:16:34 +08:00
value_repr = repr ( value )
if isinstance ( value , datetime_safe . date ) :
value_repr = " datetime. %s " % value_repr
2014-09-26 20:31:50 +08:00
return value_repr , { " import datetime " }
2014-08-19 21:23:29 +08:00
# Times
elif isinstance ( value , datetime . time ) :
value_repr = repr ( value )
2014-09-26 20:31:50 +08:00
return value_repr , { " import datetime " }
2014-01-15 22:20:47 +08:00
# Settings references
elif isinstance ( value , SettingsReference ) :
2014-09-26 20:31:50 +08:00
return " settings. %s " % value . setting_name , { " from django.conf import settings " }
2013-06-07 22:28:38 +08:00
# Simple types
2014-04-01 03:25:08 +08:00
elif isinstance ( value , six . integer_types + ( float , bool , type ( None ) ) ) :
2013-06-07 22:28:38 +08:00
return repr ( value ) , set ( )
2014-04-01 03:25:08 +08:00
elif isinstance ( value , six . binary_type ) :
value_repr = repr ( value )
if six . PY2 :
# Prepend the `b` prefix since we're importing unicode_literals
value_repr = ' b ' + value_repr
return value_repr , set ( )
elif isinstance ( value , six . text_type ) :
value_repr = repr ( value )
if six . PY2 :
# Strip the `u` prefix since we're importing unicode_literals
value_repr = value_repr [ 1 : ]
return value_repr , set ( )
2014-01-20 03:27:10 +08:00
# Decimal
elif isinstance ( value , decimal . Decimal ) :
2014-09-26 20:31:50 +08:00
return repr ( value ) , { " 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__
2014-09-26 20:31:50 +08:00
return " %s . %s . %s " % ( module , klass . __name__ , value . __name__ ) , { " import %s " % module }
2014-06-08 08:04:58 +08:00
# Further error checking
if value . __name__ == ' <lambda> ' :
2013-09-05 11:36:31 +08:00
raise ValueError ( " Cannot serialize function: lambda " )
2014-06-08 08:04:58 +08:00
if value . __module__ is None :
2013-09-05 11:36:31 +08:00
raise ValueError ( " Cannot serialize function %r : No module " % value )
2014-06-08 08:04:58 +08:00
# Python 3 is a lot easier, and only uses this branch if it's not local.
if getattr ( value , " __qualname__ " , None ) and getattr ( value , " __module__ " , None ) :
if " < " not in value . __qualname__ : # Qualname can include <locals>
2014-09-26 20:31:50 +08:00
return " %s . %s " % ( value . __module__ , value . __qualname__ ) , { " import %s " % value . __module__ }
2014-06-08 08:04:58 +08:00
# Python 2/fallback version
module_name = value . __module__
# Make sure it's actually there and not an unbound method
2014-06-08 09:10:44 +08:00
module = import_module ( module_name )
2014-06-08 08:04:58 +08:00
if not hasattr ( module , value . __name__ ) :
raise ValueError (
2014-09-08 20:15:33 +08:00
" Could not find function %s in %s . \n "
" Please note that due to Python 2 limitations, you cannot "
" serialize unbound method functions (e.g. a method "
" declared and used in the same class body). Please move "
" the function into the main module body to use migrations. \n "
" For more information, see "
" https://docs.djangoproject.com/en/dev/topics/migrations/#serializing-values "
2014-07-02 04:42:25 +08:00
% ( value . __name__ , module_name ) )
2014-09-26 20:31:50 +08:00
return " %s . %s " % ( module_name , value . __name__ ) , { " import %s " % module_name }
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__
2014-09-27 01:01:34 +08:00
if module == six . moves . builtins . __name__ :
return value . __name__ , set ( )
else :
2014-09-26 20:31:50 +08:00
return " %s . %s " % ( module , value . __name__ ) , { " 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 )
2014-05-22 19:29:30 +08:00
# When len(strings)==0, the empty iterable should be serialized as
# "()", not "(,)" because (,) is invalid Python syntax.
format = " ( %s ) " if len ( strings ) != 1 else " ( %s ,) "
2014-01-22 16:06:06 +08:00
return format % ( " , " . join ( strings ) ) , imports
2014-07-05 00:48:13 +08:00
# Compiled regex
elif isinstance ( value , COMPILED_REGEX_TYPE ) :
2014-09-26 20:31:50 +08:00
imports = { " import re " }
2014-07-05 00:48:13 +08:00
regex_pattern , pattern_imports = cls . serialize ( value . pattern )
regex_flags , flag_imports = cls . serialize ( value . flags )
imports . update ( pattern_imports )
imports . update ( flag_imports )
args = [ regex_pattern ]
if value . flags :
args . append ( regex_flags )
return " re.compile( %s ) " % ' , ' . join ( args ) , 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 = """ \
2014-05-16 01:41:55 +08:00
# -*- coding: utf-8 -*-
2014-04-01 03:25:08 +08:00
from __future__ import unicode_literals
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
"""