Project

General

Profile

Revision d1a4905f

IDd1a4905f911cd924fb3e6830cab389314f7c5b2c
Parent 1a305335
Child 988e217a

Added by officers over 11 years ago

added south for database migration

View differences:

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", {
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff