1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
"""
Handles freezing of models into FakeORMs.
"""
from __future__ import print_function
import sys
from django.db import models
from django.db.models.base import ModelBase, Model
from django.contrib.contenttypes.generic import GenericRelation
from south.utils import get_attribute, auto_through
from south import modelsinspector
from south.utils.py3 import string_types
def freeze_apps(apps):
"""
Takes a list of app labels, and returns a string of their frozen form.
"""
if isinstance(apps, string_types):
apps = [apps]
frozen_models = set()
# For each app, add in all its models
for app in apps:
for model in models.get_models(models.get_app(app)):
# Only add if it's not abstract or proxy
if not model._meta.abstract and not getattr(model._meta, "proxy", False):
frozen_models.add(model)
# Now, add all the dependencies
for model in list(frozen_models):
frozen_models.update(model_dependencies(model))
# Serialise!
model_defs = {}
model_classes = {}
for model in frozen_models:
model_defs[model_key(model)] = prep_for_freeze(model)
model_classes[model_key(model)] = model
# Check for any custom fields that failed to freeze.
missing_fields = False
for key, fields in model_defs.items():
for field_name, value in fields.items():
if value is None:
missing_fields = True
model_class = model_classes[key]
field_class = model_class._meta.get_field_by_name(field_name)[0]
print(" ! Cannot freeze field '%s.%s'" % (key, field_name))
print(" ! (this field has class %s.%s)" % (field_class.__class__.__module__, field_class.__class__.__name__))
if missing_fields:
print("")
print(" ! South cannot introspect some fields; this is probably because they are custom")
print(" ! fields. If they worked in 0.6 or below, this is because we have removed the")
print(" ! models parser (it often broke things).")
print(" ! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork")
sys.exit(1)
return model_defs
def freeze_apps_to_string(apps):
return pprint_frozen_models(freeze_apps(apps))
###
def model_key(model):
"For a given model, return 'appname.modelname'."
return "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
def prep_for_freeze(model):
"""
Takes a model and returns the ready-to-serialise dict (all you need
to do is just pretty-print it).
"""
fields = modelsinspector.get_model_fields(model, m2m=True)
# Remove useless attributes (like 'choices')
for name, field in fields.items():
fields[name] = remove_useless_attributes(field)
# See if there's a Meta
fields['Meta'] = remove_useless_meta(modelsinspector.get_model_meta(model))
# Add in our own special items to track the object name and managed
fields['Meta']['object_name'] = model._meta.object_name # Special: not eval'able.
if not getattr(model._meta, "managed", True):
fields['Meta']['managed'] = repr(model._meta.managed)
return fields
### Dependency resolvers
def model_dependencies(model, checked_models=None):
"""
Returns a set of models this one depends on to be defined; things like
OneToOneFields as ID, ForeignKeys everywhere, etc.
"""
depends = set()
checked_models = checked_models or set()
# Get deps for each field
for field in model._meta.fields + model._meta.many_to_many:
depends.update(field_dependencies(field, checked_models))
# Add in any non-abstract bases
for base in model.__bases__:
if issubclass(base, models.Model) and hasattr(base, '_meta') and not base._meta.abstract:
depends.add(base)
# Now recurse
new_to_check = depends - checked_models
while new_to_check:
checked_model = new_to_check.pop()
if checked_model == model or checked_model in checked_models:
continue
checked_models.add(checked_model)
deps = model_dependencies(checked_model, checked_models)
# Loop through dependencies...
for dep in deps:
# If the new dep is not already checked, add to the queue
if (dep not in depends) and (dep not in new_to_check) and (dep not in checked_models):
new_to_check.add(dep)
depends.add(dep)
return depends
def field_dependencies(field, checked_models=None):
checked_models = checked_models or set()
depends = set()
arg_defs, kwarg_defs = modelsinspector.matching_details(field)
for attrname, options in arg_defs + list(kwarg_defs.values()):
if options.get("ignore_if_auto_through", False) and auto_through(field):
continue
if options.get("is_value", False):
value = attrname
elif attrname == 'rel.through' and hasattr(getattr(field, 'rel', None), 'through_model'):
# Hack for django 1.1 and below, where the through model is stored
# in rel.through_model while rel.through stores only the model name.
value = field.rel.through_model
else:
try:
value = get_attribute(field, attrname)
except AttributeError:
if options.get("ignore_missing", False):
continue
raise
if isinstance(value, Model):
value = value.__class__
if not isinstance(value, ModelBase):
continue
if getattr(value._meta, "proxy", False):
value = value._meta.proxy_for_model
if value in checked_models:
continue
checked_models.add(value)
depends.add(value)
depends.update(model_dependencies(value, checked_models))
return depends
### Prettyprinters
def pprint_frozen_models(models):
return "{\n %s\n }" % ",\n ".join([
"%r: %s" % (name, pprint_fields(fields))
for name, fields in sorted(models.items())
])
def pprint_fields(fields):
return "{\n %s\n }" % ",\n ".join([
"%r: %r" % (name, defn)
for name, defn in sorted(fields.items())
])
### Output sanitisers
USELESS_KEYWORDS = ["choices", "help_text", "verbose_name"]
USELESS_DB_KEYWORDS = ["related_name", "default", "blank"] # Important for ORM, not for DB.
INDEX_KEYWORDS = ["db_index"]
def remove_useless_attributes(field, db=False, indexes=False):
"Removes useless (for database) attributes from the field's defn."
# Work out what to remove, and remove it.
keywords = USELESS_KEYWORDS[:]
if db:
keywords += USELESS_DB_KEYWORDS[:]
if indexes:
keywords += INDEX_KEYWORDS[:]
if field:
for name in keywords:
if name in field[2]:
del field[2][name]
return field
USELESS_META = ["verbose_name", "verbose_name_plural"]
def remove_useless_meta(meta):
"Removes useless (for database) attributes from the table's meta."
if meta:
for name in USELESS_META:
if name in meta:
del meta[name]
return meta
|