From 38a9e29d1c5936cd1a2f1d07ef479db095cde336 Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.dos.anjos@gmail.com>
Date: Wed, 29 Jun 2016 15:18:26 +0200
Subject: [PATCH] Fix natural-key usage throughout scheduler branch mods
 (closes #433)

---
 beat/web/backend/models.py                   | 24 +++++++++++---------
 beat/web/backend/utils.py                    |  8 +++----
 beat/web/databases/models.py                 | 11 +++++++++
 beat/web/experiments/models.py               | 20 ++++++++++------
 beat/web/utils/management/commands/backup.py | 11 ++++++---
 5 files changed, 49 insertions(+), 25 deletions(-)

diff --git a/beat/web/backend/models.py b/beat/web/backend/models.py
index 4b6557714..b015b4c99 100644
--- a/beat/web/backend/models.py
+++ b/beat/web/backend/models.py
@@ -63,12 +63,12 @@ from ..statistics.utils import updateStatistics
 
 class EnvironmentManager(ShareableManager):
 
-    def get_by_natural_key(self, key):
-        name, version = key.rsplit(' ', 1)
-        return self.get(
-            name=name,
-            version=version[1:-1],
-            )
+    def get_by_natural_key(self, name, version):
+        return self.get(name=name, version=version)
+
+    def get_by_fullname(self, fullname):
+        name, version = fullname.rsplit(' ', 1)
+        return self.get_by_natural_key(name, version[1:-1])
 
 
 class Environment(Shareable):
@@ -128,7 +128,7 @@ class Environment(Shareable):
 
 
     def natural_key(self):
-        return self.fullname()
+        return (self.name, self.version)
 
 
     #_____ Utilities __________
@@ -229,7 +229,7 @@ class Worker(models.Model):
 
 
     def natural_key(self):
-        return self.name
+        return (self.name,)
 
 
     def get_admin_change_url(self):
@@ -303,7 +303,7 @@ class Worker(models.Model):
         wishlist = Environment.objects.filter(queues__in=queues, active=True)
         wishlist = wishlist.order_by('id').distinct()
 
-        required = [k.natural_key() for k in wishlist]
+        required = [k.fullname() for k in wishlist]
         missing = [k for k in required if k not in environments]
         unused = [k for k in environments if k not in required]
 
@@ -532,6 +532,8 @@ class Queue(models.Model):
         related_name='queues',
         )
 
+    objects = QueueManager()
+
     #_____ Meta parameters __________
 
     class Meta:
@@ -553,7 +555,7 @@ class Queue(models.Model):
 
 
     def natural_key(self):
-        return self.name
+        return (self.name,)
 
 
     def get_admin_change_url(self):
@@ -620,7 +622,7 @@ class Queue(models.Model):
             'time-limit': self.time_limit,
             'cores-per-slot': self.cores_per_slot,
             'max-slots-per-user': self.max_slots_per_user,
-            'environments': [k.natural_key() for k in self.environments.all()],
+            'environments': [k.fullname() for k in self.environments.all()],
             'slots': dict([(s.worker.name, dict(quantity=s.quantity,
               priority=s.priority)) for s in self.slots.all()]),
             'groups': [k.name for k in Group.objects.all() if 'can_access' in get_perms(k, self)]
diff --git a/beat/web/backend/utils.py b/beat/web/backend/utils.py
index 02c91a991..bda58d941 100644
--- a/beat/web/backend/utils.py
+++ b/beat/web/backend/utils.py
@@ -280,7 +280,7 @@ def setup_backend(d):
 
         # Associates environments with queues
         for envkey in attrs['environments']:
-            env = Environment.objects.get_by_natural_key(envkey)
+            env = Environment.objects.get_by_fullname(envkey)
             logger.info("Appending `%s' to `%s'...", env, queue)
             queue.environments.add(env)
 
@@ -317,7 +317,7 @@ def setup_backend(d):
         # 10.3 Associate and dissociate environments
         queue.environments.clear()
         for e in attrs['environments']:
-            env = Environment.objects.get_by_natural_key(e)
+            env = Environment.objects.get_by_fullname(e)
             logger.info("Appending `%s' to `%s'...", env, queue)
             queue.environments.add(env)
 
@@ -369,7 +369,7 @@ def find_environments(paths=None):
   Returns:
 
     dict: A dictionary containing each environment available using as key the
-      natural key for environments (i.e., ``name (version)``) and as values
+      fullname for environments (i.e., ``name (version)``) and as values
       another dictionary with these keys:
 
         * name: The environment name (str)
@@ -407,5 +407,5 @@ def pick_execute(split, environments):
     """Resolves the path to the ``execute`` program to use for the split"""
 
     # Check we have a compatible environment to execute the user algorithm
-    envinfo = environments.get(split.job.block.environment.natural_key())
+    envinfo = environments.get(split.job.block.environment.fullname())
     return envinfo['execute'] if envinfo else None
diff --git a/beat/web/databases/models.py b/beat/web/databases/models.py
index 1351621e1..01bb490b1 100755
--- a/beat/web/databases/models.py
+++ b/beat/web/databases/models.py
@@ -422,6 +422,12 @@ class DatabaseSetTemplateOutput(models.Model):
 #----------------------------------------------------------
 
 
+class DatabaseSetOutputManager(models.Manager):
+
+    def get_by_natural_key(self, hash):
+        return self.get(hash=hash)
+
+
 class DatabaseSetOutput(models.Model):
     template = models.ForeignKey(DatabaseSetTemplateOutput,
         related_name='instances', on_delete=models.CASCADE)
@@ -429,6 +435,8 @@ class DatabaseSetOutput(models.Model):
         on_delete=models.CASCADE)
     hash = models.CharField(max_length=64, unique=True)
 
+    objects = DatabaseSetOutputManager()
+
     def __str__(self):
         return self.fullname()
 
@@ -445,3 +453,6 @@ class DatabaseSetOutput(models.Model):
 
     def all_needed_dataformats(self):
         return self.template.all_needed_dataformats()
+
+    def natural_key(self):
+        return (self.hash,)
diff --git a/beat/web/experiments/models.py b/beat/web/experiments/models.py
index 67af2255b..51b5a60c4 100644
--- a/beat/web/experiments/models.py
+++ b/beat/web/experiments/models.py
@@ -1253,7 +1253,7 @@ class CachedFile(models.Model):
 
 
     def natural_key(self):
-        return self.hash
+        return (self.hash,)
 
 
     def path(self):
@@ -1310,12 +1310,16 @@ class CachedFile(models.Model):
 
 class BlockInputManager(models.Manager):
 
-    def get_by_natural_key(self, hash):
-        candidate = self.filter(cache__hash=hash)
-        if candidate:
-            return candidate[0]
+    def get_by_natural_key(self, name, experiment_author,
+        toolchain_author, toolchain_name,
+        toolchain_version, experiment_name, cache_hash, database_hash):
+        block = Block.objects.get_by_natural_key(name, experiment_author,
+            toolchain_author, toolchain_name, toolchain_version,
+            experiment_name)
+        if cache_hash:
+            return self.get(cache__hash=cache_hash, block=block)
         else:
-            return self.get(database__hash=hash)
+            return self.get(database__hash=database_hash, block=block)
 
 
 class BlockInput(models.Model):
@@ -1337,7 +1341,9 @@ class BlockInput(models.Model):
     objects = BlockInputManager()
 
     def natural_key(self):
-        return self.has,
+        cache_hash = self.cache and self.cache.hash
+        database_hash = self.database and self.database.hash
+        return self.block.natural_key() + (cache_hash, database_hash)
 
 
 #----------------------------------------------------------
diff --git a/beat/web/utils/management/commands/backup.py b/beat/web/utils/management/commands/backup.py
index 5df5374de..bc3aa30ce 100644
--- a/beat/web/utils/management/commands/backup.py
+++ b/beat/web/utils/management/commands/backup.py
@@ -40,7 +40,7 @@ import datetime
 from django.core.management import call_command
 from django.core.management.base import BaseCommand
 from django.conf import settings
-from django.apps import apps
+from django.apps import apps, registry
 
 from ....import __version__
 
@@ -133,19 +133,24 @@ class Command(BaseCommand):
                     ],
                 )
 
+        # remove uninstalled apps
+        global APPS
+        installed_apps = registry.apps.all_models.keys()
+        use_apps = [app for app in APPS if app in installed_apps]
+
         try:
             tmpdir = tempfile.mkdtemp('.backup', 'beat.web-')
 
             # backs-up everything else first
             arguments = copy.deepcopy(dump_arguments)
-            arguments['exclude'] += APPS
+            arguments['exclude'] += use_apps
             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)
 
             # and backs-up the apps respecting the imposed order
-            for app in APPS:
+            for app in use_apps:
 
                 destfile = os.path.join(tmpdir, '%s.json' % app)
                 arguments = copy.deepcopy(dump_arguments)
-- 
GitLab