2 These classes represent possible migrations. A migration is simply an object
3 with an up() method and a down() method - the down() method is allowed to
4 raise an IrreversibleMigration exception. These objects are instances of
5 subclasses of BaseMigration. Migration classes will be provided for stuff
6 ranging from basic SQL migrations to more specialised things such as adding
9 from dmigrations
.migrations
import BaseMigration
12 class IrreversibleMigrationError(Exception):
15 class Migration(BaseMigration
):
16 "Explict SQL migration, with sql for migrating both up and down"
18 def __init__(self
, sql_up
, sql_down
=None):
20 self
.sql_down
= sql_down
23 self
.execute_sql(self
.sql_up
)
27 self
.execute_sql(self
.sql_down
)
29 raise IrreversibleMigrationError
, 'No sql_down provided'
31 def execute_sql(self
, sql
, return_rows
=False):
32 "Executes sql, which can be a string or a list of strings"
34 if isinstance(sql
, basestring
):
35 # Split string in to multiple statements
36 statements_re
= re
.compile(r
";[ \t]*$", re
.M
)
37 statements
= [s
for s
in statements_re
.split(sql
) if s
.strip()]
38 elif isinstance(sql
, (list, tuple)):
39 # Assume each item in the list is already an individual statement
42 assert False, 'sql argument must be string or list/tuple'
44 from django
.db
import connection
45 cursor
= connection
.cursor()
47 for statement
in statements
:
48 # Escape % due to format strings
49 cursor
.execute(statement
.replace('%', '%%'))
52 return cursor
.fetchall()
55 return 'Migration, up: %r, down: %r' % (self
.sql_up
, self
.sql_down
)
57 class Compound(BaseMigration
):
59 A migration that is composed of one or more other migrations. DO NOT USE.
61 def __init__(self
, migrations
=[]):
62 self
.migrations
= migrations
65 for migration
in self
.migrations
:
69 for migration
in reversed(self
.migrations
):
73 return 'Compound Migration: %s' % self
.migrations
76 class AddColumn(Migration
):
77 "A migration that adds a database column"
79 add_column_sql
= 'ALTER TABLE `%s_%s` ADD COLUMN `%s` %s;'
80 drop_column_sql
= 'ALTER TABLE `%s_%s` DROP COLUMN `%s`;'
81 constrain_to_table_sql
= 'ALTER TABLE `%s_%s` ADD CONSTRAINT %s FOREIGN KEY (`%s`) REFERENCES `%s` (`id`);'
82 constrain_to_table_down_sql
= 'ALTER TABLE `%s_%s` DROP FOREIGN KEY `%s`;'
84 def __init__(self
, app
, model
, column
, spec
, constrain_to_table
=None):
85 self
.app
, self
.model
, self
.column
, self
.spec
= app
, model
, column
, spec
86 if constrain_to_table
:
87 # this can only be used for ForeignKeys that link to another table's
88 # id field. It is not for arbitrary relationships across tables!
89 # Note also that this will create the ForeignKey field as allowing
90 # nulls. Even if you don't want it to. This is because if it doesn't
91 # allow null then the migration will blow up, because we're adding
92 # the column without adding data to it. So you have to write another
93 # migration later to change it from NULL to NOT NULL if you need to,
94 # after you've populated it.
96 # add the FK constraint
97 constraint_name
= "%s_refs_id_%x" % (column
, abs(hash((model
,constrain_to_table
))))
98 sql_up
= [self
.constrain_to_table_sql
% (app
, model
, constraint_name
, "%s_id" % column
, constrain_to_table
)]
100 sql_up
.insert(0,self
.add_column_sql
% (app
, model
, "%s_id" % column
, spec
))
101 sql_down
= [self
.drop_column_sql
% (app
, model
, "%s_id" % column
)]
102 # if add_column_sql has NOT NULL in it, bin it
103 sql_up
[0] = sql_up
[0].replace(" NOT NULL", "")
104 # drop FK on sql_down
105 sql_down
.insert(0,self
.constrain_to_table_down_sql
% (app
, model
, constraint_name
))
108 sql_up
= [self
.add_column_sql
% (app
, model
, column
, spec
)]
109 sql_down
= [self
.drop_column_sql
% (app
, model
, column
)]
111 super(AddColumn
, self
).__init
__(
117 return "AddColumn: app: %s, model: %s, column: %s, spec: %s" % (
118 self
.app
, self
.model
, self
.column
, self
.spec
121 class DropColumn(AddColumn
):
123 A migration that drops a database column. Needs the full column spec so
124 it can correctly create the down() method.
126 def __init__(self
, *args
, **kwargs
):
127 super(DropColumn
, self
).__init
__(*args
, **kwargs
)
128 # Now swap over the sql_up and sql_down properties
129 self
.sql_up
, self
.sql_down
= self
.sql_down
, self
.sql_up
132 return super(DropColumn
, self
).replace('AddColumn', 'DropColumn')
134 class AddIndex(Migration
):
135 "A migration that adds an index (and removes it on down())"
137 add_index_sql
= 'CREATE INDEX `%s` ON `%s_%s` (`%s`);'
138 drop_index_sql
= 'ALTER TABLE %s_%s DROP INDEX `%s`;'
140 def __init__(self
, app
, model
, column
):
141 self
.app
, self
.model
, self
.column
= app
, model
, column
142 index_name
= '%s_%s_%s' % (app
, model
, column
)
143 super(AddIndex
, self
).__init
__(
144 sql_up
= [self
.add_index_sql
% (index_name
, app
, model
, column
)],
145 sql_down
= [self
.drop_index_sql
% (app
, model
, index_name
)],
149 return "AddIndex: app: %s, model: %s, column: %s" % (
150 self
.app
, self
.model
, self
.column
153 class DropIndex(AddIndex
):
155 def __init__(self
, app
, model
, column
):
156 super(DropIndex
, self
).__init
__(app
, model
, column
)
157 self
.sql_up
, self
.sql_down
= self
.sql_down
, self
.sql_up
160 return super(DropIndex
, self
).replace('AddIndex', 'DropIndex')
162 class InsertRows(Migration
):
163 "Inserts some rows in to a table"
165 insert_row_sql
= 'INSERT INTO `%s` (%s) VALUES (%s)'
166 delete_rows_sql
= 'DELETE FROM `%s` WHERE id IN (%s)'
168 def __init__(self
, table_name
, columns
, insert_rows
, delete_ids
):
169 self
.table_name
= table_name
171 from django
.db
import connection
# so we can use escape_string
172 connection
.cursor() # Opens connection if not already open
177 v
= unicode(v
) # In case v is an integer or long
178 # escape_string wants a bytestring
179 escaped
= connection
.connection
.escape_string(v
.encode('utf8'))
180 # We get bugs if we use bytestrings elsewhere, so convert back to unicode
181 # http://sourceforge.net/forum/forum.php?thread_id=1609278&forum_id=70461
182 return u
"'%s'" % escaped
.decode('utf8')
184 for row
in insert_rows
:
185 values
= ', '.join(map(escape
, row
))
187 self
.insert_row_sql
% (
188 table_name
, ', '.join(map(str, columns
)), values
193 sql_down
= [self
.delete_rows_sql
% (table_name
, ', '.join(map(str, delete_ids
)))]
195 sql_down
= ["SELECT 1"]
197 super(InsertRows
, self
).__init
__(
198 sql_up
= ["BEGIN"] + sql_up
+ ["COMMIT"],
199 sql_down
= ["BEGIN"] + sql_down
+ ["COMMIT"],