web/lib/django/core/management/commands/dumpdata.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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