root / env / lib / python2.7 / site-packages / south / management / commands / schemamigration.py @ d1a4905f
History | View | Annotate | Download (9.76 KB)
1 |
"""
|
---|---|
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"""
|