Project

General

Profile

Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / south / creator / freezer.py @ d1a4905f

History | View | Annotate | Download (7.02 KB)

1
"""
2
Handles freezing of models into FakeORMs.
3
"""
4

    
5
import sys
6

    
7
from django.db import models
8
from django.db.models.base import ModelBase, Model
9
from django.contrib.contenttypes.generic import GenericRelation
10

    
11
from south.orm import FakeORM
12
from south.utils import get_attribute, auto_through
13
from south import modelsinspector
14

    
15
def freeze_apps(apps):
16
    """
17
    Takes a list of app labels, and returns a string of their frozen form.
18
    """
19
    if isinstance(apps, basestring):
20
        apps = [apps]
21
    frozen_models = set()
22
    # For each app, add in all its models
23
    for app in apps:
24
        for model in models.get_models(models.get_app(app)):
25
            # Only add if it's not abstract or proxy
26
            if not model._meta.abstract and not getattr(model._meta, "proxy", False):
27
                frozen_models.add(model)
28
    # Now, add all the dependencies
29
    for model in list(frozen_models):
30
        frozen_models.update(model_dependencies(model))
31
    # Serialise!
32
    model_defs = {}
33
    model_classes = {}
34
    for model in frozen_models:
35
        model_defs[model_key(model)] = prep_for_freeze(model)
36
        model_classes[model_key(model)] = model
37
    # Check for any custom fields that failed to freeze.
38
    missing_fields = False
39
    for key, fields in model_defs.items():
40
        for field_name, value in fields.items():
41
            if value is None:
42
                missing_fields = True
43
                model_class = model_classes[key]
44
                field_class = model_class._meta.get_field_by_name(field_name)[0]
45
                print " ! Cannot freeze field '%s.%s'" % (key, field_name)
46
                print " ! (this field has class %s.%s)" % (field_class.__class__.__module__, field_class.__class__.__name__)
47
    if missing_fields:
48
        print ""
49
        print " ! South cannot introspect some fields; this is probably because they are custom"
50
        print " ! fields. If they worked in 0.6 or below, this is because we have removed the"
51
        print " ! models parser (it often broke things)."
52
        print " ! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork"
53
        sys.exit(1)
54
    
55
    return model_defs
56
    
57
def freeze_apps_to_string(apps):
58
    return pprint_frozen_models(freeze_apps(apps))
59
    
60
### 
61

    
62
def model_key(model):
63
    "For a given model, return 'appname.modelname'."
64
    return "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
65

    
66
def prep_for_freeze(model):
67
    """
68
    Takes a model and returns the ready-to-serialise dict (all you need
69
    to do is just pretty-print it).
70
    """
71
    fields = modelsinspector.get_model_fields(model, m2m=True)
72
    # Remove useless attributes (like 'choices')
73
    for name, field in fields.items():
74
        fields[name] = remove_useless_attributes(field)
75
    # See if there's a Meta
76
    fields['Meta'] = remove_useless_meta(modelsinspector.get_model_meta(model))
77
    # Add in our own special items to track the object name and managed
78
    fields['Meta']['object_name'] = model._meta.object_name # Special: not eval'able.
79
    if not getattr(model._meta, "managed", True):
80
        fields['Meta']['managed'] = repr(model._meta.managed)
81
    return fields
82

    
83
### Dependency resolvers
84

    
85
def model_dependencies(model, checked_models=None):
86
    """
87
    Returns a set of models this one depends on to be defined; things like
88
    OneToOneFields as ID, ForeignKeys everywhere, etc.
89
    """
90
    depends = set()
91
    checked_models = checked_models or set()
92
    # Get deps for each field
93
    for field in model._meta.fields + model._meta.many_to_many:
94
        depends.update(field_dependencies(field, checked_models))
95
    # Add in any non-abstract bases
96
    for base in model.__bases__:
97
        if issubclass(base, models.Model) and hasattr(base, '_meta') and not base._meta.abstract:
98
            depends.add(base)
99
    # Now recurse
100
    new_to_check = depends - checked_models
101
    while new_to_check:
102
        checked_model = new_to_check.pop()
103
        if checked_model == model or checked_model in checked_models:
104
            continue
105
        checked_models.add(checked_model)
106
        deps = model_dependencies(checked_model, checked_models)
107
        # Loop through dependencies...
108
        for dep in deps:
109
            # If the new dep is not already checked, add to the queue
110
            if (dep not in depends) and (dep not in new_to_check) and (dep not in checked_models):
111
                new_to_check.add(dep)
112
            depends.add(dep)
113
    return depends
114

    
115
def field_dependencies(field, checked_models=None):
116
    checked_models = checked_models or set()
117
    depends = set()
118
    arg_defs, kwarg_defs = modelsinspector.matching_details(field)
119
    for attrname, options in arg_defs + kwarg_defs.values():
120
        if options.get("ignore_if_auto_through", False) and auto_through(field):
121
            continue
122
        if options.get("is_value", False):
123
            value = attrname
124
        elif attrname == 'rel.through' and hasattr(getattr(field, 'rel', None), 'through_model'):
125
            # Hack for django 1.1 and below, where the through model is stored
126
            # in rel.through_model while rel.through stores only the model name.
127
            value = field.rel.through_model
128
        else:
129
            try:
130
                value = get_attribute(field, attrname)
131
            except AttributeError:
132
                if options.get("ignore_missing", False):
133
                    continue
134
                raise
135
        if isinstance(value, Model):
136
            value = value.__class__
137
        if not isinstance(value, ModelBase):
138
            continue
139
        if getattr(value._meta, "proxy", False):
140
            value = value._meta.proxy_for_model
141
        if value in checked_models:
142
            continue
143
        checked_models.add(value)
144
        depends.add(value)
145
        depends.update(model_dependencies(value, checked_models))
146

    
147
    return depends
148

    
149
### Prettyprinters
150

    
151
def pprint_frozen_models(models):
152
    return "{\n        %s\n    }" % ",\n        ".join([
153
        "%r: %s" % (name, pprint_fields(fields))
154
        for name, fields in sorted(models.items())
155
    ])
156

    
157
def pprint_fields(fields):
158
    return "{\n            %s\n        }" % ",\n            ".join([
159
        "%r: %r" % (name, defn)
160
        for name, defn in sorted(fields.items())
161
    ])
162

    
163
### Output sanitisers
164

    
165
USELESS_KEYWORDS = ["choices", "help_text", "verbose_name"]
166
USELESS_DB_KEYWORDS = ["related_name", "default", "blank"] # Important for ORM, not for DB.
167
INDEX_KEYWORDS = ["db_index"]
168

    
169
def remove_useless_attributes(field, db=False, indexes=False):
170
    "Removes useless (for database) attributes from the field's defn."
171
    # Work out what to remove, and remove it.
172
    keywords = USELESS_KEYWORDS[:]
173
    if db:
174
        keywords += USELESS_DB_KEYWORDS[:]
175
    if indexes:
176
        keywords += INDEX_KEYWORDS[:]
177
    if field:
178
        for name in keywords:
179
            if name in field[2]:
180
                del field[2][name]
181
    return field
182

    
183
USELESS_META = ["verbose_name", "verbose_name_plural"]
184
def remove_useless_meta(meta):
185
    "Removes useless (for database) attributes from the table's meta."
186
    if meta:
187
        for name in USELESS_META:
188
            if name in meta:
189
                del meta[name]
190
    return meta