From 0f409fdb126c2dedd91d93bcd57b76fe98cbcc55 Mon Sep 17 00:00:00 2001 From: Philip ABBET <philip.abbet@idiap.ch> Date: Fri, 18 Nov 2016 16:49:44 +0100 Subject: [PATCH] [website] Allows the upload of a shared library for a binary algorithm --- beat/web/algorithms/api.py | 10 ++- .../static/algorithms/css/editor.css | 13 ++++ .../templates/algorithms/edition.html | 68 ++++++++++++++++--- .../templates/algorithms/panels/editor.html | 11 +++ .../algorithms/templates/algorithms/view.html | 2 +- beat/web/algorithms/views.py | 27 +++++--- beat/web/code/models.py | 14 ++++ beat/web/common/texts.py | 1 + buildout.cfg | 1 + 9 files changed, 124 insertions(+), 23 deletions(-) mode change 100644 => 100755 beat/web/algorithms/views.py mode change 100644 => 100755 beat/web/common/texts.py diff --git a/beat/web/algorithms/api.py b/beat/web/algorithms/api.py index c40350671..11a1e03fb 100755 --- a/beat/web/algorithms/api.py +++ b/beat/web/algorithms/api.py @@ -181,8 +181,12 @@ def binary(request, author_name, object_name, version=None): return HttpResponseBadRequest() file = request.FILES['binary'] - with open(os.path.join(settings.ALGORITHMS_ROOT, algorithm.source_code_filename()), 'wb') as dest: - for chunk in file.chunks(): - dest.write(chunk) + + binary_data = '' + for chunk in file.chunks(): + binary_data += chunk + + algorithm.source_code = binary_data + algorithm.save() return HttpResponse(status=204) diff --git a/beat/web/algorithms/static/algorithms/css/editor.css b/beat/web/algorithms/static/algorithms/css/editor.css index 6a6c48aca..a643f3dd6 100644 --- a/beat/web/algorithms/static/algorithms/css/editor.css +++ b/beat/web/algorithms/static/algorithms/css/editor.css @@ -292,3 +292,16 @@ div.documentation_editor hr.separator color: #C0C0C0; background-color: #C0C0C0; } + + +div#binary_file_selector span.binary_file_button +{ + margin-left: 5px; + margin-right: 5px; +} + + +div#binary_file_selector span.filename +{ + font-style: italic; +} diff --git a/beat/web/algorithms/templates/algorithms/edition.html b/beat/web/algorithms/templates/algorithms/edition.html index 1af55e4be..47fb65d44 100644 --- a/beat/web/algorithms/templates/algorithms/edition.html +++ b/beat/web/algorithms/templates/algorithms/edition.html @@ -36,6 +36,7 @@ {{ block.super }} <link rel="stylesheet" href="{% fingerprint "algorithms/css/editor.css" %}" type="text/css" media="screen" /> <link rel="stylesheet" href="{% fingerprint "ui/css/smart-selector.css" %}" type="text/css" media="screen" /> +<link rel="stylesheet" href="{% fingerprint "blueimp-file-upload/css/jquery.fileupload.css" %}" type="text/css" media="screen" /> {% code_editor_css %} {% endblock %} @@ -46,6 +47,8 @@ <script src="{% fingerprint "algorithms/js/editor.js" %}" type="text/javascript" charset="utf-8"></script> <script src="{% fingerprint "ui/js/smartselector.js" %}" type="text/javascript" charset="utf-8"></script> +<script src="{% fingerprint "jquery-ui/ui/minified/jquery-ui.min.js" %}" type="text/javascript" charset="utf-8"></script> +<script src="{% fingerprint "blueimp-file-upload/js/jquery.fileupload.js" %}" type="text/javascript" charset="utf-8"></script> {% code_editor_scripts "python" %} <script type="text/javascript"> @@ -73,9 +76,10 @@ function setupEditor(algorithm, dataformats, libraries) { $('.contribution_editor').show(); +{% if not binary %} // Source code editor var source_code_editor = new beat.contributions.editor.SourceCodeEditor('source_code'); - +{% endif %} // Options var declaration = JSON.parse('{{ declaration|escapejs }}'); @@ -145,6 +149,7 @@ function setupEditor(algorithm, dataformats, libraries) ); +{% if not binary %} // Libraries editor if (declaration.uses === undefined) declaration.uses = {}; @@ -153,7 +158,7 @@ function setupEditor(algorithm, dataformats, libraries) 'libraries_editor', declaration.uses, libraries, smart_selector ); - +{% endif %} // Analyzer checkbox handling checkbox_analyzer.change(function() { @@ -172,11 +177,14 @@ function setupEditor(algorithm, dataformats, libraries) results_panel.hide(); } +{% if not binary %} source_code_editor.changeProcessMethod(checkbox_analyzer[0].checked); +{% endif %} inputs_editor.setAnalyzer(checkbox_analyzer[0].checked); }); +{% if not binary %} // Language radio buttons handling var language_python_selector = $('#language_python'); var language_cxx_selector = $('#language_cxx'); @@ -191,6 +199,21 @@ function setupEditor(algorithm, dataformats, libraries) $('#source_code_editor').hide(); $('#libraries-editor-panel').hide(); }); +{% endif %} + + +{% if binary %} + $('#shared_library').fileupload({ + url: '{% url "api_algorithms:binary" algorithm_author algorithm_name algorithm_version %}', + add: function (e, data) { + $('.button_save')[0].shared_library = data; + $('#binary_file_name').text(data.files[0].name); + }, + done: function (e, data) { + window.location = '{% url "algorithms:view" algorithm_author algorithm_name algorithm_version %}'; + } + }); +{% endif %} // Save button checkbox handling @@ -208,14 +231,16 @@ function setupEditor(algorithm, dataformats, libraries) {% endif %} {% endif %} - var declaration = { - language: 'python', - }; + var declaration = {}; +{% if not edition %} if (language_python_selector[0].checked) declaration.language = 'python'; else declaration.language = 'cxx'; +{% else %} + declaration.language = '{{ algorithm_language }}'; +{% endif %} if (!checkbox_analyzer[0].checked) { @@ -236,13 +261,14 @@ function setupEditor(algorithm, dataformats, libraries) if (declaration.parameters === null) return false; +{% if not binary %} if (language_python_selector[0].checked) { declaration.uses = libraries_panel.getLibraries(displayErrors); if (declaration.uses === null) return false; } - +{% endif %} var data = { {% if not edition %} @@ -261,8 +287,10 @@ function setupEditor(algorithm, dataformats, libraries) {% endif %} }; +{% if not binary %} if (language_python_selector[0].checked) data.code = source_code_editor.getSourceCode(); +{% endif %} $.ajax({ @@ -277,10 +305,14 @@ function setupEditor(algorithm, dataformats, libraries) contentType: "application/json; charset=utf-8", dataType: "json", success: function(data) { - {% if edition %} + {% if not binary %} + {% if edition %} window.location = '{% url "algorithms:view" algorithm_author algorithm_name algorithm_version %}'; - {% else %} + {% else %} window.location = data.object_view; + {% endif %} + {% else %} + $('.button_save')[0].shared_library.submit(); {% endif %} }, error: function(jqXHR, textStatus, errorThrown) { @@ -489,6 +521,7 @@ function setupEditor(algorithm, dataformats, libraries) {# libraries editor panel #} + {% if not binary %} <div id="libraries-editor-panel" class="panel panel-default contribution_editor " style="display:none;"> <div class="panel-heading" role="tab" id="library-editor-1"> <h4 class="panel-title"> @@ -519,11 +552,13 @@ function setupEditor(algorithm, dataformats, libraries) </div>{# panel-body #} </div>{# panel-collapse #} </div>{# panel #} + {% endif %} </div>{# panel-group #} </div>{# row #} </div> +{% if not binary %} <div id="source_code_editor" class="row"> <div class="col-sm-offset-1 col-sm-10 contribution_editor" style="display: none;"> <label for="source_code">Source code:</label> @@ -531,6 +566,23 @@ function setupEditor(algorithm, dataformats, libraries) <p class="help">{{ messages.code|safe }}</p> </div> </div> +{% else %} +<div id="binary_file_selector" class="row"> + <div class="col-sm-offset-1 col-sm-10 contribution_editor" style="display: none;"> + <div class="alert alert-info"> + <i class="fa fa-info"></i> <span>This algorithm is implemented in <strong>{{ algorithm_language_name }}</strong>, compiled as a <strong>shared library</strong>.</span> + </div> + <label for="shared_library">Shared library:</label> + <span class="btn btn-success fileinput-button binary_file_button"> + <i class="glyphicon glyphicon-plus"></i> + <span>Select file...</span> + <input id="shared_library" type="file" name="binary" /> + </span> + <span id="binary_file_name" class="filename"></span> + <p class="help">{{ messages.shared_library|safe }}</p> + </div> +</div> +{% endif %} {% smart_selector "smart_selector" %} diff --git a/beat/web/algorithms/templates/algorithms/panels/editor.html b/beat/web/algorithms/templates/algorithms/panels/editor.html index 711ddd31b..14ebdc0f1 100644 --- a/beat/web/algorithms/templates/algorithms/panels/editor.html +++ b/beat/web/algorithms/templates/algorithms/panels/editor.html @@ -231,6 +231,17 @@ {% if open_source and not object.is_binary %} <textarea class="form-control" id="code-display">{{ object.source_code_file.read }}</textarea> <p class="help-block">{{ texts.code|safe }}</p> + {% elif object.is_binary %} + {% if object.valid %} + <div class="alert alert-info"> + <i class="fa fa-info"></i> <span>This algorithm is implemented in <strong>{{ object.language_fullname }}</strong>, compiled as a <strong>shared library</strong>.</span> + <a id="btn-download-binary" class="btn btn-success btn-sm" href="{% url "api_algorithms:binary" object.author.username object.name object.version %}"><i class="fa fa-download fa-lg"></i> Download</a> + </div> + {% else %} + <div class="alert alert-warning"> + <i class="fa fa-warning"></i> This algorithm must be implemented in <strong>{{ object.language_fullname }}</strong>, compiled as a <strong>shared library</strong> and uploaded to the platform. + </div> + {% endif %} {% else %} <div class="alert alert-warning"> <i class="fa fa-warning"></i> This algorithm is only usable to you. Its code was <strong>not</strong> shared. diff --git a/beat/web/algorithms/templates/algorithms/view.html b/beat/web/algorithms/templates/algorithms/view.html index cde3bb969..a791b7748 100644 --- a/beat/web/algorithms/templates/algorithms/view.html +++ b/beat/web/algorithms/templates/algorithms/view.html @@ -131,7 +131,7 @@ {% for key, value in execinfo %}<li class="list-group-item"><a title="Click to view" data-toggle="tooltip" data-placement="top" href="{% url 'backend:view-environment' key.name key.version %}">{{ key.fullname }}</a> <span class="badge">{{ value }}</span></li>{% endfor %} </ul> <p class="help">This table shows the number of times this algorithm - has been <b>successfuly</b> run using the given environment. Note + has been <b>successfully</b> run using the given environment. Note this does not provide sufficient information to evaluate if the algorithm will run when submitted to different conditions.</p> {% endif %} diff --git a/beat/web/algorithms/views.py b/beat/web/algorithms/views.py old mode 100644 new mode 100755 index 70839094f..08cb8eced --- a/beat/web/algorithms/views.py +++ b/beat/web/algorithms/views.py @@ -164,19 +164,24 @@ def edit(request, author, name, version): description = algorithm.description - parameters = {'algorithm_author': request.user.username, - 'algorithm_name': name, - 'algorithm_version': algorithm.version, - 'source_code': algorithm.source_code_file.read(), - 'declaration': algorithm.declaration_file.read().replace('\n', ''), - 'short_description': algorithm.short_description, - 'description': description.replace('\n', '\\n'), - 'html_description': restructuredtext(description).replace('\n', ''), - 'messages': Messages, - 'edition': True, - 'plot_account': settings.PLOT_ACCOUNT, + parameters = {'algorithm_author': request.user.username, + 'algorithm_name': name, + 'algorithm_version': algorithm.version, + 'algorithm_language': algorithm.json_language, + 'algorithm_language_name': algorithm.language_fullname(), + 'declaration': algorithm.declaration_file.read().replace('\n', ''), + 'short_description': algorithm.short_description, + 'description': description.replace('\n', '\\n'), + 'html_description': restructuredtext(description).replace('\n', ''), + 'messages': Messages, + 'edition': True, + 'binary': algorithm.is_binary(), + 'plot_account': settings.PLOT_ACCOUNT, } + if not algorithm.is_binary(): + parameters['source_code'] = algorithm.source_code_file.read() + return render_to_response('algorithms/edition.html', parameters, context_instance=RequestContext(request)) diff --git a/beat/web/code/models.py b/beat/web/code/models.py index 4ad4eb905..33d98329b 100755 --- a/beat/web/code/models.py +++ b/beat/web/code/models.py @@ -209,6 +209,10 @@ class Code(StoredContribution): (R, 'R'), ) + CODE_NAMES = { + CXX: 'C++', + } + #_____ Fields __________ @@ -476,6 +480,16 @@ class Code(StoredContribution): return self.language in [Code.CXX] + def language_fullname(self): + if Code.CODE_NAMES.has_key(self.language): + return Code.CODE_NAMES[self.language] + return filter(lambda x: x[0] == self.language, Code.CODE_LANGUAGE)[0][1] + + + def json_language(self): + return filter(lambda x: x[0] == self.language, Code.CODE_LANGUAGE)[0][1].lower() + + #_____ Overrides __________ def save(self, *args, **kwargs): diff --git a/beat/web/common/texts.py b/beat/web/common/texts.py old mode 100644 new mode 100755 index 47a8a154a..543fdaa59 --- a/beat/web/common/texts.py +++ b/beat/web/common/texts.py @@ -40,4 +40,5 @@ Messages = { 'format_name': 'The name for this dataformat (space-like characters will be automatically replaced by dashes)', 'name': 'The name for this object (space-like characters will be automatically replaced by dashes)', 'version': 'The version of this object (an integer starting from 1)', + 'shared_library': 'The compiled shared library file implementing your algorithm', } diff --git a/buildout.cfg b/buildout.cfg index 096135684..4ecf41387 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -112,6 +112,7 @@ packages = jquery#~1.11.3 jquery-dateFormat#~1.0.2 jquery-ui#~1.10.4 jquery.cookie#~1.4.1 + jquery-file-upload#~9.14.0 fontawesome#~4.5.0 codemirror#~5.10.0 bootstrap#~3.3.6 -- GitLab