|
1 from django.core.exceptions import ImproperlyConfigured |
|
2 from django.core.management.base import BaseCommand, CommandError |
|
3 from django.core import serializers |
|
4 from django.db import connections, router, DEFAULT_DB_ALIAS |
|
5 from django.utils.datastructures import SortedDict |
|
6 |
|
7 from optparse import make_option |
|
8 |
|
9 class Command(BaseCommand): |
|
10 option_list = BaseCommand.option_list + ( |
|
11 make_option('--format', default='json', dest='format', |
|
12 help='Specifies the output serialization format for fixtures.'), |
|
13 make_option('--indent', default=None, dest='indent', type='int', |
|
14 help='Specifies the indent level to use when pretty-printing output'), |
|
15 make_option('--database', action='store', dest='database', |
|
16 default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load ' |
|
17 'fixtures into. Defaults to the "default" database.'), |
|
18 make_option('-e', '--exclude', dest='exclude',action='append', default=[], |
|
19 help='App to exclude (use multiple --exclude to exclude multiple apps).'), |
|
20 make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False, |
|
21 help='Use natural keys if they are available.'), |
|
22 ) |
|
23 help = 'Output the contents of the database as a fixture of the given format.' |
|
24 args = '[appname appname.ModelName ...]' |
|
25 |
|
26 def handle(self, *app_labels, **options): |
|
27 from django.db.models import get_app, get_apps, get_models, get_model |
|
28 |
|
29 format = options.get('format','json') |
|
30 indent = options.get('indent',None) |
|
31 using = options.get('database', DEFAULT_DB_ALIAS) |
|
32 connection = connections[using] |
|
33 exclude = options.get('exclude',[]) |
|
34 show_traceback = options.get('traceback', False) |
|
35 use_natural_keys = options.get('use_natural_keys', False) |
|
36 |
|
37 excluded_apps = set(get_app(app_label) for app_label in exclude) |
|
38 |
|
39 if len(app_labels) == 0: |
|
40 app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps) |
|
41 else: |
|
42 app_list = SortedDict() |
|
43 for label in app_labels: |
|
44 try: |
|
45 app_label, model_label = label.split('.') |
|
46 try: |
|
47 app = get_app(app_label) |
|
48 except ImproperlyConfigured: |
|
49 raise CommandError("Unknown application: %s" % app_label) |
|
50 |
|
51 model = get_model(app_label, model_label) |
|
52 if model is None: |
|
53 raise CommandError("Unknown model: %s.%s" % (app_label, model_label)) |
|
54 |
|
55 if app in app_list.keys(): |
|
56 if app_list[app] and model not in app_list[app]: |
|
57 app_list[app].append(model) |
|
58 else: |
|
59 app_list[app] = [model] |
|
60 except ValueError: |
|
61 # This is just an app - no model qualifier |
|
62 app_label = label |
|
63 try: |
|
64 app = get_app(app_label) |
|
65 except ImproperlyConfigured: |
|
66 raise CommandError("Unknown application: %s" % app_label) |
|
67 app_list[app] = None |
|
68 |
|
69 # Check that the serialization format exists; this is a shortcut to |
|
70 # avoid collating all the objects and _then_ failing. |
|
71 if format not in serializers.get_public_serializer_formats(): |
|
72 raise CommandError("Unknown serialization format: %s" % format) |
|
73 |
|
74 try: |
|
75 serializers.get_serializer(format) |
|
76 except KeyError: |
|
77 raise CommandError("Unknown serialization format: %s" % format) |
|
78 |
|
79 # Now collate the objects to be serialized. |
|
80 objects = [] |
|
81 for model in sort_dependencies(app_list.items()): |
|
82 if not model._meta.proxy and router.allow_syncdb(using, model): |
|
83 objects.extend(model._default_manager.using(using).all()) |
|
84 |
|
85 try: |
|
86 return serializers.serialize(format, objects, indent=indent, |
|
87 use_natural_keys=use_natural_keys) |
|
88 except Exception, e: |
|
89 if show_traceback: |
|
90 raise |
|
91 raise CommandError("Unable to serialize database: %s" % e) |
|
92 |
|
93 def sort_dependencies(app_list): |
|
94 """Sort a list of app,modellist pairs into a single list of models. |
|
95 |
|
96 The single list of models is sorted so that any model with a natural key |
|
97 is serialized before a normal model, and any model with a natural key |
|
98 dependency has it's dependencies serialized first. |
|
99 """ |
|
100 from django.db.models import get_model, get_models |
|
101 # Process the list of models, and get the list of dependencies |
|
102 model_dependencies = [] |
|
103 models = set() |
|
104 for app, model_list in app_list: |
|
105 if model_list is None: |
|
106 model_list = get_models(app) |
|
107 |
|
108 for model in model_list: |
|
109 models.add(model) |
|
110 # Add any explicitly defined dependencies |
|
111 if hasattr(model, 'natural_key'): |
|
112 deps = getattr(model.natural_key, 'dependencies', []) |
|
113 if deps: |
|
114 deps = [get_model(*d.split('.')) for d in deps] |
|
115 else: |
|
116 deps = [] |
|
117 |
|
118 # Now add a dependency for any FK or M2M relation with |
|
119 # a model that defines a natural key |
|
120 for field in model._meta.fields: |
|
121 if hasattr(field.rel, 'to'): |
|
122 rel_model = field.rel.to |
|
123 if hasattr(rel_model, 'natural_key'): |
|
124 deps.append(rel_model) |
|
125 for field in model._meta.many_to_many: |
|
126 rel_model = field.rel.to |
|
127 if hasattr(rel_model, 'natural_key'): |
|
128 deps.append(rel_model) |
|
129 model_dependencies.append((model, deps)) |
|
130 |
|
131 model_dependencies.reverse() |
|
132 # Now sort the models to ensure that dependencies are met. This |
|
133 # is done by repeatedly iterating over the input list of models. |
|
134 # If all the dependencies of a given model are in the final list, |
|
135 # that model is promoted to the end of the final list. This process |
|
136 # continues until the input list is empty, or we do a full iteration |
|
137 # over the input models without promoting a model to the final list. |
|
138 # If we do a full iteration without a promotion, that means there are |
|
139 # circular dependencies in the list. |
|
140 model_list = [] |
|
141 while model_dependencies: |
|
142 skipped = [] |
|
143 changed = False |
|
144 while model_dependencies: |
|
145 model, deps = model_dependencies.pop() |
|
146 |
|
147 # If all of the models in the dependency list are either already |
|
148 # on the final model list, or not on the original serialization list, |
|
149 # then we've found another model with all it's dependencies satisfied. |
|
150 found = True |
|
151 for candidate in ((d not in models or d in model_list) for d in deps): |
|
152 if not candidate: |
|
153 found = False |
|
154 if found: |
|
155 model_list.append(model) |
|
156 changed = True |
|
157 else: |
|
158 skipped.append((model, deps)) |
|
159 if not changed: |
|
160 raise CommandError("Can't resolve dependencies for %s in serialized app list." % |
|
161 ', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name) |
|
162 for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__)) |
|
163 ) |
|
164 model_dependencies = skipped |
|
165 |
|
166 return model_list |