root / env / lib / python2.7 / site-packages / south / management / commands / migrate.py @ d1a4905f
History | View | Annotate | Download (11.8 KB)
1 | d1a4905f | officers | """
|
---|---|---|---|
2 | Migrate management command.
|
||
3 | """
|
||
4 | |||
5 | import os.path, re, sys |
||
6 | from optparse import make_option |
||
7 | |||
8 | from django.core.management.base import BaseCommand |
||
9 | from django.conf import settings |
||
10 | from django.utils.importlib import import_module |
||
11 | |||
12 | from south import migration |
||
13 | from south.migration import Migrations |
||
14 | from south.exceptions import NoMigrations |
||
15 | from south.db import DEFAULT_DB_ALIAS |
||
16 | |||
17 | class Command(BaseCommand): |
||
18 | option_list = BaseCommand.option_list + ( |
||
19 | make_option('--all', action='store_true', dest='all_apps', default=False, |
||
20 | help='Run the specified migration for all apps.'),
|
||
21 | make_option('--list', action='store_true', dest='show_list', default=False, |
||
22 | help='List migrations noting those that have been applied'),
|
||
23 | make_option('--changes', action='store_true', dest='show_changes', default=False, |
||
24 | help='List changes for migrations'),
|
||
25 | make_option('--skip', action='store_true', dest='skip', default=False, |
||
26 | help='Will skip over out-of-order missing migrations'),
|
||
27 | make_option('--merge', action='store_true', dest='merge', default=False, |
||
28 | help='Will run out-of-order missing migrations as they are - no rollbacks.'),
|
||
29 | make_option('--no-initial-data', action='store_true', dest='no_initial_data', default=False, |
||
30 | help='Skips loading initial data if specified.'),
|
||
31 | make_option('--fake', action='store_true', dest='fake', default=False, |
||
32 | help="Pretends to do the migrations, but doesn't actually execute them."),
|
||
33 | make_option('--db-dry-run', action='store_true', dest='db_dry_run', default=False, |
||
34 | help="Doesn't execute the SQL generated by the db methods, and doesn't store a record that the migration(s) occurred. Useful to test migrations before applying them."),
|
||
35 | make_option('--delete-ghost-migrations', action='store_true', dest='delete_ghosts', default=False, |
||
36 | help="Tells South to delete any 'ghost' migrations (ones in the database but not on disk)."),
|
||
37 | make_option('--ignore-ghost-migrations', action='store_true', dest='ignore_ghosts', default=False, |
||
38 | help="Tells South to ignore any 'ghost' migrations (ones in the database but not on disk) and continue to apply new migrations."),
|
||
39 | make_option('--noinput', action='store_false', dest='interactive', default=True, |
||
40 | help='Tells Django to NOT prompt the user for input of any kind.'),
|
||
41 | make_option('--database', action='store', dest='database', |
||
42 | default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. '
|
||
43 | 'Defaults to the "default" database.'),
|
||
44 | ) |
||
45 | if '--verbosity' not in [opt.get_opt_string() for opt in BaseCommand.option_list]: |
||
46 | option_list += ( |
||
47 | make_option('--verbosity', action='store', dest='verbosity', default='1', |
||
48 | type='choice', choices=['0', '1', '2'], |
||
49 | help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
|
||
50 | ) |
||
51 | help = "Runs migrations for all apps."
|
||
52 | args = "[appname] [migrationname|zero] [--all] [--list] [--skip] [--merge] [--no-initial-data] [--fake] [--db-dry-run] [--database=dbalias]"
|
||
53 | |||
54 | def handle(self, app=None, target=None, skip=False, merge=False, backwards=False, fake=False, db_dry_run=False, show_list=False, show_changes=False, database=DEFAULT_DB_ALIAS, delete_ghosts=False, ignore_ghosts=False, **options): |
||
55 | |||
56 | # NOTE: THIS IS DUPLICATED FROM django.core.management.commands.syncdb
|
||
57 | # This code imports any module named 'management' in INSTALLED_APPS.
|
||
58 | # The 'management' module is the preferred way of listening to post_syncdb
|
||
59 | # signals, and since we're sending those out with create_table migrations,
|
||
60 | # we need apps to behave correctly.
|
||
61 | for app_name in settings.INSTALLED_APPS: |
||
62 | try:
|
||
63 | import_module('.management', app_name)
|
||
64 | except ImportError, exc: |
||
65 | msg = exc.args[0]
|
||
66 | if not msg.startswith('No module named') or 'management' not in msg: |
||
67 | raise
|
||
68 | # END DJANGO DUPE CODE
|
||
69 | |||
70 | # if all_apps flag is set, shift app over to target
|
||
71 | if options.get('all_apps', False): |
||
72 | target = app |
||
73 | app = None
|
||
74 | |||
75 | # Migrate each app
|
||
76 | if app:
|
||
77 | try:
|
||
78 | apps = [Migrations(app)] |
||
79 | except NoMigrations:
|
||
80 | print "The app '%s' does not appear to use migrations." % app |
||
81 | print "./manage.py migrate " + self.args |
||
82 | return
|
||
83 | else:
|
||
84 | apps = list(migration.all_migrations())
|
||
85 | |||
86 | # Do we need to show the list of migrations?
|
||
87 | if show_list and apps: |
||
88 | list_migrations(apps, database, **options) |
||
89 | |||
90 | if show_changes and apps: |
||
91 | show_migration_changes(apps) |
||
92 | |||
93 | if not (show_list or show_changes): |
||
94 | |||
95 | for app in apps: |
||
96 | result = migration.migrate_app( |
||
97 | app, |
||
98 | target_name = target, |
||
99 | fake = fake, |
||
100 | db_dry_run = db_dry_run, |
||
101 | verbosity = int(options.get('verbosity', 0)), |
||
102 | interactive = options.get('interactive', True), |
||
103 | load_initial_data = not options.get('no_initial_data', False), |
||
104 | merge = merge, |
||
105 | skip = skip, |
||
106 | database = database, |
||
107 | delete_ghosts = delete_ghosts, |
||
108 | ignore_ghosts = ignore_ghosts, |
||
109 | ) |
||
110 | if result is False: |
||
111 | sys.exit(1) # Migration failed, so the command fails. |
||
112 | |||
113 | |||
114 | def list_migrations(apps, database = DEFAULT_DB_ALIAS, **options): |
||
115 | """
|
||
116 | Prints a list of all available migrations, and which ones are currently applied.
|
||
117 | Accepts a list of Migrations instances.
|
||
118 | """
|
||
119 | from south.models import MigrationHistory |
||
120 | applied_migrations = MigrationHistory.objects.filter(app_name__in=[app.app_label() for app in apps]) |
||
121 | if database != DEFAULT_DB_ALIAS:
|
||
122 | applied_migrations = applied_migrations.using(database) |
||
123 | applied_migration_names = ['%s.%s' % (mi.app_name,mi.migration) for mi in applied_migrations] |
||
124 | |||
125 | print
|
||
126 | for app in apps: |
||
127 | print " " + app.app_label() |
||
128 | # Get the migrations object
|
||
129 | for migration in app: |
||
130 | if migration.app_label() + "." + migration.name() in applied_migration_names: |
||
131 | applied_migration = applied_migrations.get(app_name=migration.app_label(), migration=migration.name()) |
||
132 | print format_migration_list_item(migration.name(), applied=applied_migration.applied, **options)
|
||
133 | else:
|
||
134 | print format_migration_list_item(migration.name(), applied=False, **options) |
||
135 | print
|
||
136 | |||
137 | def show_migration_changes(apps): |
||
138 | """
|
||
139 | Prints a list of all available migrations, and which ones are currently applied.
|
||
140 | Accepts a list of Migrations instances.
|
||
141 |
|
||
142 | Much simpler, less clear, and much less robust version:
|
||
143 | grep "ing " migrations/*.py
|
||
144 | """
|
||
145 | for app in apps: |
||
146 | print app.app_label()
|
||
147 | # Get the migrations objects
|
||
148 | migrations = [migration for migration in app] |
||
149 | # we use reduce to compare models in pairs, not to generate a value
|
||
150 | reduce(diff_migrations, migrations)
|
||
151 | |||
152 | def format_migration_list_item(name, applied=True, **options): |
||
153 | if applied:
|
||
154 | if int(options.get('verbosity')) >= 2: |
||
155 | return ' (*) %-80s (applied %s)' % (name, applied) |
||
156 | else:
|
||
157 | return ' (*) %s' % name |
||
158 | else:
|
||
159 | return ' ( ) %s' % name |
||
160 | |||
161 | def diff_migrations(migration1, migration2): |
||
162 | |||
163 | def model_name(models, model): |
||
164 | return models[model].get('Meta', {}).get('object_name', model) |
||
165 | |||
166 | def field_name(models, model, field): |
||
167 | return '%s.%s' % (model_name(models, model), field) |
||
168 | |||
169 | print " " + migration2.name() |
||
170 | |||
171 | models1 = migration1.migration_class().models |
||
172 | models2 = migration2.migration_class().models |
||
173 | |||
174 | # find new models
|
||
175 | for model in models2.keys(): |
||
176 | if not model in models1.keys(): |
||
177 | print ' added model %s' % model_name(models2, model) |
||
178 | |||
179 | # find removed models
|
||
180 | for model in models1.keys(): |
||
181 | if not model in models2.keys(): |
||
182 | print ' removed model %s' % model_name(models1, model) |
||
183 | |||
184 | # compare models
|
||
185 | for model in models1: |
||
186 | if model in models2: |
||
187 | |||
188 | # find added fields
|
||
189 | for field in models2[model]: |
||
190 | if not field in models1[model]: |
||
191 | print ' added field %s' % field_name(models2, model, field) |
||
192 | |||
193 | # find removed fields
|
||
194 | for field in models1[model]: |
||
195 | if not field in models2[model]: |
||
196 | print ' removed field %s' % field_name(models1, model, field) |
||
197 | |||
198 | # compare fields
|
||
199 | for field in models1[model]: |
||
200 | if field in models2[model]: |
||
201 | |||
202 | name = field_name(models1, model, field) |
||
203 | |||
204 | # compare field attributes
|
||
205 | field_value1 = models1[model][field] |
||
206 | field_value2 = models2[model][field] |
||
207 | |||
208 | # if a field has become a class, or vice versa
|
||
209 | if type(field_value1) != type(field_value2): |
||
210 | print ' type of %s changed from %s to %s' % ( |
||
211 | name, field_value1, field_value2) |
||
212 | |||
213 | # if class
|
||
214 | elif isinstance(field_value1, dict): |
||
215 | # print ' %s is a class' % name
|
||
216 | pass
|
||
217 | |||
218 | # else regular field
|
||
219 | else:
|
||
220 | |||
221 | type1, attr_list1, field_attrs1 = models1[model][field] |
||
222 | type2, attr_list2, field_attrs2 = models2[model][field] |
||
223 | |||
224 | if type1 != type2:
|
||
225 | print ' %s type changed from %s to %s' % ( |
||
226 | name, type1, type2) |
||
227 | |||
228 | if attr_list1 != []:
|
||
229 | print ' %s list %s is not []' % ( |
||
230 | name, attr_list1) |
||
231 | if attr_list2 != []:
|
||
232 | print ' %s list %s is not []' % ( |
||
233 | name, attr_list2) |
||
234 | if attr_list1 != attr_list2:
|
||
235 | print ' %s list changed from %s to %s' % ( |
||
236 | name, attr_list1, attr_list2) |
||
237 | |||
238 | # find added field attributes
|
||
239 | for attr in field_attrs2: |
||
240 | if not attr in field_attrs1: |
||
241 | print ' added %s attribute %s=%s' % ( |
||
242 | name, attr, field_attrs2[attr]) |
||
243 | |||
244 | # find removed field attributes
|
||
245 | for attr in field_attrs1: |
||
246 | if not attr in field_attrs2: |
||
247 | print ' removed attribute %s(%s=%s)' % ( |
||
248 | name, attr, field_attrs1[attr]) |
||
249 | |||
250 | # compare field attributes
|
||
251 | for attr in field_attrs1: |
||
252 | if attr in field_attrs2: |
||
253 | |||
254 | value1 = field_attrs1[attr] |
||
255 | value2 = field_attrs2[attr] |
||
256 | if value1 != value2:
|
||
257 | print ' %s attribute %s changed from %s to %s' % ( |
||
258 | name, attr, value1, value2) |
||
259 | |||
260 | return migration2 |