Project

General

Profile

Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / south / management / commands / migrate.py @ d1a4905f

History | View | Annotate | Download (11.8 KB)

1
"""
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