From ba536a684dd29b0810a2fc43dc733efa326db8a3 Mon Sep 17 00:00:00 2001 From: Flavio Tarsetti <Flavio.Tarsetti@idiap.ch> Date: Fri, 19 Aug 2016 00:09:12 +0200 Subject: [PATCH] [common/plotters] new-version for plotterparameter (in progress) --- beat/web/common/serializers.py | 45 ++++++- beat/web/plotters/admin.py | 3 + beat/web/plotters/serializers.py | 8 +- .../controllers/plotterparameterController.js | 1 + .../directives/plotterparameterItemView.js | 63 +++++++++ .../plotterparameters/panels/actions.html | 12 ++ .../panels/viewer_editor_new_version.html | 120 ++++++++++++++++++ .../plotterparameters/plotterparameter.html | 6 + .../web/plotters/templatetags/plotter_tags.py | 30 +++++ beat/web/plotters/urls.py | 18 +++ beat/web/plotters/views.py | 86 +++++++++++++ 11 files changed, 385 insertions(+), 7 deletions(-) create mode 100644 beat/web/plotters/templates/plotterparameters/panels/viewer_editor_new_version.html diff --git a/beat/web/common/serializers.py b/beat/web/common/serializers.py index 60904c6f8..44b7b28b5 100644 --- a/beat/web/common/serializers.py +++ b/beat/web/common/serializers.py @@ -43,6 +43,7 @@ from .fields import JSONSerializerField, StringListField import simplejson as json import difflib +import ast #---------------------------------------------------------- @@ -274,6 +275,35 @@ class ContributionSerializer(VersionableSerializer): #---------------------------------------------------------- +class MapDot(dict): + def __init__(self, *args, **kwargs): + super(MapDot, self).__init__(*args, **kwargs) + for arg in args: + if isinstance(arg, dict): + for k, v in arg.iteritems(): + self[k] = v + + if kwargs: + for k, v in kwargs.iteritems(): + self[k] = v + + def __getattr__(self, attr): + return self.get(attr) + + def __setattr__(self, key, value): + self.__setitem__(key, value) + + def __setitem__(self, key, value): + super(MapDot, self).__setitem__(key, value) + self.__dict__.update({key: value}) + + def __delattr__(self, item): + self.__delitem__(item) + + def __delitem__(self, key): + super(MapDot, self).__delitem__(key) + del self.__dict__[key] + class ContributionCreationSerializer(serializers.ModelSerializer): declaration = JSONSerializerField(required=False) @@ -298,10 +328,17 @@ class ContributionCreationSerializer(serializers.ModelSerializer): data['name'] = name if data.has_key('previous_version'): - previous_version_id = self.Meta.beat_core_class.Storage(settings.PREFIX, - data['previous_version']) - if previous_version_id.username is None: - previous_version_id.username = user.username + if self.Meta.beat_core_class is not None: + previous_version_id = self.Meta.beat_core_class.Storage(settings.PREFIX, + data['previous_version']) + if previous_version_id.username is None: + previous_version_id.username = user.username + else: + previous_version_id = MapDot() + previous_version_id["username"] = user.username + previous_version_id["name"] = name + previous_version_id["version"] = data['previous_version'] + data['data'] = json.dumps(ast.literal_eval(json.loads(json.dumps(data['data'])))) else: previous_version_id = None diff --git a/beat/web/plotters/admin.py b/beat/web/plotters/admin.py index 19e3e02e2..f38005e07 100644 --- a/beat/web/plotters/admin.py +++ b/beat/web/plotters/admin.py @@ -243,11 +243,14 @@ class PlotterParameterAdmin(admin.ModelAdmin): 'creation_date', 'sharing', 'short_description', + 'previous_version', ) search_fields = ['author__username', 'name', 'short_description', 'plotter__name', + 'previous_version__author__username', + 'previous_version__name', ] list_display_links = ('id', 'name') list_filter = ('sharing', ) diff --git a/beat/web/plotters/serializers.py b/beat/web/plotters/serializers.py index 34540fbae..98ee8d95b 100644 --- a/beat/web/plotters/serializers.py +++ b/beat/web/plotters/serializers.py @@ -109,9 +109,10 @@ class PlotterParameterCreationFailedException(Exception): pass class PlotterParameterCreationSerializer(ContributionCreationSerializer): + print "vv" class Meta(ContributionCreationSerializer.Meta): model = PlotterParameter - fields = ['name', 'plotter'] + fields = ['name', 'plotter', 'data', 'version', 'previous_version', 'short_description', 'description'] #beat_core_class = beat.core.PlotterParameter def create(self, validated_data): @@ -121,7 +122,7 @@ class PlotterParameterCreationSerializer(ContributionCreationSerializer): raise serializers.ValidationError('No name provided') try: - plotterparameter = PlotterParameter.objects.get(author=self.context['request'].user, name=validated_data['name']) + plotterparameter = PlotterParameter.objects.get(author=self.context['request'].user, name=validated_data['name'], version=validated_data['version']) except: pass @@ -140,8 +141,9 @@ class PlotterParameterCreationSerializer(ContributionCreationSerializer): if plotter is None: raise serializers.ValidationError('Required plotter does not exist') + if not validated_data.has_key("data"): + validated_data['data'] = {} - validated_data['data'] = {} plotterparameter = PlotterParameter.objects.create(**validated_data) if plotterparameter is None: raise PlotterParameterCreationFailedException() diff --git a/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js b/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js index 535fd7611..af17fb8a8 100644 --- a/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js +++ b/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js @@ -35,6 +35,7 @@ app.controller('plotterparameterController',['$scope', 'plotterFactory', 'plotte $scope.plotterparameterData = {}; $scope.textdata = []; $scope.plotterparams_update = {}; + $scope.plotterparams_newversion = {}; $scope.init = function(user, plotterparameter, url_prefix, data_itemcontent_file, data_table_itemcontent_file) { diff --git a/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js b/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js index 45ddc2745..20ba22e64 100644 --- a/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js +++ b/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js @@ -279,6 +279,69 @@ app.directive("createplotterparameter", function() }; }); +//Directive used to handle save plotterparameter click +app.directive("createplotterparameternewversion", function() +{ + return { + link:function(scope, element, attrs) + { + element.bind("click", function() + { + //No plotter is selected + if(attrs.plotter == "None" || attrs.plotter=="") + { + //plotter is selected: parameter tuning + //should actually never fall here + + if($("#plotter-selection :selected").text().length == 0) + { + alert("Please select a plotter first") + } + else + { + scope.plotterparams_update.plotter = scope.plotters.selected.id; + + createPlotterParameter(); + } + } + else + { + scope.plotterparams_newversion.plotter = scope.plotters.selected.id; + scope.plotterparams_newversion.name = scope.plotterparameter_name; + scope.plotterparams_newversion.version = (parseInt(scope.plotterparameter_version) + 1).toString(); + scope.plotterparams_newversion.previous_version = parseInt(scope.plotterparameter_version).toString(); + scope.plotterparams_newversion.data = scope.plotterparams_update; + + createPlotterParameter(); + } + }); + + function createPlotterParameter() + { + scope.plotterparameterFactory.createPlotterParameter(scope.user, scope.plotterparams_newversion, scope.url_prefix) + .success(function (returnedData) + { + + //beat.ui.plotterparameter.plotterparameter_created('plotterparameter_created', scope); + }) + .error(function (error) + { + var error_text = ""; + $.each(error, function( key, value ) { + + error_text += key + ": " + value; + }); + scope.status = 'Unable to create plotterparameter:\n' + error_text; + + //alert(scope.status); + console.log(scope.status); + }); + + } + } + }; +}); + //Directive used to append parameters app.directive("plotparams", function($compile){ diff --git a/beat/web/plotters/templates/plotterparameters/panels/actions.html b/beat/web/plotters/templates/plotterparameters/panels/actions.html index aa1bcac5b..a50c974f7 100644 --- a/beat/web/plotters/templates/plotterparameters/panels/actions.html +++ b/beat/web/plotters/templates/plotterparameters/panels/actions.html @@ -38,8 +38,20 @@ <a id="save-button" class="btn btn-default btn-save" data-toggle="tooltip" data-placement="bottom" data-plotter="{{object.plotter}}" title="Save" saveplotterparameter><i class="fa fa-floppy-o fa-lg"></i></a> {% endifequal %} + + <!-- New version, needs to be the owner --> + <a class="btn btn-default btn-new-version" href="{% url 'plotters:new-version' object.author.username object.name object.version %}" data-toggle="tooltip" data-placement="bottom" title="New version"><i class="fa fa-copy fa-lg"></i></a> + {% endifequal %} {% endif %} + + {% if not request.user.is_anonymous %} + + <!-- Fork button, needs to be logged in --> + <a class="btn btn-default btn-fork" href="{% url 'plotters:fork' object.author.username object.name object.version %}" data-toggle="tooltip" data-placement="bottom" title="Fork"><i class="fa fa-code-fork fa-lg"></i></a> + + {% endif %} + {%comment%} <!-- Share, needs to be the owner and it may not be public already --> diff --git a/beat/web/plotters/templates/plotterparameters/panels/viewer_editor_new_version.html b/beat/web/plotters/templates/plotterparameters/panels/viewer_editor_new_version.html new file mode 100644 index 000000000..ceb0ccdcd --- /dev/null +++ b/beat/web/plotters/templates/plotterparameters/panels/viewer_editor_new_version.html @@ -0,0 +1,120 @@ +{% comment %} + * 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/. +{% endcomment %} +{% load ui_tags %} +{% load plotter_tags %} + +{% load fingerprint %} + +<link rel="stylesheet" href="{% fingerprint "chosen-bootstrap/chosen.bootstrap.min.css" %}" type="text/css" media="screen" /> +<script src="{% fingerprint "plotters/js/dialogs.js" %}" type="text/javascript" charset="utf-8"></script> +<script src="{% fingerprint "bootstrap3-typeahead/bootstrap3-typeahead.min.js" %}" type="text/javascript" charset="utf-8"></script> +<script src="{% fingerprint "chosen/chosen.jquery.min.js" %}" type="text/javascript" charset="utf-8"></script> + +<div class="row"> + <div class="col-sm-8 vertical-center"> + <h3> + <ol class="breadcrumb"> + <li>New version of plotterparameter: {{parameters.plotterparameter_author}}/{{parameters.plotterparameter_name}}/{{parameters.plotterparameter_version}}</li> + </ol> + </h3> + </div><div class="col-sm-4 vertical-center"> + <div class="action-buttons pull-right"> + <button id="save" type="submit" class="btn btn-success btn-sm button_save" data-toggle="tooltip" data-placement="bottom" data-plotter="{{plotter}}" data-parameters="{{parameters}}" createplotterparameternewversion><i class="fa fa-save"></i> Save</button> + <a id="cancel" class="btn btn-danger btn-sm" onclick="window.history.back();"><i class="fa fa-times fa-lg"></i> Cancel</a> + </div> + </div> +</div> + +<div class="row"> + <div class="col-sm-12"> + + {# Navigation Tabs #} + <ul id="object-tabs" class="nav nav-tabs" role="tablist"> + + + <li role="presentation" class="active"><a href="#viewer" role="tab" data-toggle="tab" aria-controls="viewer">Plotterparameter</a></li> + {% if owner %} + <li role="presentation"><a href="#sharing" role="tab" data-toggle="tab" aria-controls="sharing">Sharing</a></li> + {% endif %} + + {% visible_reports plotterparameter as reports %} + + <li role="presentation"><a href="#history" role="tab" data-toggle="tab" aria-controls="history">History</a></li> + </ul> + + {# Navigation Panes #} + <div class="tab-content"> + <div role="tabpanel" class="tab-pane active" id="viewer"> + + <div class="row"> + <div class="col-sm-12"> + <div class="panel-group" id="information-accordion" role="tablist" aria-multiselectable="true"> + + <div class="panel panel-default step1"> + <div class="panel-heading" role="tab" id="info-heading"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-parent="#info-heading" href="#collapse-info" aria-expanded="true" aria-controls="collapse-info">Click the "Save" Button to create a new version of this plotterparameter</a> + </h4> + </div>{# panel-heading #} + <div id="collapse-info" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="info-heading"> + <div class="panel-body"> + + <div id="plotterparameter_items" class="row" plotterparameteritems> + <div id="space-for-plotterparameter-plot"> + </div> + </div> + + </div>{# panel-body #} + </div>{# collapse #} + </div>{# panel #} + + + </div>{# panel-group #} + </div>{# col-sm-12 #} + </div>{# row #} + + </div> + <div role="tabpanel" class="tab-pane" id="doc"> + {%comment%} + {% doc_editor plotterparameter 'api_plotterparameters:object' %} + {%endcomment%} + {% doc_editor plotterparameter 'api_plotters:all' %} + </div> + {% if owner %} + <div role="tabpanel" class="tab-pane" id="sharing"> + {% plotterparameter_sharing plotterparameter %} + </div> + {% endif %} + <div role="tabpanel" class="tab-pane" id="history"> + {% history "plotterparameters" plotterparameter "history" 400 %} + </div> + </div> + + </div> +</div> + + +{% plotterparameter_created "plotterparameter_created" %} + +<script type="text/javascript"> + +</script> diff --git a/beat/web/plotters/templates/plotterparameters/plotterparameter.html b/beat/web/plotters/templates/plotterparameters/plotterparameter.html index dd3e5fcba..0f453abbe 100644 --- a/beat/web/plotters/templates/plotterparameters/plotterparameter.html +++ b/beat/web/plotters/templates/plotterparameters/plotterparameter.html @@ -95,8 +95,14 @@ {% plotterparameter_viewer_editor plotterparameter%} +{% elif plotterparameter_mode == 'NEW_VERSION_MODE' %} + + {% plotterparameter_viewer_editor_new_version plotterparameter%} + {% else %} + + <div id="title" class="row"> <div class="{% if owner %}col-sm-9 vertical-center{% else %}col-sm-12{% endif %}"{% if owner %} onmouseover="expand_breadcrumb(this, 9, 3);" onmouseout="reset_breadcrumb(this, 9, 3);"{% endif %}> diff --git a/beat/web/plotters/templatetags/plotter_tags.py b/beat/web/plotters/templatetags/plotter_tags.py index f78a3eff3..670b0eaaf 100644 --- a/beat/web/plotters/templatetags/plotter_tags.py +++ b/beat/web/plotters/templatetags/plotter_tags.py @@ -135,6 +135,36 @@ def plotterparameter_sampleplot_display(context, plotter): panel_id=id, ) +@register.inclusion_tag('plotterparameters/panels/viewer_editor_new_version.html', takes_context=True) +def plotterparameter_viewer_editor_new_version(context, plotterparameter): + '''Composes a plotterparameter viewer and editor + + This panel primarily exists for user's plotterparameter list page. + + Parameters: + + objects (iterable): An iterable containing plotterparameter objects + owner (bool): A flag indicating if the list is being created for the + owner of his personal list page on the user-microsite or not. + id: The HTML id to set on the generated table. This is handy for the + filter functionality normally available on list pages. + + ''' + plotter = plotterparameter.plotter + plotter_sample_data = plotterparameter.plotter.sample_data + plotterparameter = plotterparameter + parameters = context['parameters'] + + return dict( + request=context['request'], + parameters=parameters, + plotterparameter=plotterparameter, + plotter=plotter, + plotter_sample_data=plotter_sample_data, + panel_id=id, + URL_PREFIX=context['URL_PREFIX'], + ) + @register.inclusion_tag('plotterparameters/panels/viewer_editor.html', takes_context=True) def plotterparameter_viewer_editor(context, plotterparameter): diff --git a/beat/web/plotters/urls.py b/beat/web/plotters/urls.py index bc05b166a..56596b05b 100644 --- a/beat/web/plotters/urls.py +++ b/beat/web/plotters/urls.py @@ -80,12 +80,19 @@ urlpatterns = [ name='plot_sample_with_params', ), + url( r'^plotterparameter/(?P<author_name>\w+)/new_plotterparameter/$', views.create_plotterparameter, name='new_plotterparameter', ), + url( + r'^plotterparameter/(?P<author_name>\w+)/(?P<plotterparameter_name>[-\w]+)/(?P<version>\d+)/new/$', + views.create_new_version, + name='new-version', + ), + url( r'^(?P<author_name>\w+)/plotterparameter/$', views.list_plotterparameters, @@ -110,10 +117,21 @@ urlpatterns = [ name='view', ), + url( r'^(?P<author>\w+)/(?P<name>[-\w]+)/$', views.view, name='plotter-view-latest', ), + #TODO: CREATE VIEWS for fork and new version + + url( + r'^fork/(?P<author>\w+)/(?P<name>[-\w]+)/(?P<version>\d+)/$', + views.fork, + name='fork', + ), + + + ] diff --git a/beat/web/plotters/views.py b/beat/web/plotters/views.py index 816094d20..dbb25e04d 100644 --- a/beat/web/plotters/views.py +++ b/beat/web/plotters/views.py @@ -769,6 +769,92 @@ def create_plotterparameter(request, author_name): #------------------------------------------------ +@login_required +def create_new_version(request, author_name=None, plotterparameter_name=None, version=None): + """Creates a new plotterparameter or a new version of an existing plotterparameter + + The user must be authenticated before it can add a new toolchain + """ + + parameters = {'plotterparameter_author': request.user.username, + 'plotterparameter_name': plotterparameter_name, + 'plotterparameter_version': version, + 'short_description': '', + 'description': '', + 'errors': '', + 'edition': False, + } + + # Retrieves the existing toolchain (if necessary) + plotterparameter = None + if plotterparameter_name is not None: + previous_versions = PlotterParameter.objects.filter( + author=request.user, + name__iexact=plotterparameter_name, + version=version, + ).order_by('-version') + if len(previous_versions) == 0: + raise Http404() + + previous_version = previous_versions[0] + plotterparameter = previous_version + + description = previous_version.description + + parameters['plotterparameter_version'] = previous_version.version + 1 + parameters['short_description'] = previous_version.short_description + parameters['description'] = description.replace('\n', '\\n') + parameters['data'] = previous_version.data + parameters['plotter'] = previous_version.plotter.id + else: + declaration, errors = prototypes.load('toolchain') + parameters['declaration'] = simplejson.dumps(declaration) + + return render_to_response('plotterparameters/plotterparameter.html', + dict( + author=request.user.username, + parameters=parameters, + plotterparameter_name=plotterparameter_name, + plotterparameter=plotterparameter, + owner=(request.user == plotterparameter.author), + plotterparameter_mode="NEW_VERSION_MODE", + ), + context_instance=RequestContext(request), + ) + + +@login_required +def fork(request, author, name, version): + """Creates a new toolchain by forking an existing toolchain + + The user must be authenticated before it can add a new toolchain + """ + + # Retrieves the forked toolchain + fork_of = get_object_or_404(Toolchain.objects.for_user(request.user, True), + author__username__iexact=author, + name__iexact=name, + version=int(version) + ) + + description = fork_of.description + + parameters = {'toolchain_author': request.user.username, + 'toolchain_name': name, + 'toolchain_version': 1, + 'fork_of': fork_of, + 'declaration': fork_of.declaration_string.replace('\n', ''), + 'short_description': fork_of.short_description, + 'description': description.replace('\n', '\\n'), + 'errors': fork_of.errors.replace('\n', '\\n') if fork_of.errors is not None else '', + 'edition': False, + 'messages': Messages, + } + + return render_to_response('toolchains/edition.html', + parameters, + context_instance=RequestContext(request)) + class PartialGroupView(TemplateView): def get_template_names(self): -- GitLab