root / env / lib / python2.7 / site-packages / south / management / commands / schemamigration.py @ d1a4905f
History | View | Annotate | Download (9.76 KB)
1 | d1a4905f | officers | """
|
---|---|---|---|
2 | Startmigration command, version 2.
|
||
3 | """
|
||
4 | |||
5 | import sys |
||
6 | import os |
||
7 | import re |
||
8 | import string |
||
9 | import random |
||
10 | import inspect |
||
11 | from optparse import make_option |
||
12 | |||
13 | try:
|
||
14 | set
|
||
15 | except NameError: |
||
16 | from sets import Set as set |
||
17 | |||
18 | from django.core.management.base import BaseCommand |
||
19 | from django.core.management.color import no_style |
||
20 | from django.db import models |
||
21 | from django.conf import settings |
||
22 | |||
23 | from south.migration import Migrations, migrate_app |
||
24 | from south.models import MigrationHistory |
||
25 | from south.exceptions import NoMigrations |
||
26 | from south.creator import changes, actions, freezer |
||
27 | from south.management.commands.datamigration import Command as DataCommand |
||
28 | |||
29 | class Command(DataCommand): |
||
30 | option_list = DataCommand.option_list + ( |
||
31 | make_option('--add-model', action='append', dest='added_model_list', type='string', |
||
32 | help='Generate a Create Table migration for the specified model. Add multiple models to this migration with subsequent --model parameters.'),
|
||
33 | make_option('--add-field', action='append', dest='added_field_list', type='string', |
||
34 | help='Generate an Add Column migration for the specified modelname.fieldname - you can use this multiple times to add more than one column.'),
|
||
35 | make_option('--add-index', action='append', dest='added_index_list', type='string', |
||
36 | help='Generate an Add Index migration for the specified modelname.fieldname - you can use this multiple times to add more than one column.'),
|
||
37 | make_option('--initial', action='store_true', dest='initial', default=False, |
||
38 | help='Generate the initial schema for the app.'),
|
||
39 | make_option('--auto', action='store_true', dest='auto', default=False, |
||
40 | help='Attempt to automatically detect differences from the last migration.'),
|
||
41 | make_option('--empty', action='store_true', dest='empty', default=False, |
||
42 | help='Make a blank migration.'),
|
||
43 | make_option('--update', action='store_true', dest='update', default=False, |
||
44 | help='Update the most recent migration instead of creating a new one. Rollback this migration if it is already applied.'),
|
||
45 | ) |
||
46 | help = "Creates a new template schema migration for the given app"
|
||
47 | usage_str = "Usage: ./manage.py schemamigration appname migrationname [--empty] [--initial] [--auto] [--add-model ModelName] [--add-field ModelName.field_name] [--stdout]"
|
||
48 | |||
49 | def handle(self, app=None, name="", added_model_list=None, added_field_list=None, freeze_list=None, initial=False, auto=False, stdout=False, added_index_list=None, verbosity=1, empty=False, update=False, **options): |
||
50 | |||
51 | # Any supposed lists that are None become empty lists
|
||
52 | added_model_list = added_model_list or []
|
||
53 | added_field_list = added_field_list or []
|
||
54 | added_index_list = added_index_list or []
|
||
55 | freeze_list = freeze_list or []
|
||
56 | |||
57 | # --stdout means name = -
|
||
58 | if stdout:
|
||
59 | name = "-"
|
||
60 | |||
61 | # Only allow valid names
|
||
62 | if re.search('[^_\w]', name) and name != "-": |
||
63 | self.error("Migration names should contain only alphanumeric characters and underscores.") |
||
64 | |||
65 | # Make sure options are compatable
|
||
66 | if initial and (added_model_list or added_field_list or auto): |
||
67 | self.error("You cannot use --initial and other options together\n" + self.usage_str) |
||
68 | |||
69 | if auto and (added_model_list or added_field_list or initial): |
||
70 | self.error("You cannot use --auto and other options together\n" + self.usage_str) |
||
71 | |||
72 | if not app: |
||
73 | self.error("You must provide an app to create a migration for.\n" + self.usage_str) |
||
74 | |||
75 | # Get the Migrations for this app (creating the migrations dir if needed)
|
||
76 | migrations = Migrations(app, force_creation=True, verbose_creation=int(verbosity) > 0) |
||
77 | |||
78 | # What actions do we need to do?
|
||
79 | if auto:
|
||
80 | # Get the old migration
|
||
81 | try:
|
||
82 | last_migration = migrations[-2 if update else -1] |
||
83 | except IndexError: |
||
84 | self.error("You cannot use --auto on an app with no migrations. Try --initial.") |
||
85 | # Make sure it has stored models
|
||
86 | if migrations.app_label() not in getattr(last_migration.migration_class(), "complete_apps", []): |
||
87 | self.error("You cannot use automatic detection, since the previous migration does not have this whole app frozen.\nEither make migrations using '--freeze %s' or set 'SOUTH_AUTO_FREEZE_APP = True' in your settings.py." % migrations.app_label()) |
||
88 | # Alright, construct two model dicts to run the differ on.
|
||
89 | old_defs = dict(
|
||
90 | (k, v) for k, v in last_migration.migration_class().models.items() |
||
91 | if k.split(".")[0] == migrations.app_label() |
||
92 | ) |
||
93 | new_defs = dict(
|
||
94 | (k, v) for k, v in freezer.freeze_apps([migrations.app_label()]).items() |
||
95 | if k.split(".")[0] == migrations.app_label() |
||
96 | ) |
||
97 | change_source = changes.AutoChanges( |
||
98 | migrations = migrations, |
||
99 | old_defs = old_defs, |
||
100 | old_orm = last_migration.orm(), |
||
101 | new_defs = new_defs, |
||
102 | ) |
||
103 | |||
104 | elif initial:
|
||
105 | # Do an initial migration
|
||
106 | change_source = changes.InitialChanges(migrations) |
||
107 | |||
108 | else:
|
||
109 | # Read the commands manually off of the arguments
|
||
110 | if (added_model_list or added_field_list or added_index_list): |
||
111 | change_source = changes.ManualChanges( |
||
112 | migrations, |
||
113 | added_model_list, |
||
114 | added_field_list, |
||
115 | added_index_list, |
||
116 | ) |
||
117 | elif empty:
|
||
118 | change_source = None
|
||
119 | else:
|
||
120 | print >>sys.stderr, "You have not passed any of --initial, --auto, --empty, --add-model, --add-field or --add-index." |
||
121 | sys.exit(1)
|
||
122 | |||
123 | # Validate this so we can access the last migration without worrying
|
||
124 | if update and not migrations: |
||
125 | self.error("You cannot use --update on an app with no migrations.") |
||
126 | |||
127 | # if not name, there's an error
|
||
128 | if not name: |
||
129 | if change_source:
|
||
130 | name = change_source.suggest_name() |
||
131 | if update:
|
||
132 | name = re.sub(r'^\d{4}_', '', migrations[-1].name()) |
||
133 | if not name: |
||
134 | self.error("You must provide a name for this migration\n" + self.usage_str) |
||
135 | |||
136 | # Get the actions, and then insert them into the actions lists
|
||
137 | forwards_actions = [] |
||
138 | backwards_actions = [] |
||
139 | if change_source:
|
||
140 | for action_name, params in change_source.get_changes(): |
||
141 | # Run the correct Action class
|
||
142 | try:
|
||
143 | action_class = getattr(actions, action_name)
|
||
144 | except AttributeError: |
||
145 | raise ValueError("Invalid action name from source: %s" % action_name) |
||
146 | else:
|
||
147 | action = action_class(**params) |
||
148 | action.add_forwards(forwards_actions) |
||
149 | action.add_backwards(backwards_actions) |
||
150 | print >>sys.stderr, action.console_line()
|
||
151 | |||
152 | # Nowt happen? That's not good for --auto.
|
||
153 | if auto and not forwards_actions: |
||
154 | self.error("Nothing seems to have changed.") |
||
155 | |||
156 | # Work out which apps to freeze
|
||
157 | apps_to_freeze = self.calc_frozen_apps(migrations, freeze_list)
|
||
158 | |||
159 | # So, what's in this file, then?
|
||
160 | file_contents = MIGRATION_TEMPLATE % { |
||
161 | "forwards": "\n".join(forwards_actions or [" pass"]), |
||
162 | "backwards": "\n".join(backwards_actions or [" pass"]), |
||
163 | "frozen_models": freezer.freeze_apps_to_string(apps_to_freeze),
|
||
164 | "complete_apps": apps_to_freeze and "complete_apps = [%s]" % (", ".join(map(repr, apps_to_freeze))) or "" |
||
165 | } |
||
166 | |||
167 | # Deal with update mode as late as possible, avoid a rollback as long
|
||
168 | # as something else can go wrong.
|
||
169 | if update:
|
||
170 | last_migration = migrations[-1]
|
||
171 | if MigrationHistory.objects.filter(applied__isnull=False, app_name=app, migration=last_migration.name()): |
||
172 | print >>sys.stderr, "Migration to be updated, %s, is already applied, rolling it back now..." % last_migration.name() |
||
173 | migrate_app(migrations, 'current-1', verbosity=verbosity)
|
||
174 | for ext in ('py', 'pyc'): |
||
175 | old_filename = "%s.%s" % (os.path.join(migrations.migrations_dir(), last_migration.filename), ext)
|
||
176 | if os.path.isfile(old_filename):
|
||
177 | os.unlink(old_filename) |
||
178 | migrations.remove(last_migration) |
||
179 | |||
180 | # See what filename is next in line. We assume they use numbers.
|
||
181 | new_filename = migrations.next_filename(name) |
||
182 | |||
183 | # - is a special name which means 'print to stdout'
|
||
184 | if name == "-": |
||
185 | print file_contents
|
||
186 | # Write the migration file if the name isn't -
|
||
187 | else:
|
||
188 | fp = open(os.path.join(migrations.migrations_dir(), new_filename), "w") |
||
189 | fp.write(file_contents) |
||
190 | fp.close() |
||
191 | verb = 'Updated' if update else 'Created' |
||
192 | if empty:
|
||
193 | print >>sys.stderr, "%s %s. You must now edit this migration and add the code for each direction." % (verb, new_filename) |
||
194 | else:
|
||
195 | print >>sys.stderr, "%s %s. You can now apply this migration with: ./manage.py migrate %s" % (verb, new_filename, app) |
||
196 | |||
197 | |||
198 | MIGRATION_TEMPLATE = """# -*- coding: utf-8 -*-
|
||
199 | import datetime
|
||
200 | from south.db import db
|
||
201 | from south.v2 import SchemaMigration
|
||
202 | from django.db import models
|
||
203 |
|
||
204 |
|
||
205 | class Migration(SchemaMigration):
|
||
206 |
|
||
207 | def forwards(self, orm):
|
||
208 | %(forwards)s
|
||
209 |
|
||
210 | def backwards(self, orm):
|
||
211 | %(backwards)s
|
||
212 |
|
||
213 | models = %(frozen_models)s
|
||
214 |
|
||
215 | %(complete_apps)s""" |