1 from django.core.exceptions import ImproperlyConfigured |
1 from django.core.exceptions import ImproperlyConfigured |
2 from django.core.management.base import BaseCommand, CommandError |
2 from django.core.management.base import BaseCommand, CommandError |
3 from django.core import serializers |
3 from django.core import serializers |
|
4 from django.db import connections, router, DEFAULT_DB_ALIAS |
4 from django.utils.datastructures import SortedDict |
5 from django.utils.datastructures import SortedDict |
5 |
6 |
6 from optparse import make_option |
7 from optparse import make_option |
7 |
8 |
8 class Command(BaseCommand): |
9 class Command(BaseCommand): |
9 option_list = BaseCommand.option_list + ( |
10 option_list = BaseCommand.option_list + ( |
10 make_option('--format', default='json', dest='format', |
11 make_option('--format', default='json', dest='format', |
11 help='Specifies the output serialization format for fixtures.'), |
12 help='Specifies the output serialization format for fixtures.'), |
12 make_option('--indent', default=None, dest='indent', type='int', |
13 make_option('--indent', default=None, dest='indent', type='int', |
13 help='Specifies the indent level to use when pretty-printing output'), |
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.'), |
14 make_option('-e', '--exclude', dest='exclude',action='append', default=[], |
18 make_option('-e', '--exclude', dest='exclude',action='append', default=[], |
15 help='App to exclude (use multiple --exclude to exclude multiple apps).'), |
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.'), |
16 ) |
22 ) |
17 help = 'Output the contents of the database as a fixture of the given format.' |
23 help = 'Output the contents of the database as a fixture of the given format.' |
18 args = '[appname ...]' |
24 args = '[appname appname.ModelName ...]' |
19 |
25 |
20 def handle(self, *app_labels, **options): |
26 def handle(self, *app_labels, **options): |
21 from django.db.models import get_app, get_apps, get_models, get_model |
27 from django.db.models import get_app, get_apps, get_models, get_model |
22 |
28 |
23 format = options.get('format','json') |
29 format = options.get('format','json') |
24 indent = options.get('indent',None) |
30 indent = options.get('indent',None) |
|
31 using = options.get('database', DEFAULT_DB_ALIAS) |
|
32 connection = connections[using] |
25 exclude = options.get('exclude',[]) |
33 exclude = options.get('exclude',[]) |
26 show_traceback = options.get('traceback', False) |
34 show_traceback = options.get('traceback', False) |
|
35 use_natural_keys = options.get('use_natural_keys', False) |
27 |
36 |
28 excluded_apps = [get_app(app_label) for app_label in exclude] |
37 excluded_apps = set(get_app(app_label) for app_label in exclude) |
29 |
38 |
30 if len(app_labels) == 0: |
39 if len(app_labels) == 0: |
31 app_list = SortedDict([(app, None) for app in get_apps() if app not in excluded_apps]) |
40 app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps) |
32 else: |
41 else: |
33 app_list = SortedDict() |
42 app_list = SortedDict() |
34 for label in app_labels: |
43 for label in app_labels: |
35 try: |
44 try: |
36 app_label, model_label = label.split('.') |
45 app_label, model_label = label.split('.') |
65 try: |
74 try: |
66 serializers.get_serializer(format) |
75 serializers.get_serializer(format) |
67 except KeyError: |
76 except KeyError: |
68 raise CommandError("Unknown serialization format: %s" % format) |
77 raise CommandError("Unknown serialization format: %s" % format) |
69 |
78 |
|
79 # Now collate the objects to be serialized. |
70 objects = [] |
80 objects = [] |
71 for app, model_list in app_list.items(): |
81 for model in sort_dependencies(app_list.items()): |
72 if model_list is None: |
82 if not model._meta.proxy and router.allow_syncdb(using, model): |
73 model_list = get_models(app) |
83 objects.extend(model._default_manager.using(using).all()) |
74 |
|
75 for model in model_list: |
|
76 if not model._meta.proxy: |
|
77 objects.extend(model._default_manager.all()) |
|
78 |
84 |
79 try: |
85 try: |
80 return serializers.serialize(format, objects, indent=indent) |
86 return serializers.serialize(format, objects, indent=indent, |
|
87 use_natural_keys=use_natural_keys) |
81 except Exception, e: |
88 except Exception, e: |
82 if show_traceback: |
89 if show_traceback: |
83 raise |
90 raise |
84 raise CommandError("Unable to serialize database: %s" % e) |
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 |