summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/south/creator/freezer.py
blob: 0f98cea0d75e0b49d73de301e1b46610785643da (plain)
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