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