diff --git a/beat/web/databases/admin.py b/beat/web/databases/admin.py
index dfd9f4c8afff9cca17ca82b2a20b9556357d444a..32daf554d3763a3738303ade03696bbec765717a 100644
--- a/beat/web/databases/admin.py
+++ b/beat/web/databases/admin.py
@@ -33,8 +33,9 @@ from django.utils import six
 from .models import Database as DatabaseModel
 from .models import DatabaseProtocol as DatabaseProtocolModel
 from .models import DatabaseSet as DatabaseSetModel
+from .models import DatabaseSetOutput as DatabaseSetOutputModel
 from .models import DatabaseSetTemplate as DatabaseSetTemplateModel
-from .models import DatabaseOutput as DatabaseOutputModel
+from .models import DatabaseSetTemplateOutput as DatabaseSetTemplateOutputModel
 from .models import validate_database
 
 from ..ui.forms import CodeMirrorJSONFileField, CodeMirrorRSTFileField, \
@@ -234,9 +235,9 @@ admin.site.register(DatabaseModel, Database)
 #------------------------------------------------
 
 
-class DatabaseOutputInline(admin.TabularInline):
+class DatabaseSetTemplateOutputInline(admin.TabularInline):
 
-    model           = DatabaseOutputModel
+    model           = DatabaseSetTemplateOutputModel
     extra           = 0
     ordering        = ('name',)
 
@@ -248,7 +249,7 @@ class DatabaseSetTemplate(admin.ModelAdmin):
     list_display_links  = ('id', 'name')
 
     inlines = [
-            DatabaseOutputInline,
+            DatabaseSetTemplateOutputInline,
             ]
 
 admin.site.register(DatabaseSetTemplateModel, DatabaseSetTemplate)
@@ -257,6 +258,13 @@ admin.site.register(DatabaseSetTemplateModel, DatabaseSetTemplate)
 #------------------------------------------------
 
 
+class DatabaseSetOutputInline(admin.TabularInline):
+
+    model           = DatabaseSetOutputModel
+    extra           = 0
+    ordering        = ('hash',)
+
+
 class DatabaseSet(admin.ModelAdmin):
 
     list_display        = ('id', 'protocol', 'name', 'template')
@@ -268,4 +276,8 @@ class DatabaseSet(admin.ModelAdmin):
                            'protocol__name']
     list_display_links  = ('id', 'name')
 
+    inlines = [
+            DatabaseSetOutputInline,
+            ]
+
 admin.site.register(DatabaseSetModel, DatabaseSet)
diff --git a/beat/web/databases/models.py b/beat/web/databases/models.py
index 87166515863bbda195f907488d7dc3bcf3896213..64fa485e86db3dd3b7c87e94840781effe04aaa3 100755
--- a/beat/web/databases/models.py
+++ b/beat/web/databases/models.py
@@ -263,6 +263,8 @@ class Database(Versionable):
             result.extend(database_protocol.all_needed_dataformats())
         return list(set(result))
 
+    def core(self):
+        return validate_database(self.declaration)
 
     #_____ Properties __________
 
@@ -275,173 +277,6 @@ class Database(Versionable):
 #----------------------------------------------------------
 
 
-@receiver(models.signals.pre_delete, sender=Database)
-def delete_protocols(sender, **kwargs):
-    instance = kwargs['instance']
-    instance.protocols.all().delete()
-
-
-#----------------------------------------------------------
-
-
-# These two auto-delete files from filesystem when they are unneeded:
-@receiver(models.signals.post_delete, sender=Database)
-def auto_delete_file_on_delete(sender, instance, **kwargs):
-    """Deletes file from filesystem when ``Database`` object is deleted.
-    """
-    if instance.declaration_file:
-        instance.declaration_file.delete(save=False)
-
-    if instance.source_code_file:
-        instance.source_code_file.delete(save=False)
-
-    if instance.description_file:
-        instance.description_file.delete(save=False)
-
-
-@receiver(models.signals.pre_save, sender=Database)
-def auto_delete_file_on_change(sender, instance, **kwargs):
-    """Deletes file from filesystem when ``Database`` object is changed."""
-
-    if not instance.pk:
-        return False
-
-    try:
-        old_file = Database.objects.get(pk=instance.pk).declaration_file
-    except Database.DoesNotExist:
-        return False
-
-    if old_file != instance.declaration_file:
-        old_file.delete(save=False)
-
-    try:
-        old_code = Database.objects.get(pk=instance.pk).source_code_file
-    except Database.DoesNotExist:
-        return False
-
-    if old_code != instance.source_code_file:
-        old_code.delete(save=False)
-
-    try:
-        old_descr = Database.objects.get(pk=instance.pk).description_file
-    except Database.DoesNotExist:
-        return False
-
-    if old_descr != instance.description_file:
-        old_descr.delete(save=False)
-
-
-#----------------------------------------------------------
-
-
-@receiver(models.signals.post_save, sender=Database)
-def refresh_protocols(sender, instance, **kwargs):
-    """Refreshes changed protocols"""
-
-    try:
-        json_declaration = instance.declaration
-
-        protocols = DatabaseProtocol.objects.filter(
-                database__name=instance.name,
-                database__version=instance.version,
-            )
-
-        existing = set((k.name, k.set_template_basename()) for k in protocols)
-        new_objects = set((k['name'], k['template']) for k in json_declaration['protocols'])
-
-        for protocol_name, template in existing - new_objects:
-            # notice: no need to worry, this will clean-up all the rest
-            protocols.get(name__iexact=protocol_name).delete()
-
-        json_protocols = dict([(k['name'], k) for k in json_declaration['protocols']])
-
-        for protocol_name, template in new_objects - existing:
-            protocol = DatabaseProtocol(name=protocol_name, database=instance)
-            protocol.save()
-
-            json_protocol = json_protocols[protocol_name]
-
-            # creates all the template sets, outputs, etc for the first time
-            for set_attr in json_protocol['sets']:
-
-                tset_name = json_protocol['template'] + '__' + set_attr['template']
-
-                dataset_template = DatabaseSetTemplate.objects.filter(name=tset_name)
-                if not dataset_template: #create
-                    dataset_template = DatabaseSetTemplate(name=tset_name)
-                    dataset_template.save()
-                else:
-                    dataset_template = dataset_template[0]
-
-                # Create the databaset
-                dataset_set = DatabaseSet.objects.filter(
-                    name = set_attr['name'],
-                    template = dataset_template,
-                    protocol = protocol,
-                    )
-
-                if not dataset_set: #create
-                    dataset_set = DatabaseSet(
-                        name = set_attr['name'],
-                        template = dataset_template,
-                        protocol = protocol,
-                        )
-                    dataset_set.save()
-
-                    # Create the database set output
-                    for output_name, format_name in set_attr['outputs'].items():
-                        if len(format_name.split('/')) != 3:
-                            raise SyntaxError(
-                                    "Dataformat should be named following the style " \
-                                    "`<user>/<format>/<version>', the " \
-                                    "value `%s' is not valid" % (
-                                        format_name,
-                                        )
-                                    )
-                        (author, name, version) = format_name.split('/')
-                        dataformats = DataFormat.objects.filter(
-                            author__username=author,
-                            name=name,
-                            version=version,
-                            )
-
-                        # TODO: Remove this when validation works (see comments)
-                        if len(dataformats) != 1:
-                            raise SyntaxError(
-                                "Could not find dataformat named `%s' to set" \
-                                "output `%s' of template `%s' for protocol" \
-                                "`%s' of database `%s'", (
-                                  format_name,
-                                  output_name,
-                                  dataset_template.name,
-                                  protocol_name,
-                                  instance.name,
-                                  )
-                                )
-                            return
-
-                        database_output = DatabaseOutput.objects.filter(
-                            name=output_name,
-                            template=dataset_template,
-                            dataformat=dataformats[0],
-                            )
-
-                        if not database_output: # create
-                            database_output = DatabaseOutput(
-                                name=output_name,
-                                template=dataset_template,
-                                dataformat=dataformats[0],
-                                )
-                            database_output.save()
-
-    except Exception:
-        instance.delete() #do we need this or is it auto-rolled back?
-        raise
-
-
-#----------------------------------------------------------
-
-
 class DatabaseProtocolManager(models.Manager):
 
     def get_by_natural_key(self, database_name, database_version, name):
@@ -454,10 +289,11 @@ class DatabaseProtocolManager(models.Manager):
 
 class DatabaseProtocol(models.Model):
 
-    objects     = DatabaseProtocolManager()
+    objects = DatabaseProtocolManager()
 
-    database    = models.ForeignKey(Database, related_name='protocols')
-    name        = models.CharField(max_length=200, blank=True)
+    database = models.ForeignKey(Database, related_name='protocols',
+        on_delete=models.CASCADE)
+    name = models.CharField(max_length=200, blank=True)
 
     class Meta:
         unique_together = ('database', 'name')
@@ -496,16 +332,6 @@ class DatabaseProtocol(models.Model):
 #----------------------------------------------------------
 
 
-@receiver(models.signals.pre_delete, sender=DatabaseProtocol)
-def delete_sets(sender, **kwargs):
-
-    instance = kwargs['instance']
-    instance.sets.all().delete()
-
-
-#----------------------------------------------------------
-
-
 class DatabaseSetTemplateManager(models.Manager):
 
     def get_by_natural_key(self, name):
@@ -528,15 +354,6 @@ class DatabaseSetTemplate(models.Model):
 #----------------------------------------------------------
 
 
-@receiver(models.signals.pre_delete, sender=DatabaseSetTemplate)
-def delete_outputs(sender, **kwargs):
-
-    instance = kwargs['instance']
-    instance.outputs.all().delete()
-
-
-#----------------------------------------------------------
-
 class DatabaseSetManager(models.Manager):
 
     def get_by_natural_key(self, database_name, database_version, protocol_name, name, template_name):
@@ -553,9 +370,11 @@ class DatabaseSet(models.Model):
 
     objects     = DatabaseSetManager()
 
-    protocol    = models.ForeignKey(DatabaseProtocol, related_name='sets')
+    protocol    = models.ForeignKey(DatabaseProtocol, related_name='sets',
+        on_delete=models.CASCADE)
     name        = models.CharField(max_length=200, blank=True)
-    template    = models.ForeignKey(DatabaseSetTemplate, related_name='sets')
+    template    = models.ForeignKey(DatabaseSetTemplate, related_name='sets',
+        on_delete=models.CASCADE)
 
     class Meta:
         unique_together = ('protocol', 'name', 'template')
@@ -587,29 +406,46 @@ class DatabaseSet(models.Model):
 #----------------------------------------------------------
 
 
-@receiver(models.signals.post_delete, sender=DatabaseSet)
-def delete_empty_template_sets(sender, **kwargs):
+class DatabaseSetTemplateOutput(models.Model):
+    template        = models.ForeignKey(DatabaseSetTemplate,
+        related_name='outputs', on_delete=models.CASCADE)
+    name            = models.CharField(max_length=200)
+    dataformat      = models.ForeignKey(DataFormat,
+        related_name='database_outputs', on_delete=models.CASCADE)
+
+    class Meta:
+        unique_together = ('template', 'name', 'dataformat')
+
+    def __str__(self):
+        return self.fullname()
 
-    instance = kwargs['instance']
-    try:
-        if not instance.template.sets.all(): instance.template.delete()
-    except:
-        pass
+    def fullname(self):
+        return self.template.name + '.' + self.name
 
 
 #----------------------------------------------------------
 
 
-class DatabaseOutput(models.Model):
-    template        = models.ForeignKey(DatabaseSetTemplate, related_name='outputs')
-    name            = models.CharField(max_length=200)
-    dataformat      = models.ForeignKey(DataFormat, related_name='database_outputs')
-
-    class Meta:
-        unique_together = ('template', 'name', 'dataformat')
+class DatabaseSetOutput(models.Model):
+    template = models.ForeignKey(DatabaseSetTemplateOutput,
+        related_name='instances', on_delete=models.CASCADE)
+    set = models.ForeignKey(DatabaseSet, related_name='outputs',
+        on_delete=models.CASCADE)
+    hash = models.CharField(max_length=64, unique=True)
 
     def __str__(self):
         return self.fullname()
 
     def fullname(self):
-        return self.template.name + '.' + self.name
+        return '%s.%s.%s.%s' % (
+            self.set.protocol.database.fullname(),
+            self.set.protocol.name,
+            self.set.name,
+            self.name,
+            )
+
+    def all_referenced_dataformats(self):
+        return self.template.all_referenced_dataformats()
+
+    def all_needed_dataformats(self):
+        return self.template.all_needed_dataformats()
diff --git a/beat/web/databases/signals.py b/beat/web/databases/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf8ebad4207de36fef3802ff4d086292dc5881e9
--- /dev/null
+++ b/beat/web/databases/signals.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+
+###############################################################################
+#                                                                             #
+# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/           #
+# Contact: beat.support@idiap.ch                                              #
+#                                                                             #
+# This file is part of the beat.web module of the BEAT platform.              #
+#                                                                             #
+# Commercial License Usage                                                    #
+# Licensees holding valid commercial BEAT licenses may use this file in       #
+# accordance with the terms contained in a written agreement between you      #
+# and Idiap. For further information contact tto@idiap.ch                     #
+#                                                                             #
+# Alternatively, this file may be used under the terms of the GNU Affero      #
+# Public License version 3 as published by the Free Software and appearing    #
+# in the file LICENSE.AGPL included in the packaging of this file.            #
+# The BEAT platform is distributed in the hope that it will be useful, but    #
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  #
+# or FITNESS FOR A PARTICULAR PURPOSE.                                        #
+#                                                                             #
+# You should have received a copy of the GNU Affero Public License along      #
+# with the BEAT platform. If not, see http://www.gnu.org/licenses/.           #
+#                                                                             #
+###############################################################################
+
+
+from django.db import models
+
+from ..dataformats.models import DataFormat
+
+from .models import Database, DatabaseProtocol, DatabaseSet
+from .models import DatabaseSetTemplate, DatabaseSetTemplateOutput
+from .models import DatabaseSetOutput
+
+
+@receiver(models.signals.post_delete, sender=Database)
+def auto_delete_file_on_delete(sender, instance, **kwargs):
+    """Deletes file from filesystem when ``Database`` object is deleted.
+    """
+    if instance.declaration_file:
+        instance.declaration_file.delete(save=False)
+
+    if instance.source_code_file:
+        instance.source_code_file.delete(save=False)
+
+    if instance.description_file:
+        instance.description_file.delete(save=False)
+
+
+@receiver(models.signals.pre_save, sender=Database)
+def auto_delete_file_on_change(sender, instance, **kwargs):
+    """Deletes file from filesystem when ``Database`` object is changed."""
+
+    if not instance.pk:
+        return False
+
+    try:
+        old_file = Database.objects.get(pk=instance.pk).declaration_file
+    except Database.DoesNotExist:
+        return False
+
+    if old_file != instance.declaration_file:
+        old_file.delete(save=False)
+
+    try:
+        old_code = Database.objects.get(pk=instance.pk).source_code_file
+    except Database.DoesNotExist:
+        return False
+
+    if old_code != instance.source_code_file:
+        old_code.delete(save=False)
+
+    try:
+        old_descr = Database.objects.get(pk=instance.pk).description_file
+    except Database.DoesNotExist:
+        return False
+
+    if old_descr != instance.description_file:
+        old_descr.delete(save=False)
+
+
+@receiver(models.signals.post_save, sender=Database)
+def refresh_protocols(sender, instance, **kwargs):
+    """Refreshes changed protocols"""
+
+    try:
+        core = instance.core()
+
+        protocols = DatabaseProtocol.objects.filter(
+                database__name=instance.name,
+                database__version=instance.version,
+            )
+
+        existing = set((k.name, k.set_template_basename()) for k in protocols)
+        new_objects = set((k['name'], k['template']) for k in core.protocols()])
+
+        for protocol_name, template in existing - new_objects:
+            # notice: no need to worry, this will clean-up all the rest
+            protocols.get(name__iexact=protocol_name).delete()
+
+        json_protocols = dict([(k['name'], k) for k in core.protocols()]])
+
+        for protocol_name, template in new_objects - existing:
+            protocol = DatabaseProtocol(name=protocol_name, database=instance)
+            protocol.save()
+
+            json_protocol = json_protocols[protocol_name]
+
+            # creates all the template sets, outputs, etc for the first time
+            for set_attr in json_protocol['sets']:
+
+                tset_name = json_protocol['template'] + '__' + set_attr['template']
+
+                dataset_template = DatabaseSetTemplate.objects.filter(name=tset_name)
+                if not dataset_template: #create
+                    dataset_template = DatabaseSetTemplate(name=tset_name)
+                    dataset_template.save()
+                else:
+                    dataset_template = dataset_template[0]
+
+                # Create the database set
+                dataset = DatabaseSet.objects.filter(
+                    name = set_attr['name'],
+                    template = dataset_template,
+                    protocol = protocol,
+                    )
+
+                if not dataset: #create
+                    dataset = DatabaseSet(
+                        name = set_attr['name'],
+                        template = dataset_template,
+                        protocol = protocol,
+                        )
+                    dataset.save()
+
+                    # Create the database set template output
+                    for output_name, format_name in set_attr['outputs'].items():
+                        if len(format_name.split('/')) != 3:
+                            raise SyntaxError(
+                                "Dataformat should be named following the " \
+                                "style `<username>/<format>/<version>', the " \
+                                "value `%s' is not valid" % (format_name,)
+                                )
+                        (author, name, version) = format_name.split('/')
+                        dataformats = DataFormat.objects.filter(
+                            author__username=author,
+                            name=name,
+                            version=version,
+                            )
+
+                        # TODO: Remove this when validation works (see comments)
+                        if len(dataformats) != 1:
+                            raise SyntaxError(
+                                "Could not find dataformat named `%s' to set" \
+                                "output `%s' of template `%s' for protocol" \
+                                "`%s' of database `%s'", (
+                                  format_name,
+                                  output_name,
+                                  dataset_template.name,
+                                  protocol_name,
+                                  instance.name,
+                                  )
+                                )
+                            return
+
+                        database_template_output = \
+                            DatabaseSetTemplateOutput.objects.filter(
+                                name=output_name,
+                                template=dataset_template,
+                                dataformat=dataformats[0],
+                                )
+
+                        if not database_template_output: # create
+                            database_template_output = \
+                                DatabaseSetTemplateOutput(
+                                    name=output_name,
+                                    template=dataset_template,
+                                    dataformat=dataformats[0],
+                                    )
+                            database_template_output.save()
+
+                        else:
+                            database_template_output = \
+                                database_template_output[0]
+
+                        # Create the database template output
+                        hash = core.hash_output(protocol.name,
+                            dataset.name, output_name)
+                        dataset_output = \
+                            DatabaseSetOutput.objects.filter(hash=hash)
+
+                        if not dataset_output: # create
+                            dataset_output = DatabaseSetOutput(
+                                template=database_template_output,
+                                set=dataset,
+                                hash=hash,
+                                )
+                            dataset_output.save()
+
+    except Exception:
+        instance.delete() #do we need this or is it auto-rolled back?
+        raise
+
+
+@receiver(models.signals.post_delete, sender=DatabaseSet)
+def delete_empty_template_sets(sender, **kwargs):
+
+    instance = kwargs['instance']
+    try:
+        if not instance.template.sets.all(): instance.template.delete()
+    except:
+        pass