1 from django
.db
import connection
2 from exceptions
import *
6 cursor
= connection
.cursor()
10 def _execute_in_transaction(*sql
):
11 cursor
= connection
.cursor()
12 cursor
.execute("BEGIN")
14 cursor
.execute("COMMIT")
16 def table_present(table_name
):
17 cursor
= _execute("SHOW TABLES LIKE %s", [table_name
])
18 return bool(cursor
.fetchone())
21 return [(m
, 'up') for m
in migrations
]
23 def _down(migrations
):
24 return [(m
, 'down') for m
in migrations
]
26 class MigrationState(object):
28 def __init__(self
, dev
=None, migration_db
=None):
29 self
.migration_db
= migration_db
32 def migration_table_present(self
):
33 return table_present('dmigrations')
35 def log(self
, action
, migration_name
, status
='success'):
36 from migration_log
import log_action
37 log_action(action
, migration_name
, status
)
39 def applied_but_not_in_db(self
):
40 migrations_in_db
= set(self
.migration_db
.list())
41 applied_migrations
= [
42 m
[0] for m
in _execute(
43 "SELECT migration FROM dmigrations"
46 return self
.migration_db
.sort_migrations(
47 [m
for m
in applied_migrations
if m
not in migrations_in_db
]
50 def apply(self
, name
):
52 migration
= self
.migration_db
.load_migration_object(name
)
54 self
.mark_as_applied(name
, log
=False)
55 self
.log('apply', name
)
57 self
.log('apply', name
, str(e
))
60 def unapply(self
, name
):
62 migration
= self
.migration_db
.load_migration_object(name
)
64 self
.mark_as_unapplied(name
, log
=False)
65 self
.log('unapply', name
)
67 self
.log('unapply', name
, str(e
))
70 def mark_as_applied(self
, name
, log
=True):
71 if not self
.is_applied(name
):
72 _execute_in_transaction(
73 "INSERT INTO dmigrations (migration) VALUES (%s)", [name
]
76 self
.log('mark_as_applied', name
)
78 def mark_as_unapplied(self
, name
, log
=True):
79 if self
.is_applied(name
):
80 _execute_in_transaction(
81 "DELETE FROM dmigrations WHERE migration = %s", [name
]
84 self
.log('mark_as_unapplied', name
)
86 def is_applied(self
, name
):
88 "SELECT * FROM dmigrations WHERE migration = %s", [name
]
90 return bool(cursor
.fetchone())
92 def all_migrations_applied(self
):
93 cursor
= _execute("SELECT migration FROM dmigrations")
94 return self
.migration_db
.sort_migrations(
95 [row
[0] for row
in cursor
.fetchall()]
98 def create_migration_table(self
):
100 CREATE TABLE `dmigrations` (
101 `id` int(11) NOT NULL auto_increment,
102 `migration` VARCHAR(255) NOT NULL,
104 ) ENGINE=InnoDB DEFAULT CHARSET=utf8
106 _execute_in_transaction(create_new
)
109 "Create the dmigration table, if necessary"
110 if not self
.migration_table_present():
111 self
.create_migration_table()
112 from migration_log
import init
as log_init
115 def resolve_name(self
, name
):
117 Resolve user-friendly migration name to real full migration name.
118 So both "5" and "005_foo" become "005_foo.py" etc.
120 Raises exception if ambiguous, returns None if not found.
123 if re
.search('^\d+$', name
):
124 name
= self
.migration_db
.find_unique_migration_by_number(
129 def force_resolve_name(self
, name
):
131 Resolve name or raise an exception if not possible.
133 Raises exception if ambiguous or not found.
135 resolved_name
= self
.resolve_name(name
)
136 if resolved_name
not in self
.migration_db
.list():
137 raise NoSuchMigrationError(name
)
140 def list_considering_dev(self
):
142 Return list of migrations considering value of dev flag.
145 m
for m
in self
.migration_db
.list()
146 if self
.dev
or not self
.migration_db
.is_dev_migration(m
)
149 def plan_to(self
, point
):
151 Point can be a migration name or a number.
152 Number can resolve to an unique migration name,
153 or to a point between migrations,
154 but it cannot resolve to an ambiguous migration.
156 Return plan to migrate to such point.
158 migrations
= self
.list_considering_dev()
160 if point
in migrations
:
161 i
= migrations
.index(point
) + 1
162 elif re
.search(r
'^\d+$', point
):
164 # NOTE: It only checks that the point is not a duplicate
165 self
.migration_db
.find_unique_migration_by_number(point
)
167 while i
< len(migrations
) and \
168 self
.migration_db
.migration_number(migrations
[i
]) <= point
:
171 raise NoSuchMigrationError(point
)
174 self
.applied_only(reversed(migrations
[i
:]))
176 self
.unapplied_only(migrations
[:i
])
179 def applied_only(self
, migrations
):
180 return [m
for m
in migrations
if self
.is_applied(m
)]
182 def unapplied_only(self
, migrations
):
183 return [m
for m
in migrations
if not self
.is_applied(m
)]
185 def plan(self
, action
, *args
):
186 if action
in ['all', 'up', 'down'] and len(args
) > 0:
187 raise Exception(u
"Too many arguments")
189 if action
in ['upto', 'downto', 'to'] and len(args
) != 1:
190 raise Exception(u
"Action %s requires exactly 1 argument" % action
)
192 if action
== 'apply':
193 return _up(self
.unapplied_only(
194 [self
.force_resolve_name(arg
) for arg
in args
]
197 if action
== 'unapply':
198 return _down(self
.applied_only(
199 [self
.force_resolve_name(arg
) for arg
in args
]
203 return _up(self
.unapplied_only(self
.list_considering_dev()))
206 return _up(self
.unapplied_only(self
.list_considering_dev()))[:1]
209 return _down(reversed(
210 self
.applied_only(self
.list_considering_dev())
214 return self
.plan_to(*args
)
216 if action
== 'downto':
217 return [(m
,a
) for (m
,a
) in self
.plan_to(*args
) if a
== 'down']
220 return [(m
,a
) for (m
,a
) in self
.plan_to(*args
) if a
== 'up']
223 u
"Unknown action %s" % " ".join([action
] + list(args
))