Commit 7927e6e4 authored by Flavio TARSETTI's avatar Flavio TARSETTI

Merge branch 'remove_asset_edition' into 'master'

Remove asset edition

Closes #578, #576, #575, #574, #573, and #572

See merge request !398
parents 5f3512b8 0cf2fda0
Pipeline #43880 passed with stages
in 18 minutes and 35 seconds
This diff is collapsed.
......@@ -43,16 +43,6 @@
<a class="btn btn-default btn-delete" onclick="modal_delete('algorithm', '{{ object.fullname }}', '{% url 'api_algorithms:all' %}', '{% url 'algorithms:list' request.user.username %}');" data-toggle="tooltip" data-placement="bottom" title="Delete"><i class="fa fa-times fa-lg"></i></a>
{% endif %}
<!-- New version, needs to be the owner -->
{% if object.valid %}
<a class="btn btn-default btn-new-version" href="{% url 'algorithms:new-version' object.name %}" data-toggle="tooltip" data-placement="bottom" title="New version"><i class="fa fa-copy fa-lg"></i></a>
{% endif %}
<!-- Edit, needs to be modifiable -->
{% if object.modifiable %}
<a class="btn btn-default btn-edit" href="{% url 'algorithms:edit' object.author.username object.name object.version %}" data-toggle="tooltip" data-placement="bottom" title="Edit"><i class="fa fa-edit fa-lg"></i></a>
{% endif %}
{% endifequal %}
<!-- Edit, as admin -->
......@@ -60,11 +50,6 @@
<a class="btn btn-default btn-edit" href="{% url 'admin:algorithms_algorithm_change' object.id %}" data-toggle="tooltip" data-placement="bottom" title="Edit as admin"><i class="fa fa-cogs fa-lg"></i></a>
{% endif %}
{% if object.valid and open_source and not request.user.is_anonymous %}
<!-- Fork button, needs to be logged in -->
<a class="btn btn-default btn-fork" href="{% url 'algorithms: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 %}
<!-- Search, works for logged-in and anonymous users -->
{% if object.valid %}
<a class="btn btn-default btn-search" href="{% url 'search:search' %}?query=type:results%20{% if object.analysis %}analyzer{% else %}algo{% endif %}:{{ object.fullname }}" data-toggle="tooltip" data-placement="bottom" title="Search experiments"><i class="fa fa-search fa-lg"></i></a>
......
......@@ -267,13 +267,6 @@
</div>
{% endif %}
</div>
{% if owner and object.modifiable %}
<div class="col-sm-2 action-buttons">
<a id="btn-edit-object" class="btn btn-primary btn-sm pull-right" href="{% url 'algorithms:edit' object.author.username object.name object.version %}"><i class="fa fa-edit fa-lg"></i> Edit</a>
</div>
{% endif %}
</div>
{% if open_source and not object.is_binary %}
......
......@@ -51,12 +51,6 @@
{% endif %}
<!-- Notice there can be no div space if vertical-center is used -->
</div><div class="col-sm-2 vertical-center">
{% if owner %}
<a class="btn btn-success btn-sm pull-right" href="{% url 'algorithms:new' %}"><i class="fa fa-plus fa-lg"></i> New</a>
{% endif %}
</div><!-- col -->
</div><!-- row -->
......
......@@ -33,20 +33,8 @@ from . import views
app_name = "algorithms"
urlpatterns = [
path("new/", views.create, name="new",),
re_path(r"^update/(?P<name>[-\w]+)/$", views.create, name="new-version",),
path("", views.public_ls, name="public-list",),
re_path(r"^(?P<author_name>\w+)/$", views.ls, name="list",),
re_path(
r"^fork/(?P<author>\w+)/(?P<name>[-\w]+)/(?P<version>\d+)/$",
views.fork,
name="fork",
),
re_path(
r"^edit/(?P<author>\w+)/(?P<name>[-\w]+)/(?P<version>\d+)/$",
views.edit,
name="edit",
),
re_path(
r"^diff/(?P<author1>\w+)/(?P<name1>[-\w]+)/(?P<version1>\d+)/(?P<author2>\w+)/(?P<name2>[-\w]+)/(?P<version2>\d+)/$",
views.diff,
......@@ -58,5 +46,4 @@ urlpatterns = [
views.view,
name="view",
),
re_path(r"^(?P<author_name>\w+)/$", views.ls, name="list",),
]
......@@ -25,186 +25,18 @@
# #
###############################################################################
import simplejson as json
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import Http404
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from beat.core import prototypes
from ..common.texts import Messages
from ..common.utils import ensure_string
from ..team.models import Team
from ..ui.templatetags.markup import restructuredtext
from .models import Algorithm
# ----------------------------------------------------------
@login_required
def create(request, name=None):
"""Creates a new algorithm or a new version of an existing algorithm
The user must be authenticated before it can add a new algorithm
"""
parameters = {
"algorithm_author": request.user.username,
"algorithm_name": name,
"algorithm_version": 1,
"short_description": "",
"description": "",
"messages": Messages,
"edition": False,
"plot_account": settings.PLOT_ACCOUNT,
}
# Retrieves the existing algorithm (if necessary)
if name is not None:
previous_versions = Algorithm.objects.filter(
author=request.user, name__iexact=name,
).order_by("-version")
if len(previous_versions) == 0:
raise Http404()
previous_version = previous_versions[0]
description = ensure_string(previous_version.description)
parameters["algorithm_version"] = previous_version.version + 1
parameters["declaration"] = previous_version.declaration_string.replace(
"\n", ""
)
parameters["short_description"] = previous_version.short_description
parameters["description"] = description.replace("\n", "\\n")
parameters["html_description"] = restructuredtext(description).replace("\n", "")
parameters["algorithm_language"] = previous_version.json_language
parameters["algorithm_language_name"] = previous_version.language_fullname()
parameters["binary"] = previous_version.is_binary()
parameters["new_version"] = True
if not previous_version.is_binary():
parameters["source_code"] = previous_version.source_code_file.read()
else:
parameters["source_code"] = ensure_string(
prototypes.binary_load("algorithm.py")
).replace(
"\n\n # TODO: Implement this algorithm\n\n",
"\n # TODO: Implement this algorithm\n",
)
declaration, errors = prototypes.load("algorithm")
declaration["language"] = "python"
parameters["declaration"] = json.dumps(declaration)
return render(request, "algorithms/edition.html", parameters)
# ----------------------------------------------------------
@login_required
def fork(request, author, name, version):
"""Creates a new algorithm by forking an existing algorithm
The user must be authenticated before it can fork an algorithm
"""
# Retrieves the forked algorithm based on user accessible algorithms
fork_of = get_object_or_404(
Algorithm.objects.for_user(request.user, True),
author__username__iexact=author,
name__iexact=name,
version=int(version),
)
# The only case a user can't fork an algorithm is if it's usable rather
# than shared
if fork_of.sharing == Algorithm.USABLE and fork_of.author != request.user:
raise Http404()
description = ensure_string(fork_of.description)
parameters = {
"original_author": author,
"algorithm_author": request.user.username,
"algorithm_name": name,
"algorithm_version": fork_of.version,
"algorithm_language": fork_of.json_language,
"algorithm_language_name": fork_of.language_fullname(),
"declaration": fork_of.declaration_string.replace("\n", ""),
"short_description": fork_of.short_description,
"description": description.replace("\n", "\\n"),
"html_description": restructuredtext(description).replace("\n", ""),
"messages": Messages,
"fork_of": fork_of,
"edition": False,
"binary": fork_of.is_binary(),
"plot_account": settings.PLOT_ACCOUNT,
}
if not fork_of.is_binary():
parameters["source_code"] = fork_of.source_code_file.read()
return render(request, "algorithms/edition.html", parameters)
# ----------------------------------------------------------
@login_required
def edit(request, author, name, version):
"""Allows the modification of the algorithm
The user must be authenticated before it can edit an algorithm
"""
if author != request.user.username:
raise Http404()
# Retrieves the algorithm
algorithm = get_object_or_404(
Algorithm,
author__username__iexact=author,
name__iexact=name,
version=int(version),
)
if not algorithm.modifiable():
return HttpResponseForbidden(
"Algorithm %s is not modifiable" % algorithm.fullname()
)
description = ensure_string(algorithm.description)
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_string.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(request, "algorithms/edition.html", parameters)
# ----------------------------------------------------------
def view(request, author, name, version=None):
"""Shows the algorithm. The Web API is used to retrieve the details about
the algorithm and check the accessibility.
......
......@@ -30,109 +30,6 @@ from django import forms
from ..common.texts import Messages
from ..ui.forms import CodeMirrorJSONCharField
from ..ui.forms import CodeMirrorRSTCharField
from ..ui.forms import NameField
from .models import DataFormat
from .models import validate_format
# ----------------------------------------------------------
class CreationForm(forms.Form):
version = forms.IntegerField(
required=True, min_value=1, widget=forms.HiddenInput(), # make it hidden
)
name = NameField(
label="Name",
max_length=100,
widget=forms.TextInput(attrs=dict(size="100%")),
help_text=Messages["format_name"],
required=True,
)
file = CodeMirrorJSONCharField(
label="Format", help_text=Messages["format"], required=True,
)
short_description = forms.CharField(
label="Short description",
max_length=100,
widget=forms.TextInput(attrs=dict(size="100%")),
help_text=Messages["short_description"],
required=False,
)
description = CodeMirrorRSTCharField(
label="Description", help_text=Messages["description"], required=False,
)
def clean_file(self):
"""Cleans-up the file data, make sure it is really new"""
try:
validate_format(self.cleaned_data["file"])
except Exception as e:
raise forms.ValidationError(str(e))
# if that works out, then we return the passed file
return self.cleaned_data["file"]
def clean_name(self):
"""Makes sure the user, name, version is not repeated"""
name = self.cleaned_data["name"]
version = self.cleaned_data["version"]
obj = DataFormat.objects.filter(
author=self.__user__, name=name, version=version,
)
if obj:
raise forms.ValidationError(
"Choose another name for your data format. The version %d of data format %s already exists"
% (version, name)
)
return name
def __init__(self, *args, **kwargs):
self.__user__ = kwargs.pop("request_user", None)
self.__name_is_set__ = kwargs.pop("name", None)
super(CreationForm, self).__init__(*args, **kwargs)
if self.__name_is_set__:
self.fields["name"].widget.attrs["readonly"] = "True"
# ----------------------------------------------------------
class UpdateForm(forms.Form):
file = CodeMirrorJSONCharField(label="Declaration", help_text=Messages["format"],)
short_description = forms.CharField(
label="Short description",
widget=forms.TextInput(attrs=dict(size="100%")),
help_text=Messages["short_description"],
required=False,
)
description = CodeMirrorRSTCharField(
label="Description", help_text=Messages["description"], required=False,
)
def clean_file(self):
"""Cleans-up the file data, make sure it is valid"""
try:
validate_format(self.cleaned_data["file"])
except Exception as e:
raise forms.ValidationError(str(e))
# if that works out, then we return the passed file
return self.cleaned_data["file"]
# ----------------------------------------------------------
......
{% extends "base.html" %}
{% 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 %}
{% block title %}
{{ block.super }} - {% if not object %}New dataformat{% else %}{% if op == "new-version" %}Create{% elif op == "fork" %}Fork{% else %}Edit{% endif %} {{ object.fullname }}{% endif %}
{% endblock %}
{% block stylesheets %}
{{ block.super }}
{{ form.media }}
{% endblock %}
{% block scripts %}
{{ block.super }}
{% endblock %}
{% block content %}
<form action="" method="post">
<div class="row">
<div class="col-sm-offset-1 col-sm-8 vertical-center">
<h3>
<ol class="breadcrumb">
{% if not object %}
<li>New dataformat</li>
{% else %}
<li>{% if op == "new-version" %}Create{% elif op == "fork" %}Fork{% else %}Edit{% endif %}</li>
<li><a data-toggle="tooltip" data-placement="bottom" title="View all your dataformats" href="{% url 'dataformats:list' request.user.username %}">dataformats</a></li>
<li><a data-toggle="tooltip" data-placement="bottom" title="View all {{ object.author.username }}'s dataformats" href="{% url 'dataformats:list' object.author.username %}">{{ object.author.username }}</a></li>
<li><a title="View the latest version" data-toggle="tooltip" data-placement="bottom" href="{% url 'dataformats:view-latest' object.author.username object.name %}">{{ object.name }}</a></li>
<li>{% if op == "new-version" %}{{ object.version|add:1 }}{% else %}<a data-toggle="tooltip" data-placement="bottom" title="View the object you're forking" href="{% url 'dataformats:view' object.author.username object.name object.version %}">{{ object.version }}</a>{% endif %}</li>
{% endif %}
</h3>
<!-- vertical-center: notice no space between divs -->
</div><div class="col-sm-2 vertical-center">
<div class="action-buttons pull-right">
<button id="save" type="submit" class="btn btn-success btn-sm"><i class="fa fa-save fa-lg"></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-offset-1 col-sm-10">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
Data format not created, please fix the errors below
</div>
{% endif %}
{{ form.non_field_errors }}
{{ form.version }}
{% if op == "fork" or op == "new" %}
<div class="form-group{% if form.name.errors %} has-error{% endif %}" {% if version > 1 %}style="display: none;"{% endif %}>
<label class="control-label" for="id_name">Name:</label>
<input class="form-control" id="id_name" maxlength="100" name="name" type="text" placeholder="Choose a name...">
{{ form.name.errors }}
{% if form.name.help_text %}<p class="help">{{ form.name.help_text|safe }}</p>{% endif %}
</div>
{% else %}
<input id="id_name" name="name" type="hidden" value="{{ object.name }}"/>
{% endif %}
<div class="form-group{% if form.file.errors %} has-error{% endif %}">
<label class="control-label" for="id_file">Declaration:</label>
{{ form.file }}
{{ form.file.errors }}
{% if form.file.help_text %}<p class="help">{{ form.file.help_text|safe }}</p>{% endif %}
</div>
</fieldset>
</div>
</div>
</form>
{% endblock %}
......@@ -41,13 +41,6 @@
<a class="btn btn-default btn-delete" onclick="modal_delete('dataformat', '{{ object.fullname }}', '{% url 'api_dataformats:all' %}', '{% url 'dataformats:list' request.user.username %}');" data-toggle="tooltip" data-placement="bottom" title="Delete"><i class="fa fa-times fa-lg"></i></a>
{% endif %}
<!-- New version, needs to be the owner -->
<a class="btn btn-default btn-new-version" href="{% url 'dataformats:new-version' object.name %}" data-toggle="tooltip" data-placement="bottom" title="New version"><i class="fa fa-copy fa-lg"></i></a>
<!-- Edit, needs to be modifiable -->
{% if object.modifiable %}
<a class="btn btn-default btn-edit" href="{% url 'dataformats:edit' object.author.username object.name object.version %}" data-toggle="tooltip" data-placement="bottom" title="Edit"><i class="fa fa-edit fa-lg"></i></a>
{% endif %}
{% endifequal %}
......@@ -56,9 +49,4 @@
<a class="btn btn-default btn-edit" href="{% url 'admin:dataformats_dataformat_change' object.id %}" data-toggle="tooltip" data-placement="bottom" title="Edit as admin"><i class="fa fa-cogs fa-lg"></i></a>
{% endif %}
{% if not request.user.is_anonymous %}
<!-- Fork button, needs to be logged in -->
<a class="btn btn-default btn-fork" href="{% url 'dataformats: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 %}
</div>
......@@ -47,13 +47,6 @@
<p class="help-block">{{ texts.json|safe }}</p>
</div>
</div>
{% if owner and object.modifiable %}
<div class="col-sm-2 action-buttons">
<a id="btn-edit-declaration" class="btn btn-primary btn-sm pull-right" href="{% url 'dataformats:edit' object.author.username object.name object.version %}"><i class="fa fa-edit fa-lg"></i> Edit</a>
</div>
{% endif %}
</div>
<script type="text/javascript">
......
......@@ -51,12 +51,6 @@
{% endif %}
<!-- Notice there can be no div space if vertical-center is used -->
</div><div class="col-sm-2 vertical-center">
{% if owner %}
<a class="btn btn-success btn-sm pull-right" href="{% url 'dataformats:new' %}"><i class="fa fa-plus fa-lg"></i> New</a>
{% endif %}
</div><!-- col -->
</div><!-- row -->
......
......@@ -33,20 +33,8 @@ from . import views
app_name = "dataformats"
urlpatterns = [
path("new/", views.create, name="new",),
re_path(r"^update/(?P<name>[-\w]+)/$", views.create, name="new-version",),
path("", views.public_ls, name="public-list",),
re_path(r"^(?P<author_name>\w+)/$", views.ls, name="list",),
re_path(
r"^fork/(?P<author>\w+)/(?P<name>[-\w]+)/(?P<version>\d+)/$",
views.fork,
name="fork",
),
re_path(
r"^edit/(?P<author>\w+)/(?P<name>[-\w]+)/(?P<version>\d+)/$",
views.edit,
name="edit",
),
re_path(
r"^diff/(?P<author1>\w+)/(?P<name1>[-\w]+)/(?P<version1>\d+)/(?P<author2>\w+)/(?P<name2>[-\w]+)/(?P<version2>\d+)/$",
views.diff,
......
......@@ -26,18 +26,12 @@
###############################################################################
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import Http404
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_protect
from ..team.models import Team
from .forms import CreationForm
from .forms import UpdateForm
from .models import DataFormat
# ----------------------------------------------------------
......@@ -50,241 +44,6 @@ DEFAULT_DATAFORMAT_TEXT = """{
# ----------------------------------------------------------
@csrf_protect
@login_required
def create(request, name=None):
"""Creates a new data format or a new version of an existing data format
The user must be authenticated before it can add a new data format
"""
# Process the form
form = None
form_args = dict(request_user=request.user, name=name,)
# Retrieves the existing object if possible
previous_version = None
version = 1
if name is not None:
try:
previous_version = DataFormat.objects.filter(
author=request.user, name__iexact=name,
).order_by("-version")[0]
version = previous_version.version + 1
except IndexError:
pass
if request.method == "POST":
form = CreationForm(request.POST, **form_args)
if form.is_valid():
(dataformat, errors) = DataFormat.objects.create_dataformat(
author=request.user,
name=form.cleaned_data["name"],
version=form.cleaned_data["version"],
short_description=form.cleaned_data["short_description"],
description=form.cleaned_data["description"],
declaration=form.cleaned_data["file"],
previous_version=previous_version,
)
if dataformat is not None:
# Redirect to the page of the dataformat just created
return HttpResponseRedirect(
reverse(
"dataformats:view",
args=(
dataformat.author.username,
dataformat.name,
dataformat.version,
),
)
)
for error in errors.split("\n"):
form.add_error("file", error)
if form is None:
if previous_version is not None:
# fill-in with previous version's settings
form_args["initial"] = {
"name": previous_version.name,
"version": version,
"short_description": previous_version.short_description,
"description": previous_version.description,
"file": previous_version.declaration_string,
}
else:
# start from scratch
form_args["initial"] = {
"version": 1,
"file": DEFAULT_DATAFORMAT_TEXT,
}
if form_args["name"]:
form_args["initial"]["name"] = form_args["name"]
form = CreationForm(**form_args)
return render(
request,
"dataformats/edition.html",
{
"form": form,
"object": previous_version,
"op": "new-version" if previous_version else "new",
"edition": False,
},
)
# ----------------------------------------------------------
@csrf_protect
@login_required
def fork(request, author, name, version):
"""Creates a new data format by forking an existing data format
The user must be authenticated before it can add a new data format
"""
# Process the form
form = None
form_args = dict(request_user=request