Revision d1a4905f
added south for database migration
crm/crm/settings.py | ||
---|---|---|
11 | 11 |
|
12 | 12 |
DATABASES = { |
13 | 13 |
'default': { |
14 |
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. |
|
15 |
'NAME': '', # Or path to database file if using sqlite3. |
|
16 |
'USER': '', # Not used with sqlite3. |
|
17 |
'PASSWORD': '', # Not used with sqlite3. |
|
14 |
'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
|
|
15 |
'NAME': 'crm', # Or path to database file if using sqlite3.
|
|
16 |
'USER': 'crmuser', # Not used with sqlite3.
|
|
17 |
'PASSWORD': 'baseball', # Not used with sqlite3.
|
|
18 | 18 |
'HOST': '', # Set to empty string for localhost. Not used with sqlite3. |
19 | 19 |
'PORT': '', # Set to empty string for default. Not used with sqlite3. |
20 | 20 |
} |
... | ... | |
24 | 24 |
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name |
25 | 25 |
# although not all choices may be available on all operating systems. |
26 | 26 |
# In a Windows environment this must be set to your system time zone. |
27 |
TIME_ZONE = 'America/Chicago'
|
|
27 |
TIME_ZONE = 'America/New_York'
|
|
28 | 28 |
|
29 | 29 |
# Language code for this installation. All choices can be found here: |
30 | 30 |
# http://www.i18nguy.com/unicode/language-identifiers.html |
... | ... | |
115 | 115 |
'django.contrib.sites', |
116 | 116 |
'django.contrib.messages', |
117 | 117 |
'django.contrib.staticfiles', |
118 |
'south', |
|
118 | 119 |
# Uncomment the next line to enable the admin: |
119 | 120 |
# 'django.contrib.admin', |
120 | 121 |
# Uncomment the next line to enable admin documentation: |
env/lib/python2.7/site-packages/South-0.7.6-py2.7.egg-info/PKG-INFO | ||
---|---|---|
1 |
Metadata-Version: 1.0 |
|
2 |
Name: South |
|
3 |
Version: 0.7.6 |
|
4 |
Summary: South: Migrations for Django |
|
5 |
Home-page: http://south.aeracode.org/ |
|
6 |
Author: Andrew Godwin & Andy McCurdy |
|
7 |
Author-email: south@aeracode.org |
|
8 |
License: UNKNOWN |
|
9 |
Download-URL: http://south.aeracode.org/wiki/Download |
|
10 |
Description: South is an intelligent database migrations library for the Django web framework. It is database-independent and DVCS-friendly, as well as a whole host of other features. |
|
11 |
Platform: UNKNOWN |
|
12 |
Classifier: Development Status :: 5 - Production/Stable |
|
13 |
Classifier: Framework :: Django |
|
14 |
Classifier: Intended Audience :: Developers |
|
15 |
Classifier: Intended Audience :: System Administrators |
|
16 |
Classifier: Intended Audience :: System Administrators |
|
17 |
Classifier: License :: OSI Approved :: Apache Software License |
|
18 |
Classifier: Operating System :: OS Independent |
|
19 |
Classifier: Topic :: Software Development |
env/lib/python2.7/site-packages/South-0.7.6-py2.7.egg-info/SOURCES.txt | ||
---|---|---|
1 |
README |
|
2 |
setup.cfg |
|
3 |
setup.py |
|
4 |
South.egg-info/PKG-INFO |
|
5 |
South.egg-info/SOURCES.txt |
|
6 |
South.egg-info/dependency_links.txt |
|
7 |
South.egg-info/top_level.txt |
|
8 |
south/__init__.py |
|
9 |
south/exceptions.py |
|
10 |
south/logger.py |
|
11 |
south/models.py |
|
12 |
south/modelsinspector.py |
|
13 |
south/orm.py |
|
14 |
south/signals.py |
|
15 |
south/v2.py |
|
16 |
south/creator/__init__.py |
|
17 |
south/creator/actions.py |
|
18 |
south/creator/changes.py |
|
19 |
south/creator/freezer.py |
|
20 |
south/db/__init__.py |
|
21 |
south/db/firebird.py |
|
22 |
south/db/generic.py |
|
23 |
south/db/mysql.py |
|
24 |
south/db/oracle.py |
|
25 |
south/db/postgresql_psycopg2.py |
|
26 |
south/db/sqlite3.py |
|
27 |
south/db/sql_server/__init__.py |
|
28 |
south/db/sql_server/pyodbc.py |
|
29 |
south/hacks/__init__.py |
|
30 |
south/hacks/django_1_0.py |
|
31 |
south/introspection_plugins/__init__.py |
|
32 |
south/introspection_plugins/annoying_autoonetoone.py |
|
33 |
south/introspection_plugins/django_audit_log.py |
|
34 |
south/introspection_plugins/django_objectpermissions.py |
|
35 |
south/introspection_plugins/django_tagging.py |
|
36 |
south/introspection_plugins/django_taggit.py |
|
37 |
south/introspection_plugins/django_timezones.py |
|
38 |
south/introspection_plugins/geodjango.py |
|
39 |
south/management/__init__.py |
|
40 |
south/management/commands/__init__.py |
|
41 |
south/management/commands/convert_to_south.py |
|
42 |
south/management/commands/datamigration.py |
|
43 |
south/management/commands/graphmigrations.py |
|
44 |
south/management/commands/migrate.py |
|
45 |
south/management/commands/migrationcheck.py |
|
46 |
south/management/commands/schemamigration.py |
|
47 |
south/management/commands/startmigration.py |
|
48 |
south/management/commands/syncdb.py |
|
49 |
south/management/commands/test.py |
|
50 |
south/management/commands/testserver.py |
|
51 |
south/migration/__init__.py |
|
52 |
south/migration/base.py |
|
53 |
south/migration/migrators.py |
|
54 |
south/migration/utils.py |
|
55 |
south/tests/__init__.py |
|
56 |
south/tests/autodetection.py |
|
57 |
south/tests/db.py |
|
58 |
south/tests/db_mysql.py |
|
59 |
south/tests/freezer.py |
|
60 |
south/tests/inspector.py |
|
61 |
south/tests/logger.py |
|
62 |
south/tests/logic.py |
|
63 |
south/tests/brokenapp/__init__.py |
|
64 |
south/tests/brokenapp/models.py |
|
65 |
south/tests/brokenapp/migrations/0001_depends_on_unmigrated.py |
|
66 |
south/tests/brokenapp/migrations/0002_depends_on_unknown.py |
|
67 |
south/tests/brokenapp/migrations/0003_depends_on_higher.py |
|
68 |
south/tests/brokenapp/migrations/0004_higher.py |
|
69 |
south/tests/brokenapp/migrations/__init__.py |
|
70 |
south/tests/circular_a/__init__.py |
|
71 |
south/tests/circular_a/models.py |
|
72 |
south/tests/circular_a/migrations/0001_first.py |
|
73 |
south/tests/circular_a/migrations/__init__.py |
|
74 |
south/tests/circular_b/__init__.py |
|
75 |
south/tests/circular_b/models.py |
|
76 |
south/tests/circular_b/migrations/0001_first.py |
|
77 |
south/tests/circular_b/migrations/__init__.py |
|
78 |
south/tests/deps_a/__init__.py |
|
79 |
south/tests/deps_a/models.py |
|
80 |
south/tests/deps_a/migrations/0001_a.py |
|
81 |
south/tests/deps_a/migrations/0002_a.py |
|
82 |
south/tests/deps_a/migrations/0003_a.py |
|
83 |
south/tests/deps_a/migrations/0004_a.py |
|
84 |
south/tests/deps_a/migrations/0005_a.py |
|
85 |
south/tests/deps_a/migrations/__init__.py |
|
86 |
south/tests/deps_b/__init__.py |
|
87 |
south/tests/deps_b/models.py |
|
88 |
south/tests/deps_b/migrations/0001_b.py |
|
89 |
south/tests/deps_b/migrations/0002_b.py |
|
90 |
south/tests/deps_b/migrations/0003_b.py |
|
91 |
south/tests/deps_b/migrations/0004_b.py |
|
92 |
south/tests/deps_b/migrations/0005_b.py |
|
93 |
south/tests/deps_b/migrations/__init__.py |
|
94 |
south/tests/deps_c/__init__.py |
|
95 |
south/tests/deps_c/models.py |
|
96 |
south/tests/deps_c/migrations/0001_c.py |
|
97 |
south/tests/deps_c/migrations/0002_c.py |
|
98 |
south/tests/deps_c/migrations/0003_c.py |
|
99 |
south/tests/deps_c/migrations/0004_c.py |
|
100 |
south/tests/deps_c/migrations/0005_c.py |
|
101 |
south/tests/deps_c/migrations/__init__.py |
|
102 |
south/tests/emptyapp/__init__.py |
|
103 |
south/tests/emptyapp/models.py |
|
104 |
south/tests/emptyapp/migrations/__init__.py |
|
105 |
south/tests/fakeapp/__init__.py |
|
106 |
south/tests/fakeapp/models.py |
|
107 |
south/tests/fakeapp/migrations/0001_spam.py |
|
108 |
south/tests/fakeapp/migrations/0002_eggs.py |
|
109 |
south/tests/fakeapp/migrations/0003_alter_spam.py |
|
110 |
south/tests/fakeapp/migrations/__init__.py |
|
111 |
south/tests/non_managed/__init__.py |
|
112 |
south/tests/non_managed/models.py |
|
113 |
south/tests/non_managed/migrations/__init__.py |
|
114 |
south/tests/otherfakeapp/__init__.py |
|
115 |
south/tests/otherfakeapp/models.py |
|
116 |
south/tests/otherfakeapp/migrations/0001_first.py |
|
117 |
south/tests/otherfakeapp/migrations/0002_second.py |
|
118 |
south/tests/otherfakeapp/migrations/0003_third.py |
|
119 |
south/tests/otherfakeapp/migrations/__init__.py |
|
120 |
south/utils/__init__.py |
|
121 |
south/utils/datetime_utils.py |
env/lib/python2.7/site-packages/South-0.7.6-py2.7.egg-info/dependency_links.txt | ||
---|---|---|
1 |
|
env/lib/python2.7/site-packages/South-0.7.6-py2.7.egg-info/installed-files.txt | ||
---|---|---|
1 |
../south/orm.py |
|
2 |
../south/exceptions.py |
|
3 |
../south/signals.py |
|
4 |
../south/modelsinspector.py |
|
5 |
../south/logger.py |
|
6 |
../south/__init__.py |
|
7 |
../south/models.py |
|
8 |
../south/v2.py |
|
9 |
../south/creator/freezer.py |
|
10 |
../south/creator/changes.py |
|
11 |
../south/creator/__init__.py |
|
12 |
../south/creator/actions.py |
|
13 |
../south/db/firebird.py |
|
14 |
../south/db/postgresql_psycopg2.py |
|
15 |
../south/db/sqlite3.py |
|
16 |
../south/db/__init__.py |
|
17 |
../south/db/oracle.py |
|
18 |
../south/db/mysql.py |
|
19 |
../south/db/generic.py |
|
20 |
../south/management/__init__.py |
|
21 |
../south/introspection_plugins/geodjango.py |
|
22 |
../south/introspection_plugins/django_timezones.py |
|
23 |
../south/introspection_plugins/annoying_autoonetoone.py |
|
24 |
../south/introspection_plugins/django_audit_log.py |
|
25 |
../south/introspection_plugins/__init__.py |
|
26 |
../south/introspection_plugins/django_tagging.py |
|
27 |
../south/introspection_plugins/django_objectpermissions.py |
|
28 |
../south/introspection_plugins/django_taggit.py |
|
29 |
../south/hacks/__init__.py |
|
30 |
../south/hacks/django_1_0.py |
|
31 |
../south/migration/migrators.py |
|
32 |
../south/migration/utils.py |
|
33 |
../south/migration/base.py |
|
34 |
../south/migration/__init__.py |
|
35 |
../south/tests/freezer.py |
|
36 |
../south/tests/autodetection.py |
|
37 |
../south/tests/logger.py |
|
38 |
../south/tests/logic.py |
|
39 |
../south/tests/inspector.py |
|
40 |
../south/tests/__init__.py |
|
41 |
../south/tests/db.py |
|
42 |
../south/tests/db_mysql.py |
|
43 |
../south/db/sql_server/pyodbc.py |
|
44 |
../south/db/sql_server/__init__.py |
|
45 |
../south/management/commands/syncdb.py |
|
46 |
../south/management/commands/graphmigrations.py |
|
47 |
../south/management/commands/schemamigration.py |
|
48 |
../south/management/commands/test.py |
|
49 |
../south/management/commands/migrationcheck.py |
|
50 |
../south/management/commands/testserver.py |
|
51 |
../south/management/commands/convert_to_south.py |
|
52 |
../south/management/commands/datamigration.py |
|
53 |
../south/management/commands/__init__.py |
|
54 |
../south/management/commands/migrate.py |
|
55 |
../south/management/commands/startmigration.py |
|
56 |
../south/tests/circular_a/__init__.py |
|
57 |
../south/tests/circular_a/models.py |
|
58 |
../south/tests/emptyapp/__init__.py |
|
59 |
../south/tests/emptyapp/models.py |
|
60 |
../south/tests/deps_a/__init__.py |
|
61 |
../south/tests/deps_a/models.py |
|
62 |
../south/tests/fakeapp/__init__.py |
|
63 |
../south/tests/fakeapp/models.py |
|
64 |
../south/tests/brokenapp/__init__.py |
|
65 |
../south/tests/brokenapp/models.py |
|
66 |
../south/tests/circular_b/__init__.py |
|
67 |
../south/tests/circular_b/models.py |
|
68 |
../south/tests/otherfakeapp/__init__.py |
|
69 |
../south/tests/otherfakeapp/models.py |
|
70 |
../south/tests/deps_c/__init__.py |
|
71 |
../south/tests/deps_c/models.py |
|
72 |
../south/tests/deps_b/__init__.py |
|
73 |
../south/tests/deps_b/models.py |
|
74 |
../south/tests/non_managed/__init__.py |
|
75 |
../south/tests/non_managed/models.py |
|
76 |
../south/tests/circular_a/migrations/__init__.py |
|
77 |
../south/tests/circular_a/migrations/0001_first.py |
|
78 |
../south/tests/emptyapp/migrations/__init__.py |
|
79 |
../south/tests/deps_a/migrations/0002_a.py |
|
80 |
../south/tests/deps_a/migrations/0005_a.py |
|
81 |
../south/tests/deps_a/migrations/__init__.py |
|
82 |
../south/tests/deps_a/migrations/0004_a.py |
|
83 |
../south/tests/deps_a/migrations/0001_a.py |
|
84 |
../south/tests/deps_a/migrations/0003_a.py |
|
85 |
../south/tests/fakeapp/migrations/0002_eggs.py |
|
86 |
../south/tests/fakeapp/migrations/__init__.py |
|
87 |
../south/tests/fakeapp/migrations/0003_alter_spam.py |
|
88 |
../south/tests/fakeapp/migrations/0001_spam.py |
|
89 |
../south/tests/brokenapp/migrations/0004_higher.py |
|
90 |
../south/tests/brokenapp/migrations/0002_depends_on_unknown.py |
|
91 |
../south/tests/brokenapp/migrations/__init__.py |
|
92 |
../south/tests/brokenapp/migrations/0001_depends_on_unmigrated.py |
|
93 |
../south/tests/brokenapp/migrations/0003_depends_on_higher.py |
|
94 |
../south/tests/circular_b/migrations/__init__.py |
|
95 |
../south/tests/circular_b/migrations/0001_first.py |
|
96 |
../south/tests/otherfakeapp/migrations/0002_second.py |
|
97 |
../south/tests/otherfakeapp/migrations/__init__.py |
|
98 |
../south/tests/otherfakeapp/migrations/0003_third.py |
|
99 |
../south/tests/otherfakeapp/migrations/0001_first.py |
|
100 |
../south/tests/deps_c/migrations/0003_c.py |
|
101 |
../south/tests/deps_c/migrations/0005_c.py |
|
102 |
../south/tests/deps_c/migrations/__init__.py |
|
103 |
../south/tests/deps_c/migrations/0002_c.py |
|
104 |
../south/tests/deps_c/migrations/0001_c.py |
|
105 |
../south/tests/deps_c/migrations/0004_c.py |
|
106 |
../south/tests/deps_b/migrations/0005_b.py |
|
107 |
../south/tests/deps_b/migrations/0002_b.py |
|
108 |
../south/tests/deps_b/migrations/__init__.py |
|
109 |
../south/tests/deps_b/migrations/0004_b.py |
|
110 |
../south/tests/deps_b/migrations/0003_b.py |
|
111 |
../south/tests/deps_b/migrations/0001_b.py |
|
112 |
../south/tests/non_managed/migrations/__init__.py |
|
113 |
../south/utils/__init__.py |
|
114 |
../south/utils/datetime_utils.py |
|
115 |
../south/orm.pyc |
|
116 |
../south/exceptions.pyc |
|
117 |
../south/signals.pyc |
|
118 |
../south/modelsinspector.pyc |
|
119 |
../south/logger.pyc |
|
120 |
../south/__init__.pyc |
|
121 |
../south/models.pyc |
|
122 |
../south/v2.pyc |
|
123 |
../south/creator/freezer.pyc |
|
124 |
../south/creator/changes.pyc |
|
125 |
../south/creator/__init__.pyc |
|
126 |
../south/creator/actions.pyc |
|
127 |
../south/db/firebird.pyc |
|
128 |
../south/db/postgresql_psycopg2.pyc |
|
129 |
../south/db/sqlite3.pyc |
|
130 |
../south/db/__init__.pyc |
|
131 |
../south/db/oracle.pyc |
|
132 |
../south/db/mysql.pyc |
|
133 |
../south/db/generic.pyc |
|
134 |
../south/management/__init__.pyc |
|
135 |
../south/introspection_plugins/geodjango.pyc |
|
136 |
../south/introspection_plugins/django_timezones.pyc |
|
137 |
../south/introspection_plugins/annoying_autoonetoone.pyc |
|
138 |
../south/introspection_plugins/django_audit_log.pyc |
|
139 |
../south/introspection_plugins/__init__.pyc |
|
140 |
../south/introspection_plugins/django_tagging.pyc |
|
141 |
../south/introspection_plugins/django_objectpermissions.pyc |
|
142 |
../south/introspection_plugins/django_taggit.pyc |
|
143 |
../south/hacks/__init__.pyc |
|
144 |
../south/hacks/django_1_0.pyc |
|
145 |
../south/migration/migrators.pyc |
|
146 |
../south/migration/utils.pyc |
|
147 |
../south/migration/base.pyc |
|
148 |
../south/migration/__init__.pyc |
|
149 |
../south/tests/freezer.pyc |
|
150 |
../south/tests/autodetection.pyc |
|
151 |
../south/tests/logger.pyc |
|
152 |
../south/tests/logic.pyc |
|
153 |
../south/tests/inspector.pyc |
|
154 |
../south/tests/__init__.pyc |
|
155 |
../south/tests/db.pyc |
|
156 |
../south/tests/db_mysql.pyc |
|
157 |
../south/db/sql_server/pyodbc.pyc |
|
158 |
../south/db/sql_server/__init__.pyc |
|
159 |
../south/management/commands/syncdb.pyc |
|
160 |
../south/management/commands/graphmigrations.pyc |
|
161 |
../south/management/commands/schemamigration.pyc |
|
162 |
../south/management/commands/test.pyc |
|
163 |
../south/management/commands/migrationcheck.pyc |
|
164 |
../south/management/commands/testserver.pyc |
|
165 |
../south/management/commands/convert_to_south.pyc |
|
166 |
../south/management/commands/datamigration.pyc |
|
167 |
../south/management/commands/__init__.pyc |
|
168 |
../south/management/commands/migrate.pyc |
|
169 |
../south/management/commands/startmigration.pyc |
|
170 |
../south/tests/circular_a/__init__.pyc |
|
171 |
../south/tests/circular_a/models.pyc |
|
172 |
../south/tests/emptyapp/__init__.pyc |
|
173 |
../south/tests/emptyapp/models.pyc |
|
174 |
../south/tests/deps_a/__init__.pyc |
|
175 |
../south/tests/deps_a/models.pyc |
|
176 |
../south/tests/fakeapp/__init__.pyc |
|
177 |
../south/tests/fakeapp/models.pyc |
|
178 |
../south/tests/brokenapp/__init__.pyc |
|
179 |
../south/tests/brokenapp/models.pyc |
|
180 |
../south/tests/circular_b/__init__.pyc |
|
181 |
../south/tests/circular_b/models.pyc |
|
182 |
../south/tests/otherfakeapp/__init__.pyc |
|
183 |
../south/tests/otherfakeapp/models.pyc |
|
184 |
../south/tests/deps_c/__init__.pyc |
|
185 |
../south/tests/deps_c/models.pyc |
|
186 |
../south/tests/deps_b/__init__.pyc |
|
187 |
../south/tests/deps_b/models.pyc |
|
188 |
../south/tests/non_managed/__init__.pyc |
|
189 |
../south/tests/non_managed/models.pyc |
|
190 |
../south/tests/circular_a/migrations/__init__.pyc |
|
191 |
../south/tests/circular_a/migrations/0001_first.pyc |
|
192 |
../south/tests/emptyapp/migrations/__init__.pyc |
|
193 |
../south/tests/deps_a/migrations/0002_a.pyc |
|
194 |
../south/tests/deps_a/migrations/0005_a.pyc |
|
195 |
../south/tests/deps_a/migrations/__init__.pyc |
|
196 |
../south/tests/deps_a/migrations/0004_a.pyc |
|
197 |
../south/tests/deps_a/migrations/0001_a.pyc |
|
198 |
../south/tests/deps_a/migrations/0003_a.pyc |
|
199 |
../south/tests/fakeapp/migrations/0002_eggs.pyc |
|
200 |
../south/tests/fakeapp/migrations/__init__.pyc |
|
201 |
../south/tests/fakeapp/migrations/0003_alter_spam.pyc |
|
202 |
../south/tests/fakeapp/migrations/0001_spam.pyc |
|
203 |
../south/tests/brokenapp/migrations/0004_higher.pyc |
|
204 |
../south/tests/brokenapp/migrations/0002_depends_on_unknown.pyc |
|
205 |
../south/tests/brokenapp/migrations/__init__.pyc |
|
206 |
../south/tests/brokenapp/migrations/0001_depends_on_unmigrated.pyc |
|
207 |
../south/tests/brokenapp/migrations/0003_depends_on_higher.pyc |
|
208 |
../south/tests/circular_b/migrations/__init__.pyc |
|
209 |
../south/tests/circular_b/migrations/0001_first.pyc |
|
210 |
../south/tests/otherfakeapp/migrations/0002_second.pyc |
|
211 |
../south/tests/otherfakeapp/migrations/__init__.pyc |
|
212 |
../south/tests/otherfakeapp/migrations/0003_third.pyc |
|
213 |
../south/tests/otherfakeapp/migrations/0001_first.pyc |
|
214 |
../south/tests/deps_c/migrations/0003_c.pyc |
|
215 |
../south/tests/deps_c/migrations/0005_c.pyc |
|
216 |
../south/tests/deps_c/migrations/__init__.pyc |
|
217 |
../south/tests/deps_c/migrations/0002_c.pyc |
|
218 |
../south/tests/deps_c/migrations/0001_c.pyc |
|
219 |
../south/tests/deps_c/migrations/0004_c.pyc |
|
220 |
../south/tests/deps_b/migrations/0005_b.pyc |
|
221 |
../south/tests/deps_b/migrations/0002_b.pyc |
|
222 |
../south/tests/deps_b/migrations/__init__.pyc |
|
223 |
../south/tests/deps_b/migrations/0004_b.pyc |
|
224 |
../south/tests/deps_b/migrations/0003_b.pyc |
|
225 |
../south/tests/deps_b/migrations/0001_b.pyc |
|
226 |
../south/tests/non_managed/migrations/__init__.pyc |
|
227 |
../south/utils/__init__.pyc |
|
228 |
../south/utils/datetime_utils.pyc |
|
229 |
./ |
|
230 |
top_level.txt |
|
231 |
SOURCES.txt |
|
232 |
PKG-INFO |
|
233 |
dependency_links.txt |
env/lib/python2.7/site-packages/South-0.7.6-py2.7.egg-info/top_level.txt | ||
---|---|---|
1 |
south |
env/lib/python2.7/site-packages/south/__init__.py | ||
---|---|---|
1 |
""" |
|
2 |
South - Useable migrations for Django apps |
|
3 |
""" |
|
4 |
|
|
5 |
__version__ = "0.7.6" |
|
6 |
__authors__ = [ |
|
7 |
"Andrew Godwin <andrew@aeracode.org>", |
|
8 |
"Andy McCurdy <andy@andymccurdy.com>" |
|
9 |
] |
env/lib/python2.7/site-packages/south/creator/__init__.py | ||
---|---|---|
1 |
""" |
|
2 |
The creator module is responsible for making new migration files, either |
|
3 |
as blank templates or autodetecting changes. It contains code that used to |
|
4 |
all be in startmigration.py. |
|
5 |
""" |
env/lib/python2.7/site-packages/south/creator/actions.py | ||
---|---|---|
1 |
""" |
|
2 |
Actions - things like 'a model was removed' or 'a field was changed'. |
|
3 |
Each one has a class, which can take the action description and insert code |
|
4 |
blocks into the forwards() and backwards() methods, in the right place. |
|
5 |
""" |
|
6 |
|
|
7 |
import sys |
|
8 |
|
|
9 |
from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT |
|
10 |
from django.db.models.fields import FieldDoesNotExist, NOT_PROVIDED, CharField, TextField |
|
11 |
|
|
12 |
from south.modelsinspector import value_clean |
|
13 |
from south.creator.freezer import remove_useless_attributes, model_key |
|
14 |
from south.utils import datetime_utils |
|
15 |
|
|
16 |
|
|
17 |
class Action(object): |
|
18 |
""" |
|
19 |
Generic base Action class. Contains utility methods for inserting into |
|
20 |
the forwards() and backwards() method lists. |
|
21 |
""" |
|
22 |
|
|
23 |
prepend_forwards = False |
|
24 |
prepend_backwards = False |
|
25 |
|
|
26 |
def forwards_code(self): |
|
27 |
raise NotImplementedError |
|
28 |
|
|
29 |
def backwards_code(self): |
|
30 |
raise NotImplementedError |
|
31 |
|
|
32 |
def add_forwards(self, forwards): |
|
33 |
if self.prepend_forwards: |
|
34 |
forwards.insert(0, self.forwards_code()) |
|
35 |
else: |
|
36 |
forwards.append(self.forwards_code()) |
|
37 |
|
|
38 |
def add_backwards(self, backwards): |
|
39 |
if self.prepend_backwards: |
|
40 |
backwards.insert(0, self.backwards_code()) |
|
41 |
else: |
|
42 |
backwards.append(self.backwards_code()) |
|
43 |
|
|
44 |
def console_line(self): |
|
45 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
46 |
raise NotImplementedError |
|
47 |
|
|
48 |
@classmethod |
|
49 |
def triples_to_defs(cls, fields): |
|
50 |
# Turn the (class, args, kwargs) format into a string |
|
51 |
for field, triple in fields.items(): |
|
52 |
fields[field] = cls.triple_to_def(triple) |
|
53 |
return fields |
|
54 |
|
|
55 |
@classmethod |
|
56 |
def triple_to_def(cls, triple): |
|
57 |
"Turns a single triple into a definition." |
|
58 |
return "self.gf(%r)(%s)" % ( |
|
59 |
triple[0], # Field full path |
|
60 |
", ".join(triple[1] + ["%s=%s" % (kwd, val) for kwd, val in triple[2].items()]), # args and kwds |
|
61 |
) |
|
62 |
|
|
63 |
|
|
64 |
class AddModel(Action): |
|
65 |
""" |
|
66 |
Addition of a model. Takes the Model subclass that is being created. |
|
67 |
""" |
|
68 |
|
|
69 |
FORWARDS_TEMPLATE = ''' |
|
70 |
# Adding model '%(model_name)s' |
|
71 |
db.create_table(%(table_name)r, ( |
|
72 |
%(field_defs)s |
|
73 |
)) |
|
74 |
db.send_create_signal(%(app_label)r, [%(model_name)r])'''[1:] + "\n" |
|
75 |
|
|
76 |
BACKWARDS_TEMPLATE = ''' |
|
77 |
# Deleting model '%(model_name)s' |
|
78 |
db.delete_table(%(table_name)r)'''[1:] + "\n" |
|
79 |
|
|
80 |
def __init__(self, model, model_def): |
|
81 |
self.model = model |
|
82 |
self.model_def = model_def |
|
83 |
|
|
84 |
def console_line(self): |
|
85 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
86 |
return " + Added model %s.%s" % ( |
|
87 |
self.model._meta.app_label, |
|
88 |
self.model._meta.object_name, |
|
89 |
) |
|
90 |
|
|
91 |
def forwards_code(self): |
|
92 |
"Produces the code snippet that gets put into forwards()" |
|
93 |
field_defs = ",\n ".join([ |
|
94 |
"(%r, %s)" % (name, defn) for name, defn |
|
95 |
in self.triples_to_defs(self.model_def).items() |
|
96 |
]) + "," |
|
97 |
|
|
98 |
return self.FORWARDS_TEMPLATE % { |
|
99 |
"model_name": self.model._meta.object_name, |
|
100 |
"table_name": self.model._meta.db_table, |
|
101 |
"app_label": self.model._meta.app_label, |
|
102 |
"field_defs": field_defs, |
|
103 |
} |
|
104 |
|
|
105 |
def backwards_code(self): |
|
106 |
"Produces the code snippet that gets put into backwards()" |
|
107 |
return self.BACKWARDS_TEMPLATE % { |
|
108 |
"model_name": self.model._meta.object_name, |
|
109 |
"table_name": self.model._meta.db_table, |
|
110 |
} |
|
111 |
|
|
112 |
|
|
113 |
class DeleteModel(AddModel): |
|
114 |
""" |
|
115 |
Deletion of a model. Takes the Model subclass that is being created. |
|
116 |
""" |
|
117 |
|
|
118 |
def console_line(self): |
|
119 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
120 |
return " - Deleted model %s.%s" % ( |
|
121 |
self.model._meta.app_label, |
|
122 |
self.model._meta.object_name, |
|
123 |
) |
|
124 |
|
|
125 |
def forwards_code(self): |
|
126 |
return AddModel.backwards_code(self) |
|
127 |
|
|
128 |
def backwards_code(self): |
|
129 |
return AddModel.forwards_code(self) |
|
130 |
|
|
131 |
|
|
132 |
class _NullIssuesField(object): |
|
133 |
""" |
|
134 |
A field that might need to ask a question about rogue NULL values. |
|
135 |
""" |
|
136 |
|
|
137 |
allow_third_null_option = False |
|
138 |
irreversible = False |
|
139 |
|
|
140 |
IRREVERSIBLE_TEMPLATE = ''' |
|
141 |
# User chose to not deal with backwards NULL issues for '%(model_name)s.%(field_name)s' |
|
142 |
raise RuntimeError("Cannot reverse this migration. '%(model_name)s.%(field_name)s' and its values cannot be restored.")''' |
|
143 |
|
|
144 |
def deal_with_not_null_no_default(self, field, field_def): |
|
145 |
# If it's a CharField or TextField that's blank, skip this step. |
|
146 |
if isinstance(field, (CharField, TextField)) and field.blank: |
|
147 |
field_def[2]['default'] = repr("") |
|
148 |
return |
|
149 |
# Oh dear. Ask them what to do. |
|
150 |
print " ? The field '%s.%s' does not have a default specified, yet is NOT NULL." % ( |
|
151 |
self.model._meta.object_name, |
|
152 |
field.name, |
|
153 |
) |
|
154 |
print " ? Since you are %s, you MUST specify a default" % self.null_reason |
|
155 |
print " ? value to use for existing rows. Would you like to:" |
|
156 |
print " ? 1. Quit now, and add a default to the field in models.py" |
|
157 |
print " ? 2. Specify a one-off value to use for existing columns now" |
|
158 |
if self.allow_third_null_option: |
|
159 |
print " ? 3. Disable the backwards migration by raising an exception." |
|
160 |
while True: |
|
161 |
choice = raw_input(" ? Please select a choice: ") |
|
162 |
if choice == "1": |
|
163 |
sys.exit(1) |
|
164 |
elif choice == "2": |
|
165 |
break |
|
166 |
elif choice == "3" and self.allow_third_null_option: |
|
167 |
break |
|
168 |
else: |
|
169 |
print " ! Invalid choice." |
|
170 |
if choice == "2": |
|
171 |
self.add_one_time_default(field, field_def) |
|
172 |
elif choice == "3": |
|
173 |
self.irreversible = True |
|
174 |
|
|
175 |
def add_one_time_default(self, field, field_def): |
|
176 |
# OK, they want to pick their own one-time default. Who are we to refuse? |
|
177 |
print " ? Please enter Python code for your one-off default value." |
|
178 |
print " ? The datetime module is available, so you can do e.g. datetime.date.today()" |
|
179 |
while True: |
|
180 |
code = raw_input(" >>> ") |
|
181 |
if not code: |
|
182 |
print " ! Please enter some code, or 'exit' (with no quotes) to exit." |
|
183 |
elif code == "exit": |
|
184 |
sys.exit(1) |
|
185 |
else: |
|
186 |
try: |
|
187 |
result = eval(code, {}, {"datetime": datetime_utils}) |
|
188 |
except (SyntaxError, NameError), e: |
|
189 |
print " ! Invalid input: %s" % e |
|
190 |
else: |
|
191 |
break |
|
192 |
# Right, add the default in. |
|
193 |
field_def[2]['default'] = value_clean(result) |
|
194 |
|
|
195 |
def irreversable_code(self, field): |
|
196 |
return self.IRREVERSIBLE_TEMPLATE % { |
|
197 |
"model_name": self.model._meta.object_name, |
|
198 |
"table_name": self.model._meta.db_table, |
|
199 |
"field_name": field.name, |
|
200 |
"field_column": field.column, |
|
201 |
} |
|
202 |
|
|
203 |
|
|
204 |
class AddField(Action, _NullIssuesField): |
|
205 |
""" |
|
206 |
Adds a field to a model. Takes a Model class and the field name. |
|
207 |
""" |
|
208 |
|
|
209 |
null_reason = "adding this field" |
|
210 |
|
|
211 |
FORWARDS_TEMPLATE = ''' |
|
212 |
# Adding field '%(model_name)s.%(field_name)s' |
|
213 |
db.add_column(%(table_name)r, %(field_name)r, |
|
214 |
%(field_def)s, |
|
215 |
keep_default=False)'''[1:] + "\n" |
|
216 |
|
|
217 |
BACKWARDS_TEMPLATE = ''' |
|
218 |
# Deleting field '%(model_name)s.%(field_name)s' |
|
219 |
db.delete_column(%(table_name)r, %(field_column)r)'''[1:] + "\n" |
|
220 |
|
|
221 |
def __init__(self, model, field, field_def): |
|
222 |
self.model = model |
|
223 |
self.field = field |
|
224 |
self.field_def = field_def |
|
225 |
|
|
226 |
# See if they've made a NOT NULL column but also have no default (far too common) |
|
227 |
is_null = self.field.null |
|
228 |
default = (self.field.default is not None) and (self.field.default is not NOT_PROVIDED) |
|
229 |
|
|
230 |
if not is_null and not default: |
|
231 |
self.deal_with_not_null_no_default(self.field, self.field_def) |
|
232 |
|
|
233 |
def console_line(self): |
|
234 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
235 |
return " + Added field %s on %s.%s" % ( |
|
236 |
self.field.name, |
|
237 |
self.model._meta.app_label, |
|
238 |
self.model._meta.object_name, |
|
239 |
) |
|
240 |
|
|
241 |
def forwards_code(self): |
|
242 |
|
|
243 |
return self.FORWARDS_TEMPLATE % { |
|
244 |
"model_name": self.model._meta.object_name, |
|
245 |
"table_name": self.model._meta.db_table, |
|
246 |
"field_name": self.field.name, |
|
247 |
"field_column": self.field.column, |
|
248 |
"field_def": self.triple_to_def(self.field_def), |
|
249 |
} |
|
250 |
|
|
251 |
def backwards_code(self): |
|
252 |
return self.BACKWARDS_TEMPLATE % { |
|
253 |
"model_name": self.model._meta.object_name, |
|
254 |
"table_name": self.model._meta.db_table, |
|
255 |
"field_name": self.field.name, |
|
256 |
"field_column": self.field.column, |
|
257 |
} |
|
258 |
|
|
259 |
|
|
260 |
class DeleteField(AddField): |
|
261 |
""" |
|
262 |
Removes a field from a model. Takes a Model class and the field name. |
|
263 |
""" |
|
264 |
|
|
265 |
null_reason = "removing this field" |
|
266 |
allow_third_null_option = True |
|
267 |
|
|
268 |
def console_line(self): |
|
269 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
270 |
return " - Deleted field %s on %s.%s" % ( |
|
271 |
self.field.name, |
|
272 |
self.model._meta.app_label, |
|
273 |
self.model._meta.object_name, |
|
274 |
) |
|
275 |
|
|
276 |
def forwards_code(self): |
|
277 |
return AddField.backwards_code(self) |
|
278 |
|
|
279 |
def backwards_code(self): |
|
280 |
if not self.irreversible: |
|
281 |
return AddField.forwards_code(self) |
|
282 |
else: |
|
283 |
return self.irreversable_code(self.field) |
|
284 |
|
|
285 |
|
|
286 |
class ChangeField(Action, _NullIssuesField): |
|
287 |
""" |
|
288 |
Changes a field's type/options on a model. |
|
289 |
""" |
|
290 |
|
|
291 |
null_reason = "making this field non-nullable" |
|
292 |
|
|
293 |
FORWARDS_TEMPLATE = BACKWARDS_TEMPLATE = ''' |
|
294 |
# Changing field '%(model_name)s.%(field_name)s' |
|
295 |
db.alter_column(%(table_name)r, %(field_column)r, %(field_def)s)''' |
|
296 |
|
|
297 |
RENAME_TEMPLATE = ''' |
|
298 |
# Renaming column for '%(model_name)s.%(field_name)s' to match new field type. |
|
299 |
db.rename_column(%(table_name)r, %(old_column)r, %(new_column)r)''' |
|
300 |
|
|
301 |
def __init__(self, model, old_field, new_field, old_def, new_def): |
|
302 |
self.model = model |
|
303 |
self.old_field = old_field |
|
304 |
self.new_field = new_field |
|
305 |
self.old_def = old_def |
|
306 |
self.new_def = new_def |
|
307 |
|
|
308 |
# See if they've changed a not-null field to be null |
|
309 |
new_default = (self.new_field.default is not None) and (self.new_field.default is not NOT_PROVIDED) |
|
310 |
old_default = (self.old_field.default is not None) and (self.old_field.default is not NOT_PROVIDED) |
|
311 |
if self.old_field.null and not self.new_field.null and not new_default: |
|
312 |
self.deal_with_not_null_no_default(self.new_field, self.new_def) |
|
313 |
if not self.old_field.null and self.new_field.null and not old_default: |
|
314 |
self.null_reason = "making this field nullable" |
|
315 |
self.allow_third_null_option = True |
|
316 |
self.deal_with_not_null_no_default(self.old_field, self.old_def) |
|
317 |
|
|
318 |
def console_line(self): |
|
319 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
320 |
return " ~ Changed field %s on %s.%s" % ( |
|
321 |
self.new_field.name, |
|
322 |
self.model._meta.app_label, |
|
323 |
self.model._meta.object_name, |
|
324 |
) |
|
325 |
|
|
326 |
def _code(self, old_field, new_field, new_def): |
|
327 |
|
|
328 |
output = "" |
|
329 |
|
|
330 |
if self.old_field.column != self.new_field.column: |
|
331 |
output += self.RENAME_TEMPLATE % { |
|
332 |
"model_name": self.model._meta.object_name, |
|
333 |
"table_name": self.model._meta.db_table, |
|
334 |
"field_name": new_field.name, |
|
335 |
"old_column": old_field.column, |
|
336 |
"new_column": new_field.column, |
|
337 |
} |
|
338 |
|
|
339 |
output += self.FORWARDS_TEMPLATE % { |
|
340 |
"model_name": self.model._meta.object_name, |
|
341 |
"table_name": self.model._meta.db_table, |
|
342 |
"field_name": new_field.name, |
|
343 |
"field_column": new_field.column, |
|
344 |
"field_def": self.triple_to_def(new_def), |
|
345 |
} |
|
346 |
|
|
347 |
return output |
|
348 |
|
|
349 |
def forwards_code(self): |
|
350 |
return self._code(self.old_field, self.new_field, self.new_def) |
|
351 |
|
|
352 |
def backwards_code(self): |
|
353 |
if not self.irreversible: |
|
354 |
return self._code(self.new_field, self.old_field, self.old_def) |
|
355 |
else: |
|
356 |
return self.irreversable_code(self.old_field) |
|
357 |
|
|
358 |
|
|
359 |
class AddUnique(Action): |
|
360 |
""" |
|
361 |
Adds a unique constraint to a model. Takes a Model class and the field names. |
|
362 |
""" |
|
363 |
|
|
364 |
FORWARDS_TEMPLATE = ''' |
|
365 |
# Adding unique constraint on '%(model_name)s', fields %(field_names)s |
|
366 |
db.create_unique(%(table_name)r, %(fields)r)'''[1:] + "\n" |
|
367 |
|
|
368 |
BACKWARDS_TEMPLATE = ''' |
|
369 |
# Removing unique constraint on '%(model_name)s', fields %(field_names)s |
|
370 |
db.delete_unique(%(table_name)r, %(fields)r)'''[1:] + "\n" |
|
371 |
|
|
372 |
prepend_backwards = True |
|
373 |
|
|
374 |
def __init__(self, model, fields): |
|
375 |
self.model = model |
|
376 |
self.fields = fields |
|
377 |
|
|
378 |
def console_line(self): |
|
379 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
380 |
return " + Added unique constraint for %s on %s.%s" % ( |
|
381 |
[x.name for x in self.fields], |
|
382 |
self.model._meta.app_label, |
|
383 |
self.model._meta.object_name, |
|
384 |
) |
|
385 |
|
|
386 |
def forwards_code(self): |
|
387 |
|
|
388 |
return self.FORWARDS_TEMPLATE % { |
|
389 |
"model_name": self.model._meta.object_name, |
|
390 |
"table_name": self.model._meta.db_table, |
|
391 |
"fields": [field.column for field in self.fields], |
|
392 |
"field_names": [field.name for field in self.fields], |
|
393 |
} |
|
394 |
|
|
395 |
def backwards_code(self): |
|
396 |
return self.BACKWARDS_TEMPLATE % { |
|
397 |
"model_name": self.model._meta.object_name, |
|
398 |
"table_name": self.model._meta.db_table, |
|
399 |
"fields": [field.column for field in self.fields], |
|
400 |
"field_names": [field.name for field in self.fields], |
|
401 |
} |
|
402 |
|
|
403 |
|
|
404 |
class DeleteUnique(AddUnique): |
|
405 |
""" |
|
406 |
Removes a unique constraint from a model. Takes a Model class and the field names. |
|
407 |
""" |
|
408 |
|
|
409 |
prepend_forwards = True |
|
410 |
prepend_backwards = False |
|
411 |
|
|
412 |
def console_line(self): |
|
413 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
414 |
return " - Deleted unique constraint for %s on %s.%s" % ( |
|
415 |
[x.name for x in self.fields], |
|
416 |
self.model._meta.app_label, |
|
417 |
self.model._meta.object_name, |
|
418 |
) |
|
419 |
|
|
420 |
def forwards_code(self): |
|
421 |
return AddUnique.backwards_code(self) |
|
422 |
|
|
423 |
def backwards_code(self): |
|
424 |
return AddUnique.forwards_code(self) |
|
425 |
|
|
426 |
|
|
427 |
class AddIndex(AddUnique): |
|
428 |
""" |
|
429 |
Adds an index to a model field[s]. Takes a Model class and the field names. |
|
430 |
""" |
|
431 |
|
|
432 |
FORWARDS_TEMPLATE = ''' |
|
433 |
# Adding index on '%(model_name)s', fields %(field_names)s |
|
434 |
db.create_index(%(table_name)r, %(fields)r)'''[1:] + "\n" |
|
435 |
|
|
436 |
BACKWARDS_TEMPLATE = ''' |
|
437 |
# Removing index on '%(model_name)s', fields %(field_names)s |
|
438 |
db.delete_index(%(table_name)r, %(fields)r)'''[1:] + "\n" |
|
439 |
|
|
440 |
def console_line(self): |
|
441 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
442 |
return " + Added index for %s on %s.%s" % ( |
|
443 |
[x.name for x in self.fields], |
|
444 |
self.model._meta.app_label, |
|
445 |
self.model._meta.object_name, |
|
446 |
) |
|
447 |
|
|
448 |
|
|
449 |
class DeleteIndex(AddIndex): |
|
450 |
""" |
|
451 |
Deletes an index off a model field[s]. Takes a Model class and the field names. |
|
452 |
""" |
|
453 |
|
|
454 |
def console_line(self): |
|
455 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
456 |
return " + Deleted index for %s on %s.%s" % ( |
|
457 |
[x.name for x in self.fields], |
|
458 |
self.model._meta.app_label, |
|
459 |
self.model._meta.object_name, |
|
460 |
) |
|
461 |
|
|
462 |
def forwards_code(self): |
|
463 |
return AddIndex.backwards_code(self) |
|
464 |
|
|
465 |
def backwards_code(self): |
|
466 |
return AddIndex.forwards_code(self) |
|
467 |
|
|
468 |
|
|
469 |
class AddM2M(Action): |
|
470 |
""" |
|
471 |
Adds a unique constraint to a model. Takes a Model class and the field names. |
|
472 |
""" |
|
473 |
|
|
474 |
FORWARDS_TEMPLATE = ''' |
|
475 |
# Adding M2M table for field %(field_name)s on '%(model_name)s' |
|
476 |
db.create_table(%(table_name)r, ( |
|
477 |
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), |
|
478 |
(%(left_field)r, models.ForeignKey(orm[%(left_model_key)r], null=False)), |
|
479 |
(%(right_field)r, models.ForeignKey(orm[%(right_model_key)r], null=False)) |
|
480 |
)) |
|
481 |
db.create_unique(%(table_name)r, [%(left_column)r, %(right_column)r])'''[1:] + "\n" |
|
482 |
|
|
483 |
BACKWARDS_TEMPLATE = ''' |
|
484 |
# Removing M2M table for field %(field_name)s on '%(model_name)s' |
|
485 |
db.delete_table('%(table_name)s')'''[1:] + "\n" |
|
486 |
|
|
487 |
def __init__(self, model, field): |
|
488 |
self.model = model |
|
489 |
self.field = field |
|
490 |
|
|
491 |
def console_line(self): |
|
492 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
493 |
return " + Added M2M table for %s on %s.%s" % ( |
|
494 |
self.field.name, |
|
495 |
self.model._meta.app_label, |
|
496 |
self.model._meta.object_name, |
|
497 |
) |
|
498 |
|
|
499 |
def forwards_code(self): |
|
500 |
|
|
501 |
return self.FORWARDS_TEMPLATE % { |
|
502 |
"model_name": self.model._meta.object_name, |
|
503 |
"field_name": self.field.name, |
|
504 |
"table_name": self.field.m2m_db_table(), |
|
505 |
"left_field": self.field.m2m_column_name()[:-3], # Remove the _id part |
|
506 |
"left_column": self.field.m2m_column_name(), |
|
507 |
"left_model_key": model_key(self.model), |
|
508 |
"right_field": self.field.m2m_reverse_name()[:-3], # Remove the _id part |
|
509 |
"right_column": self.field.m2m_reverse_name(), |
|
510 |
"right_model_key": model_key(self.field.rel.to), |
|
511 |
} |
|
512 |
|
|
513 |
def backwards_code(self): |
|
514 |
|
|
515 |
return self.BACKWARDS_TEMPLATE % { |
|
516 |
"model_name": self.model._meta.object_name, |
|
517 |
"field_name": self.field.name, |
|
518 |
"table_name": self.field.m2m_db_table(), |
|
519 |
} |
|
520 |
|
|
521 |
|
|
522 |
class DeleteM2M(AddM2M): |
|
523 |
""" |
|
524 |
Adds a unique constraint to a model. Takes a Model class and the field names. |
|
525 |
""" |
|
526 |
|
|
527 |
def console_line(self): |
|
528 |
"Returns the string to print on the console, e.g. ' + Added field foo'" |
|
529 |
return " - Deleted M2M table for %s on %s.%s" % ( |
|
530 |
self.field.name, |
|
531 |
self.model._meta.app_label, |
|
532 |
self.model._meta.object_name, |
|
533 |
) |
|
534 |
|
|
535 |
def forwards_code(self): |
|
536 |
return AddM2M.backwards_code(self) |
|
537 |
|
|
538 |
def backwards_code(self): |
|
539 |
return AddM2M.forwards_code(self) |
|
540 |
|
env/lib/python2.7/site-packages/south/creator/changes.py | ||
---|---|---|
1 |
""" |
|
2 |
Contains things to detect changes - either using options passed in on the |
|
3 |
commandline, or by using autodetection, etc. |
|
4 |
""" |
|
5 |
|
|
6 |
from django.db import models |
|
7 |
from django.contrib.contenttypes.generic import GenericRelation |
|
8 |
from django.utils.datastructures import SortedDict |
|
9 |
|
|
10 |
from south.creator.freezer import remove_useless_attributes, freeze_apps, model_key |
|
11 |
from south.utils import auto_through |
|
12 |
|
|
13 |
class BaseChanges(object): |
|
14 |
""" |
|
15 |
Base changes class. |
|
16 |
""" |
|
17 |
def suggest_name(self): |
|
18 |
return '' |
|
19 |
|
|
20 |
def split_model_def(self, model, model_def): |
|
21 |
""" |
|
22 |
Given a model and its model def (a dict of field: triple), returns three |
|
23 |
items: the real fields dict, the Meta dict, and the M2M fields dict. |
|
24 |
""" |
|
25 |
real_fields = SortedDict() |
|
26 |
meta = SortedDict() |
|
27 |
m2m_fields = SortedDict() |
|
28 |
for name, triple in model_def.items(): |
|
29 |
if name == "Meta": |
|
30 |
meta = triple |
|
31 |
elif isinstance(model._meta.get_field_by_name(name)[0], models.ManyToManyField): |
|
32 |
m2m_fields[name] = triple |
|
33 |
else: |
|
34 |
real_fields[name] = triple |
|
35 |
return real_fields, meta, m2m_fields |
|
36 |
|
|
37 |
def current_model_from_key(self, key): |
|
38 |
app_label, model_name = key.split(".") |
|
39 |
return models.get_model(app_label, model_name) |
|
40 |
|
|
41 |
def current_field_from_key(self, key, fieldname): |
|
42 |
app_label, model_name = key.split(".") |
|
43 |
# Special, for the magical field from order_with_respect_to |
|
44 |
if fieldname == "_order": |
|
45 |
field = models.IntegerField() |
|
46 |
field.name = "_order" |
|
47 |
field.attname = "_order" |
|
48 |
field.column = "_order" |
|
49 |
field.default = 0 |
|
50 |
return field |
|
51 |
# Otherwise, normal. |
|
52 |
return models.get_model(app_label, model_name)._meta.get_field_by_name(fieldname)[0] |
|
53 |
|
|
54 |
|
|
55 |
class AutoChanges(BaseChanges): |
|
56 |
""" |
|
57 |
Detects changes by 'diffing' two sets of frozen model definitions. |
|
58 |
""" |
|
59 |
|
|
60 |
# Field types we don't generate add/remove field changes for. |
|
61 |
IGNORED_FIELD_TYPES = [ |
|
62 |
GenericRelation, |
|
63 |
] |
|
64 |
|
|
65 |
def __init__(self, migrations, old_defs, old_orm, new_defs): |
|
66 |
self.migrations = migrations |
|
67 |
self.old_defs = old_defs |
|
68 |
self.old_orm = old_orm |
|
69 |
self.new_defs = new_defs |
|
70 |
|
|
71 |
def suggest_name(self): |
|
72 |
parts = ["auto"] |
|
73 |
for change_name, params in self.get_changes(): |
|
74 |
if change_name == "AddModel": |
|
75 |
parts.append("add_%s" % params['model']._meta.object_name.lower()) |
|
76 |
elif change_name == "DeleteModel": |
|
77 |
parts.append("del_%s" % params['model']._meta.object_name.lower()) |
|
78 |
elif change_name == "AddField": |
|
79 |
parts.append("add_field_%s_%s" % ( |
|
80 |
params['model']._meta.object_name.lower(), |
|
81 |
params['field'].name, |
|
82 |
)) |
|
83 |
elif change_name == "DeleteField": |
|
84 |
parts.append("del_field_%s_%s" % ( |
|
85 |
params['model']._meta.object_name.lower(), |
|
86 |
params['field'].name, |
|
87 |
)) |
|
88 |
elif change_name == "ChangeField": |
|
89 |
parts.append("chg_field_%s_%s" % ( |
|
90 |
params['model']._meta.object_name.lower(), |
|
91 |
params['new_field'].name, |
|
92 |
)) |
|
93 |
elif change_name == "AddUnique": |
|
94 |
parts.append("add_unique_%s_%s" % ( |
|
95 |
params['model']._meta.object_name.lower(), |
|
96 |
"_".join([x.name for x in params['fields']]), |
|
97 |
)) |
|
98 |
elif change_name == "DeleteUnique": |
|
99 |
parts.append("del_unique_%s_%s" % ( |
|
100 |
params['model']._meta.object_name.lower(), |
|
101 |
"_".join([x.name for x in params['fields']]), |
|
102 |
)) |
|
103 |
return ("__".join(parts))[:70] |
|
104 |
|
|
105 |
def get_changes(self): |
|
106 |
""" |
|
107 |
Returns the difference between the old and new sets of models as a 5-tuple: |
|
108 |
added_models, deleted_models, added_fields, deleted_fields, changed_fields |
|
109 |
""" |
|
110 |
|
|
111 |
deleted_models = set() |
|
112 |
|
|
113 |
# See if anything's vanished |
|
114 |
for key in self.old_defs: |
|
115 |
if key not in self.new_defs: |
|
116 |
# We shouldn't delete it if it was managed=False |
|
117 |
old_fields, old_meta, old_m2ms = self.split_model_def(self.old_orm[key], self.old_defs[key]) |
|
118 |
if old_meta.get("managed", "True") != "False": |
|
119 |
# Alright, delete it. |
|
120 |
yield ("DeleteModel", { |
|
121 |
"model": self.old_orm[key], |
|
122 |
"model_def": old_fields, |
|
123 |
}) |
|
124 |
# Also make sure we delete any M2Ms it had. |
|
125 |
for fieldname in old_m2ms: |
|
126 |
# Only delete its stuff if it wasn't a through=. |
|
127 |
field = self.old_orm[key + ":" + fieldname] |
|
128 |
if auto_through(field): |
|
129 |
yield ("DeleteM2M", {"model": self.old_orm[key], "field": field}) |
|
130 |
# And any unique constraints it had |
|
131 |
unique_together = eval(old_meta.get("unique_together", "[]")) |
|
132 |
if unique_together: |
|
133 |
# If it's only a single tuple, make it into the longer one |
|
134 |
if isinstance(unique_together[0], basestring): |
|
135 |
unique_together = [unique_together] |
|
136 |
# For each combination, make an action for it |
|
137 |
for fields in unique_together: |
|
138 |
yield ("DeleteUnique", { |
|
139 |
"model": self.old_orm[key], |
|
140 |
"fields": [self.old_orm[key]._meta.get_field_by_name(x)[0] for x in fields], |
|
141 |
}) |
|
142 |
# We always add it in here so we ignore it later |
|
143 |
deleted_models.add(key) |
|
144 |
|
|
145 |
# Or appeared |
|
146 |
for key in self.new_defs: |
|
147 |
if key not in self.old_defs: |
|
148 |
# We shouldn't add it if it's managed=False |
|
149 |
new_fields, new_meta, new_m2ms = self.split_model_def(self.current_model_from_key(key), self.new_defs[key]) |
|
150 |
if new_meta.get("managed", "True") != "False": |
|
151 |
yield ("AddModel", { |
|
152 |
"model": self.current_model_from_key(key), |
|
153 |
"model_def": new_fields, |
|
154 |
}) |
|
155 |
# Also make sure we add any M2Ms it has. |
|
156 |
for fieldname in new_m2ms: |
|
157 |
# Only create its stuff if it wasn't a through=. |
|
158 |
field = self.current_field_from_key(key, fieldname) |
|
159 |
if auto_through(field): |
|
160 |
yield ("AddM2M", {"model": self.current_model_from_key(key), "field": field}) |
|
161 |
# And any unique constraints it has |
|
162 |
unique_together = eval(new_meta.get("unique_together", "[]")) |
|
163 |
if unique_together: |
|
164 |
# If it's only a single tuple, make it into the longer one |
|
165 |
if isinstance(unique_together[0], basestring): |
|
166 |
unique_together = [unique_together] |
|
167 |
# For each combination, make an action for it |
|
168 |
for fields in unique_together: |
|
169 |
yield ("AddUnique", { |
|
170 |
"model": self.current_model_from_key(key), |
|
171 |
"fields": [self.current_model_from_key(key)._meta.get_field_by_name(x)[0] for x in fields], |
|
172 |
}) |
|
173 |
|
|
174 |
# Now, for every model that's stayed the same, check its fields. |
|
175 |
for key in self.old_defs: |
|
176 |
if key not in deleted_models: |
|
177 |
|
|
178 |
old_fields, old_meta, old_m2ms = self.split_model_def(self.old_orm[key], self.old_defs[key]) |
|
179 |
new_fields, new_meta, new_m2ms = self.split_model_def(self.current_model_from_key(key), self.new_defs[key]) |
|
180 |
|
|
181 |
# Do nothing for models which are now not managed. |
|
182 |
if new_meta.get("managed", "True") == "False": |
|
183 |
continue |
|
184 |
|
|
185 |
# Find fields that have vanished. |
|
186 |
for fieldname in old_fields: |
|
187 |
if fieldname not in new_fields: |
|
188 |
# Don't do it for any fields we're ignoring |
|
189 |
field = self.old_orm[key + ":" + fieldname] |
|
190 |
field_allowed = True |
|
191 |
for field_type in self.IGNORED_FIELD_TYPES: |
|
192 |
if isinstance(field, field_type): |
|
193 |
field_allowed = False |
|
194 |
if field_allowed: |
|
195 |
# Looks alright. |
|
196 |
yield ("DeleteField", { |
|
197 |
"model": self.old_orm[key], |
|
198 |
"field": field, |
|
199 |
"field_def": old_fields[fieldname], |
|
200 |
}) |
|
201 |
|
|
202 |
# And ones that have appeared |
|
203 |
for fieldname in new_fields: |
|
204 |
if fieldname not in old_fields: |
|
205 |
# Don't do it for any fields we're ignoring |
|
206 |
field = self.current_field_from_key(key, fieldname) |
|
207 |
field_allowed = True |
|
208 |
for field_type in self.IGNORED_FIELD_TYPES: |
|
209 |
if isinstance(field, field_type): |
|
210 |
field_allowed = False |
|
211 |
if field_allowed: |
|
212 |
# Looks alright. |
|
213 |
yield ("AddField", { |
|
214 |
"model": self.current_model_from_key(key), |
|
215 |
"field": field, |
|
216 |
"field_def": new_fields[fieldname], |
|
217 |
}) |
|
218 |
|
|
219 |
# Find M2Ms that have vanished |
|
220 |
for fieldname in old_m2ms: |
|
221 |
if fieldname not in new_m2ms: |
|
222 |
# Only delete its stuff if it wasn't a through=. |
|
223 |
field = self.old_orm[key + ":" + fieldname] |
|
224 |
if auto_through(field): |
|
225 |
yield ("DeleteM2M", {"model": self.old_orm[key], "field": field}) |
|
226 |
|
|
227 |
# Find M2Ms that have appeared |
|
228 |
for fieldname in new_m2ms: |
|
229 |
if fieldname not in old_m2ms: |
|
230 |
# Only create its stuff if it wasn't a through=. |
|
231 |
field = self.current_field_from_key(key, fieldname) |
|
232 |
if auto_through(field): |
|
233 |
yield ("AddM2M", {"model": self.current_model_from_key(key), "field": field}) |
|
234 |
|
|
235 |
# For the ones that exist in both models, see if they were changed |
|
236 |
for fieldname in set(old_fields).intersection(set(new_fields)): |
|
237 |
# Non-index changes |
|
238 |
if self.different_attributes( |
|
239 |
remove_useless_attributes(old_fields[fieldname], True, True), |
|
240 |
remove_useless_attributes(new_fields[fieldname], True, True)): |
|
241 |
yield ("ChangeField", { |
|
242 |
"model": self.current_model_from_key(key), |
|
243 |
"old_field": self.old_orm[key + ":" + fieldname], |
|
244 |
"new_field": self.current_field_from_key(key, fieldname), |
|
245 |
"old_def": old_fields[fieldname], |
|
246 |
"new_def": new_fields[fieldname], |
|
247 |
}) |
|
248 |
# Index changes |
|
249 |
old_field = self.old_orm[key + ":" + fieldname] |
|
250 |
new_field = self.current_field_from_key(key, fieldname) |
|
251 |
if not old_field.db_index and new_field.db_index: |
|
252 |
# They've added an index. |
|
253 |
yield ("AddIndex", { |
|
254 |
"model": self.current_model_from_key(key), |
|
255 |
"fields": [new_field], |
|
256 |
}) |
|
257 |
if old_field.db_index and not new_field.db_index: |
|
258 |
# They've removed an index. |
|
259 |
yield ("DeleteIndex", { |
|
260 |
"model": self.old_orm[key], |
|
261 |
"fields": [old_field], |
|
262 |
}) |
|
263 |
# See if their uniques have changed |
|
264 |
if old_field.unique != new_field.unique: |
|
265 |
# Make sure we look at the one explicitly given to see what happened |
|
266 |
if new_field.unique: |
|
267 |
yield ("AddUnique", { |
|
268 |
"model": self.current_model_from_key(key), |
|
269 |
"fields": [new_field], |
|
270 |
}) |
|
271 |
else: |
|
272 |
yield ("DeleteUnique", { |
|
273 |
"model": self.old_orm[key], |
|
274 |
"fields": [old_field], |
|
275 |
}) |
|
276 |
|
|
277 |
# See if there's any M2Ms that have changed. |
|
278 |
for fieldname in set(old_m2ms).intersection(set(new_m2ms)): |
|
279 |
old_field = self.old_orm[key + ":" + fieldname] |
|
280 |
new_field = self.current_field_from_key(key, fieldname) |
|
281 |
# Have they _added_ a through= ? |
|
282 |
if auto_through(old_field) and not auto_through(new_field): |
|
283 |
yield ("DeleteM2M", {"model": self.old_orm[key], "field": old_field}) |
|
284 |
# Have they _removed_ a through= ? |
|
285 |
if not auto_through(old_field) and auto_through(new_field): |
|
286 |
yield ("AddM2M", {"model": self.current_model_from_key(key), "field": new_field}) |
|
287 |
|
|
288 |
## See if the unique_togethers have changed |
|
289 |
# First, normalise them into lists of sets. |
|
290 |
old_unique_together = eval(old_meta.get("unique_together", "[]")) |
|
291 |
new_unique_together = eval(new_meta.get("unique_together", "[]")) |
|
292 |
if old_unique_together and isinstance(old_unique_together[0], basestring): |
|
293 |
old_unique_together = [old_unique_together] |
|
294 |
if new_unique_together and isinstance(new_unique_together[0], basestring): |
|
295 |
new_unique_together = [new_unique_together] |
|
296 |
old_unique_together = map(set, old_unique_together) |
|
297 |
new_unique_together = map(set, new_unique_together) |
|
298 |
# See if any appeared or disappeared |
|
299 |
for item in old_unique_together: |
|
300 |
if item not in new_unique_together: |
|
301 |
yield ("DeleteUnique", { |
|
302 |
"model": self.old_orm[key], |
|
303 |
"fields": [self.old_orm[key + ":" + x] for x in item], |
|
304 |
}) |
|
305 |
for item in new_unique_together: |
|
306 |
if item not in old_unique_together: |
|
307 |
yield ("AddUnique", { |
|
308 |
"model": self.current_model_from_key(key), |
|
309 |
"fields": [self.current_field_from_key(key, x) for x in item], |
|
310 |
}) |
|
311 |
|
|
312 |
@classmethod |
|
313 |
def is_triple(cls, triple): |
|
314 |
"Returns whether the argument is a triple." |
|
315 |
return isinstance(triple, (list, tuple)) and len(triple) == 3 and \ |
|
316 |
isinstance(triple[0], (str, unicode)) and \ |
|
317 |
isinstance(triple[1], (list, tuple)) and \ |
|
318 |
isinstance(triple[2], dict) |
|
319 |
|
|
320 |
@classmethod |
|
321 |
def different_attributes(cls, old, new): |
|
322 |
""" |
|
323 |
Backwards-compat comparison that ignores orm. on the RHS and not the left |
|
324 |
and which knows django.db.models.fields.CharField = models.CharField. |
|
325 |
Has a whole load of tests in tests/autodetection.py. |
|
326 |
""" |
|
327 |
|
|
328 |
# If they're not triples, just do normal comparison |
|
329 |
if not cls.is_triple(old) or not cls.is_triple(new): |
|
330 |
return old != new |
|
331 |
|
|
332 |
# Expand them out into parts |
|
333 |
old_field, old_pos, old_kwd = old |
|
334 |
new_field, new_pos, new_kwd = new |
|
335 |
|
|
336 |
# Copy the positional and keyword arguments so we can compare them and pop off things |
|
337 |
old_pos, new_pos = old_pos[:], new_pos[:] |
|
338 |
old_kwd = dict(old_kwd.items()) |
|
339 |
new_kwd = dict(new_kwd.items()) |
|
340 |
|
|
341 |
# Remove comparison of the existence of 'unique', that's done elsewhere. |
|
342 |
# TODO: Make this work for custom fields where unique= means something else? |
|
343 |
if "unique" in old_kwd: |
|
344 |
del old_kwd['unique'] |
|
345 |
if "unique" in new_kwd: |
|
346 |
del new_kwd['unique'] |
|
347 |
|
|
348 |
# If the first bit is different, check it's not by dj.db.models... |
|
349 |
if old_field != new_field: |
|
350 |
if old_field.startswith("models.") and (new_field.startswith("django.db.models") \ |
|
351 |
or new_field.startswith("django.contrib.gis")): |
|
352 |
if old_field.split(".")[-1] != new_field.split(".")[-1]: |
|
353 |
return True |
|
354 |
else: |
|
355 |
# Remove those fields from the final comparison |
|
356 |
old_field = new_field = "" |
|
357 |
|
|
358 |
# If there's a positional argument in the first, and a 'to' in the second, |
|
359 |
# see if they're actually comparable. |
|
360 |
if (old_pos and "to" in new_kwd) and ("orm" in new_kwd['to'] and "orm" not in old_pos[0]): |
|
361 |
# Do special comparison to fix #153 |
|
362 |
try: |
|
363 |
if old_pos[0] != new_kwd['to'].split("'")[1].split(".")[1]: |
|
364 |
return True |
|
365 |
except IndexError: |
|
366 |
pass # Fall back to next comparison |
|
367 |
# Remove those attrs from the final comparison |
|
368 |
old_pos = old_pos[1:] |
|
369 |
del new_kwd['to'] |
|
370 |
|
|
371 |
return old_field != new_field or old_pos != new_pos or old_kwd != new_kwd |
|
372 |
|
|
373 |
|
|
374 |
class ManualChanges(BaseChanges): |
|
375 |
""" |
|
376 |
Detects changes by reading the command line. |
|
377 |
""" |
|
378 |
|
|
379 |
def __init__(self, migrations, added_models, added_fields, added_indexes): |
|
380 |
self.migrations = migrations |
|
381 |
self.added_models = added_models |
|
382 |
self.added_fields = added_fields |
|
383 |
self.added_indexes = added_indexes |
|
384 |
|
|
385 |
def suggest_name(self): |
|
386 |
bits = [] |
|
387 |
for model_name in self.added_models: |
|
388 |
bits.append('add_model_%s' % model_name) |
|
389 |
for field_name in self.added_fields: |
|
390 |
bits.append('add_field_%s' % field_name) |
|
391 |
for index_name in self.added_indexes: |
|
392 |
bits.append('add_index_%s' % index_name) |
|
393 |
return '_'.join(bits).replace('.', '_') |
|
394 |
|
|
395 |
def get_changes(self): |
|
396 |
# Get the model defs so we can use them for the yield later |
|
397 |
model_defs = freeze_apps([self.migrations.app_label()]) |
|
398 |
# Make the model changes |
|
399 |
for model_name in self.added_models: |
|
400 |
model = models.get_model(self.migrations.app_label(), model_name) |
|
401 |
real_fields, meta, m2m_fields = self.split_model_def(model, model_defs[model_key(model)]) |
|
402 |
yield ("AddModel", { |
|
403 |
"model": model, |
|
404 |
"model_def": real_fields, |
|
405 |
}) |
|
406 |
# And the field changes |
|
407 |
for field_desc in self.added_fields: |
|
408 |
try: |
|
409 |
model_name, field_name = field_desc.split(".") |
|
410 |
except (TypeError, ValueError): |
|
411 |
raise ValueError("%r is not a valid field description." % field_desc) |
|
412 |
model = models.get_model(self.migrations.app_label(), model_name) |
|
413 |
real_fields, meta, m2m_fields = self.split_model_def(model, model_defs[model_key(model)]) |
|
414 |
yield ("AddField", { |
|
415 |
"model": model, |
|
416 |
"field": model._meta.get_field_by_name(field_name)[0], |
|
417 |
"field_def": real_fields[field_name], |
|
418 |
}) |
|
419 |
# And the indexes |
|
420 |
for field_desc in self.added_indexes: |
|
421 |
try: |
|
422 |
model_name, field_name = field_desc.split(".") |
|
423 |
except (TypeError, ValueError): |
|
424 |
print "%r is not a valid field description." % field_desc |
|
425 |
model = models.get_model(self.migrations.app_label(), model_name) |
|
426 |
yield ("AddIndex", { |
|
427 |
"model": model, |
|
428 |
"fields": [model._meta.get_field_by_name(field_name)[0]], |
|
429 |
}) |
|
430 |
|
|
431 |
|
|
432 |
class InitialChanges(BaseChanges): |
|
433 |
""" |
|
434 |
Creates all models; handles --initial. |
|
435 |
""" |
|
436 |
def suggest_name(self): |
|
437 |
return 'initial' |
|
438 |
|
|
439 |
def __init__(self, migrations): |
|
440 |
self.migrations = migrations |
|
441 |
|
|
442 |
def get_changes(self): |
|
443 |
# Get the frozen models for this app |
|
444 |
model_defs = freeze_apps([self.migrations.app_label()]) |
|
445 |
|
|
446 |
for model in models.get_models(models.get_app(self.migrations.app_label())): |
|
447 |
|
|
448 |
# Don't do anything for unmanaged, abstract or proxy models |
|
449 |
if model._meta.abstract or getattr(model._meta, "proxy", False) or not getattr(model._meta, "managed", True): |
|
450 |
continue |
|
451 |
|
|
452 |
real_fields, meta, m2m_fields = self.split_model_def(model, model_defs[model_key(model)]) |
|
453 |
|
|
454 |
# Firstly, add the main table and fields |
|
455 |
yield ("AddModel", { |
Also available in: Unified diff