From 51be8af70a3a6ea6880f017e1514bb7df5c08c6a Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.anjos@idiap.ch>
Date: Thu, 30 Jun 2016 13:41:55 +0200
Subject: [PATCH] [many] Simplify backup system by using object's natural order
 (c.f. #433)

---
 beat/web/common/models.py                     |   5 +-
 beat/web/experiments/models.py                |   3 +
 beat/web/utils/management/commands/backup.py  |  19 +-
 .../utils/management/commands/xdumpdata.py    | 187 ------------------
 4 files changed, 8 insertions(+), 206 deletions(-)
 delete mode 100644 beat/web/utils/management/commands/xdumpdata.py

diff --git a/beat/web/common/models.py b/beat/web/common/models.py
index 66e680a07..9c5afc4b3 100644
--- a/beat/web/common/models.py
+++ b/beat/web/common/models.py
@@ -381,7 +381,9 @@ class Versionable(Shareable):
 
     class Meta(Shareable.Meta):
         abstract = True
-        ordering = ['name', '-version']
+
+        # setup ordering so that the dump order respects self dependencies
+        ordering = ['previous_version_id', 'fork_of_id', 'id']
 
 
     #_____ Static Methods __________
@@ -552,7 +554,6 @@ class Contribution(Versionable):
 
     class Meta(Versionable.Meta):
         abstract = True
-        ordering = ['author__username', 'name', 'version']
         unique_together = ('author', 'name', 'version')
 
 
diff --git a/beat/web/experiments/models.py b/beat/web/experiments/models.py
index 51b5a60c4..5e7b5a3d8 100644
--- a/beat/web/experiments/models.py
+++ b/beat/web/experiments/models.py
@@ -926,6 +926,9 @@ class Block(models.Model):
     class Meta:
         unique_together = ('experiment', 'name')
 
+        # setup ordering so that the dump order respects self dependencies
+        ordering = ['id']
+
 
     def __str__(self):
         return self.experiment.fullname() + ', ' + self.name + ' (%s)' % self.get_status_display()
diff --git a/beat/web/utils/management/commands/backup.py b/beat/web/utils/management/commands/backup.py
index 48706777b..c8791aeab 100644
--- a/beat/web/utils/management/commands/backup.py
+++ b/beat/web/utils/management/commands/backup.py
@@ -149,7 +149,7 @@ class Command(BaseCommand):
             destfile = os.path.join(tmpdir, 'initial.json')
             logger.info("Dumping initial (unspecified) data -> `%s'", destfile)
             arguments['output'] = destfile #new in Django-1.8.x
-            call_command('xdumpdata', **arguments)
+            call_command('dumpdata', **arguments)
 
             # and backs-up the apps respecting the imposed order
             for app in use_apps:
@@ -158,23 +158,8 @@ class Command(BaseCommand):
                 arguments = copy.deepcopy(dump_arguments)
                 logger.info("Dumping data for `%s' -> `%s'", app, destfile)
 
-                if app in only:
-
-                    app, model = only[app].split('.')
-                    model = apps.get_model(app, model)
-                    order = ('creation_date',)
-
-                    # This will check and correct objects with weird creation
-                    # dates so that the dump order is consistent
-                    while True:
-                        queryset = model.objects.order_by(*order)
-                        err = _check(app, queryset)
-                        if not err: break
-
-                    arguments['primary_keys'] = ','.join([str(k.id) for k in queryset])
-
                 arguments['output'] = destfile #new in Django-1.8.x
-                call_command('xdumpdata', only.get(app, app), **arguments)
+                call_command('dumpdata', only.get(app, app), **arguments)
 
                 # copy prefix data
                 path = os.path.join(settings.PREFIX, app)
diff --git a/beat/web/utils/management/commands/xdumpdata.py b/beat/web/utils/management/commands/xdumpdata.py
deleted file mode 100644
index e79e08049..000000000
--- a/beat/web/utils/management/commands/xdumpdata.py
+++ /dev/null
@@ -1,187 +0,0 @@
-# This file the Django/GitHub equivalent of
-# django/core/management/commands/dumpdata.py. It was copied here so that we
-# can implement primary-key ordering properly for our backup application. If
-# you find this file is outdated, please re-download the appropriate Django
-# version of this file and patch it up again. The patch area, which is quite
-# small, is clearly indicated below.
-#
-# Django (BSD License)
-
-from collections import OrderedDict
-
-from django.apps import apps
-from django.core import serializers
-from django.core.management.base import BaseCommand, CommandError
-from django.db import DEFAULT_DB_ALIAS, router
-
-# #########################################################################
-# Because this file was imported from Django's master, we need this import:
-if not hasattr(serializers, 'sort_dependencies'):
-    # Covers Django 1.7 compatibility
-    from django.core.management.commands.dumpdata import sort_dependencies
-    serializers.sort_dependencies = sort_dependencies
-    routers.allow_migrate_model = routers.allow_migrate
-# #########################################################################
-
-class Command(BaseCommand):
-    help = ("Output the contents of the database as a fixture of the given "
-            "format (using each model's default manager unless --all is "
-            "specified).")
-
-    def add_arguments(self, parser):
-        parser.add_argument('args', metavar='app_label[.ModelName]', nargs='*',
-            help='Restricts dumped data to the specified app_label or app_label.ModelName.')
-        parser.add_argument('--format', default='json', dest='format',
-            help='Specifies the output serialization format for fixtures.')
-        parser.add_argument('--indent', default=None, dest='indent', type=int,
-            help='Specifies the indent level to use when pretty-printing output.')
-        parser.add_argument('--database', action='store', dest='database',
-            default=DEFAULT_DB_ALIAS,
-            help='Nominates a specific database to dump fixtures from. '
-                 'Defaults to the "default" database.')
-        parser.add_argument('-e', '--exclude', dest='exclude', action='append', default=[],
-            help='An app_label or app_label.ModelName to exclude '
-                 '(use multiple --exclude to exclude multiple apps/models).')
-        parser.add_argument('--natural-foreign', action='store_true', dest='use_natural_foreign_keys', default=False,
-            help='Use natural foreign keys if they are available.')
-        parser.add_argument('--natural-primary', action='store_true', dest='use_natural_primary_keys', default=False,
-            help='Use natural primary keys if they are available.')
-        parser.add_argument('-a', '--all', action='store_true', dest='use_base_manager', default=False,
-            help="Use Django's base manager to dump all models stored in the database, "
-                 "including those that would otherwise be filtered or modified by a custom manager.")
-        parser.add_argument('--pks', dest='primary_keys',
-            help="Only dump objects with given primary keys. "
-                 "Accepts a comma separated list of keys. "
-                 "This option will only work when you specify one model.")
-        parser.add_argument('-o', '--output', default=None, dest='output',
-            help='Specifies file to which the output is written.')
-
-    def handle(self, *app_labels, **options):
-        format = options.get('format')
-        indent = options.get('indent')
-        using = options.get('database')
-        excludes = options.get('exclude')
-        output = options.get('output')
-        show_traceback = options.get('traceback')
-        use_natural_foreign_keys = options.get('use_natural_foreign_keys')
-        use_natural_primary_keys = options.get('use_natural_primary_keys')
-        use_base_manager = options.get('use_base_manager')
-        pks = options.get('primary_keys')
-
-        if pks:
-            primary_keys = pks.split(',')
-        else:
-            primary_keys = []
-
-        excluded_apps = set()
-        excluded_models = set()
-        for exclude in excludes:
-            if '.' in exclude:
-                try:
-                    model = apps.get_model(exclude)
-                except LookupError:
-                    raise CommandError('Unknown model in excludes: %s' % exclude)
-                excluded_models.add(model)
-            else:
-                try:
-                    app_config = apps.get_app_config(exclude)
-                except LookupError as e:
-                    raise CommandError(str(e))
-                excluded_apps.add(app_config)
-
-        if len(app_labels) == 0:
-            if primary_keys:
-                raise CommandError("You can only use --pks option with one model")
-            app_list = OrderedDict((app_config, None)
-                for app_config in apps.get_app_configs()
-                if app_config.models_module is not None and app_config not in excluded_apps)
-        else:
-            if len(app_labels) > 1 and primary_keys:
-                raise CommandError("You can only use --pks option with one model")
-            app_list = OrderedDict()
-            for label in app_labels:
-                try:
-                    app_label, model_label = label.split('.')
-                    try:
-                        app_config = apps.get_app_config(app_label)
-                    except LookupError as e:
-                        raise CommandError(str(e))
-                    if app_config.models_module is None or app_config in excluded_apps:
-                        continue
-                    try:
-                        model = app_config.get_model(model_label)
-                    except LookupError:
-                        raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
-
-                    app_list_value = app_list.setdefault(app_config, [])
-
-                    # We may have previously seen a "all-models" request for
-                    # this app (no model qualifier was given). In this case
-                    # there is no need adding specific models to the list.
-                    if app_list_value is not None:
-                        if model not in app_list_value:
-                            app_list_value.append(model)
-                except ValueError:
-                    if primary_keys:
-                        raise CommandError("You can only use --pks option with one model")
-                    # This is just an app - no model qualifier
-                    app_label = label
-                    try:
-                        app_config = apps.get_app_config(app_label)
-                    except LookupError as e:
-                        raise CommandError(str(e))
-                    if app_config.models_module is None or app_config in excluded_apps:
-                        continue
-                    app_list[app_config] = None
-
-        # Check that the serialization format exists; this is a shortcut to
-        # avoid collating all the objects and _then_ failing.
-        if format not in serializers.get_public_serializer_formats():
-            try:
-                serializers.get_serializer(format)
-            except serializers.SerializerDoesNotExist:
-                pass
-
-            raise CommandError("Unknown serialization format: %s" % format)
-
-        def get_objects():
-            # Collate the objects to be serialized.
-            for model in serializers.sort_dependencies(app_list.items()):
-                if model in excluded_models:
-                    continue
-                if not model._meta.proxy and router.allow_migrate_model(using, model):
-                    if use_base_manager:
-                        objects = model._base_manager
-                    else:
-                        objects = model._default_manager
-
-                    queryset = objects.using(using).order_by(model._meta.pk.name)
-                    if primary_keys:
-                        # ###################################################
-                        # patch for BEAT starts here: order objects
-                        # ###################################################
-                        for pk in [int(k) for k in primary_keys]:
-                            yield queryset.get(pk=pk)
-
-                    else:
-                        # ###################################################
-                        # patch for BEAT ends here
-                        # ###################################################
-                        for obj in queryset.iterator():
-                            yield obj
-
-        try:
-            self.stdout.ending = None
-            stream = open(output, 'w') if output else None
-            try:
-                serializers.serialize(format, get_objects(), indent=indent,
-                        use_natural_foreign_keys=use_natural_foreign_keys,
-                        use_natural_primary_keys=use_natural_primary_keys,
-                        stream=stream or self.stdout)
-            finally:
-                if stream:
-                    stream.close()
-        except Exception as e:
-            if show_traceback:
-                raise
-            raise CommandError("Unable to serialize database: %s" % e)
-- 
GitLab