diff --git a/beat/web/databases/__init__.py b/beat/web/databases/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..93a15eb13994a4273d1626fb9e0113696b6fb263 100644 --- a/beat/web/databases/__init__.py +++ b/beat/web/databases/__init__.py @@ -0,0 +1,28 @@ +#!/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/. # +# # +############################################################################### + +default_app_config = 'beat.web.databases.apps.DatabasesConfig' diff --git a/beat/web/databases/admin.py b/beat/web/databases/admin.py index 32daf554d3763a3738303ade03696bbec765717a..06d55ad34a83d5bcc268766c19dcf6d778d4b479 100644 --- a/beat/web/databases/admin.py +++ b/beat/web/databases/admin.py @@ -240,6 +240,13 @@ class DatabaseSetTemplateOutputInline(admin.TabularInline): model = DatabaseSetTemplateOutputModel extra = 0 ordering = ('name',) + readonly_fields = ('name', 'dataformat') + + def has_delete_permission(self, request, obj=None): + return False + + def has_add_permission(self, request): + return False class DatabaseSetTemplate(admin.ModelAdmin): @@ -247,11 +254,19 @@ class DatabaseSetTemplate(admin.ModelAdmin): list_display = ('id', 'name') search_fields = ['name'] list_display_links = ('id', 'name') + readonly_fields = ('name',) inlines = [ DatabaseSetTemplateOutputInline, ] + def has_delete_permission(self, request, obj=None): + return False + + def has_add_permission(self, request): + return False + + admin.site.register(DatabaseSetTemplateModel, DatabaseSetTemplate) @@ -263,6 +278,13 @@ class DatabaseSetOutputInline(admin.TabularInline): model = DatabaseSetOutputModel extra = 0 ordering = ('hash',) + readonly_fields = ('hash', 'template') + + def has_delete_permission(self, request, obj=None): + return False + + def has_add_permission(self, request): + return False class DatabaseSet(admin.ModelAdmin): @@ -271,13 +293,19 @@ class DatabaseSet(admin.ModelAdmin): search_fields = ['name', 'template__name', 'protocol__database__name', - 'protocol__database__short_description', - 'protocol__database__description', 'protocol__name'] list_display_links = ('id', 'name') + readonly_fields = ('name', 'template', 'protocol') inlines = [ DatabaseSetOutputInline, ] + def has_delete_permission(self, request, obj=None): + return False + + def has_add_permission(self, request): + return False + + admin.site.register(DatabaseSetModel, DatabaseSet) diff --git a/beat/web/databases/apps.py b/beat/web/databases/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..fd94461ab2849a1702de37129f7232f0690b97ad --- /dev/null +++ b/beat/web/databases/apps.py @@ -0,0 +1,37 @@ +#!/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 ..common.apps import CommonAppConfig +from django.utils.translation import ugettext_lazy as _ + +class DatabasesConfig(CommonAppConfig): + name = 'beat.web.databases' + verbose_name = _('Databases') + + def ready(self): + super(DatabasesConfig, self).ready() + from . import signals diff --git a/beat/web/databases/migrations/0002_auto_20160329_1733.py b/beat/web/databases/migrations/0002_auto_20160329_1733.py new file mode 100644 index 0000000000000000000000000000000000000000..4264e2be61b2b94c75157c8caa64c1cbb84b9111 --- /dev/null +++ b/beat/web/databases/migrations/0002_auto_20160329_1733.py @@ -0,0 +1,91 @@ +# -*- coding: 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 __future__ import unicode_literals + +from django.db import migrations, models + +from ...common.models import get_declaration + +from ..models import validate_database + +import logging +logger = logging.getLogger(__name__) + + +def refresh_databases(apps, schema_editor): + '''Refreshes each database so datasets/outputs are recreated''' + + Database = apps.get_model("databases", "Database") + DatabaseSetOutput = apps.get_model("databases", "DatabaseSetOutput") + + Database.declaration = property(get_declaration) + Database.fullname = lambda self: '%s/%d' % (self.name, self.version) + + print('') + + for db in Database.objects.order_by('id'): + print("Refreshing protocols for database `%s'..." % db.fullname()) + core = validate_database(db.declaration) + core.name = db.fullname() + for proto in db.protocols.all(): + for set in proto.sets.all(): + for output in set.template.outputs.all(): + try: + DatabaseSetOutput(template=output, set=set, + hash=core.hash_output(proto.name, set.name, + output.name)).save() + except KeyError: + logger.warn('Database output %s/%d.%s.%s.%s does ' \ + 'not exist' % (db.name, db.version, proto.name, + set.name, output.name)) + continue + + +class Migration(migrations.Migration): + + dependencies = [ + ('dataformats', '0001_initial'), + ('databases', '0001_initial'), + ] + + operations = [ + migrations.RenameModel('DatabaseOutput', 'DatabaseSetTemplateOutput'), + migrations.AlterUniqueTogether( + name='databasesettemplateoutput', + unique_together=set([('template', 'name')]), + ), + migrations.CreateModel( + name='DatabaseSetOutput', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('hash', models.CharField(unique=True, max_length=64)), + ('set', models.ForeignKey(related_name='outputs', to='databases.DatabaseSet')), + ('template', models.ForeignKey(related_name='instances', to='databases.DatabaseSetTemplateOutput')), + ], + ), + migrations.RunPython(refresh_databases), + ] diff --git a/beat/web/databases/models.py b/beat/web/databases/models.py index 64fa485e86db3dd3b7c87e94840781effe04aaa3..1351621e1bd87ed7b2fcf9ba9271d3c145ff8def 100755 --- a/beat/web/databases/models.py +++ b/beat/web/databases/models.py @@ -31,7 +31,6 @@ import simplejson from django.db import models from django.conf import settings -from django.dispatch import receiver from django.core.urlresolvers import reverse import beat.core.database @@ -263,9 +262,6 @@ class Database(Versionable): result.extend(database_protocol.all_needed_dataformats()) return list(set(result)) - def core(self): - return validate_database(self.declaration) - #_____ Properties __________ description = property(get_description, set_description) @@ -414,7 +410,7 @@ class DatabaseSetTemplateOutput(models.Model): related_name='database_outputs', on_delete=models.CASCADE) class Meta: - unique_together = ('template', 'name', 'dataformat') + unique_together = ('template', 'name') def __str__(self): return self.fullname() @@ -441,7 +437,7 @@ class DatabaseSetOutput(models.Model): self.set.protocol.database.fullname(), self.set.protocol.name, self.set.name, - self.name, + self.template.name, ) def all_referenced_dataformats(self): diff --git a/beat/web/databases/signals.py b/beat/web/databases/signals.py index cf8ebad4207de36fef3802ff4d086292dc5881e9..474c719ba27387a6b57fd02ec0f8e73e1a585dc3 100644 --- a/beat/web/databases/signals.py +++ b/beat/web/databases/signals.py @@ -27,6 +27,7 @@ from django.db import models +from django.dispatch import receiver from ..dataformats.models import DataFormat @@ -34,6 +35,8 @@ from .models import Database, DatabaseProtocol, DatabaseSet from .models import DatabaseSetTemplate, DatabaseSetTemplateOutput from .models import DatabaseSetOutput +from .models import validate_database + @receiver(models.signals.post_delete, sender=Database) def auto_delete_file_on_delete(sender, instance, **kwargs): @@ -86,7 +89,9 @@ def refresh_protocols(sender, instance, **kwargs): """Refreshes changed protocols""" try: - core = instance.core() + + core = validate_database(instance.declaration) + core.name = instance.fullname() protocols = DatabaseProtocol.objects.filter( database__name=instance.name, @@ -94,13 +99,13 @@ def refresh_protocols(sender, instance, **kwargs): ) existing = set((k.name, k.set_template_basename()) for k in protocols) - new_objects = set((k['name'], k['template']) for k in core.protocols()]) + new_objects = set([(k,v['template']) for k,v in core.protocols.items()]) 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()]]) + json_protocols = dict([(k,v) for k,v in core.protocols.items()]) for protocol_name, template in new_objects - existing: protocol = DatabaseProtocol(name=protocol_name, database=instance) @@ -135,72 +140,72 @@ def refresh_protocols(sender, instance, **kwargs): ) 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, + # 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 + # 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.objects.filter( + DatabaseSetTemplateOutput( name=output_name, template=dataset_template, dataformat=dataformats[0], ) + database_template_output.save() - 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() + else: + database_template_output = \ + database_template_output[0] + + # Create the database set 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? + instance.delete() raise