diff --git a/beat/web/algorithms/views.py b/beat/web/algorithms/views.py index c64cbf44fa1094eda76cbdf73b7459d5696c927f..b6663f2adf569982e9f2eff9658a498c933eac39 100755 --- a/beat/web/algorithms/views.py +++ b/beat/web/algorithms/views.py @@ -35,6 +35,7 @@ from django.contrib.auth.models import User from .models import Algorithm from ..team.models import Team from ..common.texts import Messages +from ..common.utils import ensure_string from ..ui.templatetags.markup import restructuredtext from beat.core import prototypes @@ -72,10 +73,10 @@ def create(request, name=None): previous_version = previous_versions[0] - description = previous_version.description + description = ensure_string(previous_version.description) parameters['algorithm_version'] = previous_version.version + 1 - parameters['declaration'] = previous_version.declaration_file.read().replace('\n', '') + 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', '') @@ -87,7 +88,7 @@ def create(request, name=None): if not previous_version.is_binary(): parameters['source_code'] = previous_version.source_code_file.read() else: - parameters['source_code'] = prototypes.binary_load('algorithm.py') \ + 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') @@ -119,7 +120,7 @@ def fork(request, author, name, version): if fork_of.sharing == Algorithm.USABLE and fork_of.author != request.user: raise Http404() - description = fork_of.description + description = ensure_string(fork_of.description) parameters = {'original_author': author, 'algorithm_author': request.user.username, @@ -127,7 +128,7 @@ def fork(request, author, name, version): 'algorithm_version': fork_of.version, 'algorithm_language': fork_of.json_language, 'algorithm_language_name': fork_of.language_fullname(), - 'declaration': fork_of.declaration_file.read().replace('\n', ''), + 'declaration': fork_of.declaration_string.replace('\n', ''), 'short_description': fork_of.short_description, 'description': description.replace('\n', '\\n'), 'html_description': restructuredtext(description).replace('\n', ''), @@ -168,14 +169,14 @@ def edit(request, author, name, version): return HttpResponseForbidden('Algorithm %s is not modifiable' % \ algorithm.fullname()) - description = algorithm.description + 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_file.read().replace('\n', ''), + 'declaration': algorithm.declaration_string.replace('\n', ''), 'short_description': algorithm.short_description, 'description': description.replace('\n', '\\n'), 'html_description': restructuredtext(description).replace('\n', ''), diff --git a/beat/web/code/models.py b/beat/web/code/models.py index f21db2276f9a30a7541ae9738538d8eafbbbbd89..5a5d3a1ea35c35419b8ab31b578e503a1d9ca63f 100755 --- a/beat/web/code/models.py +++ b/beat/web/code/models.py @@ -483,7 +483,8 @@ class Code(StoredContribution): def language_fullname(self): if self.language in Code.CODE_NAMES: return Code.CODE_NAMES[self.language] - return [language for language in Code.CODE_LANGUAGE if language == self.language][0][1] + + return [language for language in Code.CODE_LANGUAGE if language[0] == self.language][0][1] def json_language(self): diff --git a/beat/web/common/fields.py b/beat/web/common/fields.py index f4aae55dc660088c32b724edda0150312e1bae03..fc932fbbaebdbff1322394d4a89134f72b04c2fc 100644 --- a/beat/web/common/fields.py +++ b/beat/web/common/fields.py @@ -26,9 +26,9 @@ ############################################################################### from rest_framework import serializers +from django.utils import six import simplejson -import six class StringListField(serializers.ListField): diff --git a/beat/web/common/models.py b/beat/web/common/models.py index 34c14ba3d442b6a4791ad41ca2d37ce4f3e6b5c1..324a2f72409ca943bd60ff2cdc60d6cd5b27b1d4 100755 --- a/beat/web/common/models.py +++ b/beat/web/common/models.py @@ -41,6 +41,7 @@ from . import storage from .exceptions import NotUserNorTeam from .signals import shared +from .utils import ensure_string import os import re @@ -640,7 +641,8 @@ def get_declaration(instance): def get_declaration_string(instance): - return storage.get_file_content(instance, 'declaration_file') + data = storage.get_file_content(instance, 'declaration_file') + return ensure_string(data) #---------------------------------------------------------- diff --git a/beat/web/common/serializers.py b/beat/web/common/serializers.py index 933b257a3ec13703b0e1153aa96b6cf4ff7d6110..c7e70d61f4751c58e0735b968c0099c493d1848f 100644 --- a/beat/web/common/serializers.py +++ b/beat/web/common/serializers.py @@ -29,6 +29,7 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.contrib.auth.models import User +from django.utils import six from rest_framework import serializers @@ -45,7 +46,6 @@ import difflib import ast -import six #---------------------------------------------------------- @@ -311,7 +311,7 @@ class MapDot(dict): class ContributionCreationSerializer(serializers.ModelSerializer): declaration = JSONSerializerField(required=False) description = serializers.CharField(required=False, allow_blank=True) - fork_of = serializers.CharField(required=False) + fork_of = serializers.JSONField(required=False) previous_version = serializers.CharField(required=False) class Meta: @@ -341,7 +341,8 @@ class ContributionCreationSerializer(serializers.ModelSerializer): 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'])))) + data['data'] = json.dumps(data['data']) + else: previous_version_id = None @@ -353,11 +354,12 @@ class ContributionCreationSerializer(serializers.ModelSerializer): fork_of_id.username = user.username else: fork_of_id = MapDot() - fork_elem = json.loads(json.dumps(ast.literal_eval(json.loads(json.dumps(data['fork_of']))))) + fork_elem = data['fork_of'] fork_of_id["username"] = fork_elem['username'] fork_of_id["name"] = fork_elem['name'] fork_of_id["version"] = fork_elem['version'] - data['data'] = json.dumps(ast.literal_eval(json.loads(json.dumps(data['data'])))) + data['data'] = json.dumps(data['data']) + else: fork_of_id = None diff --git a/beat/web/common/utils.py b/beat/web/common/utils.py index 122c25fab23df400415f6d54044677bf7a9f5be5..a2c5886e3123c01d0eb73e0d3d7e8e8531e85245 100644 --- a/beat/web/common/utils.py +++ b/beat/web/common/utils.py @@ -28,9 +28,9 @@ """ Reusable help functions """ - from django.core.exceptions import ValidationError from django.utils.encoding import force_text +from django.utils import six from ..ui.templatetags.markup import restructuredtext @@ -117,3 +117,14 @@ def ensure_html(text): return '<pre>{}</pre>'.format(text) else: return restructuredtext(text) + +def ensure_string(data): + """ + Ensure that we have a str object from data which can be either a str in + python 2 or a bytes in python 3 + """ + if data is not None: + if isinstance(data, six.binary_type): + return data.decode('utf-8') + return data + return '' \ No newline at end of file diff --git a/beat/web/experiments/api.py b/beat/web/experiments/api.py index 882b24e32112a3da6c3e64c51111ef2736e926c3..ea7d6d27553963aa1671e94e5e840319a35e49ce 100755 --- a/beat/web/experiments/api.py +++ b/beat/web/experiments/api.py @@ -29,6 +29,7 @@ import re import uuid import simplejson +import functools from django.conf import settings from django.shortcuts import get_object_or_404 @@ -104,6 +105,15 @@ class ListCreateExperimentsView(ListCreateContributionView): def _getStatusLabel(status): return [s for s in Experiment.STATUS if s[0] == status][0][1] + def _cmp(a, b): + """ + cmp is not available anymore in Python 3. This method is implemetend + as recommended in the documentation for this kind of use case. + Based on: + https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons + """ + return (a > b) - (a < b) + def _custom_compare(exp1, exp2): PENDING = _getStatusLabel(Experiment.PENDING) @@ -113,7 +123,7 @@ class ListCreateExperimentsView(ListCreateContributionView): if exp2['status'] != PENDING: return -1 elif exp1['creation_date'] != exp2['creation_date']: - return cmp(exp2['creation_date'], exp1['creation_date']) + return _cmp(exp1['creation_date'], exp2['creation_date']) else: return 1 elif exp2['status'] == PENDING: @@ -123,13 +133,13 @@ class ListCreateExperimentsView(ListCreateContributionView): tier3_states = [_getStatusLabel(Experiment.DONE), _getStatusLabel(Experiment.FAILED)] if (exp1['status'] in tier3_states) and (exp2['status'] in tier3_states): - return cmp(exp2['end_date'], exp1['end_date']) + return _cmp(exp2['end_date'], exp1['end_date']) elif (exp1['status'] in tier3_states) and (exp2['status'] not in tier3_states): return -1 elif (exp1['status'] not in tier3_states) and (exp2['status'] in tier3_states): return 1 - return cmp(exp1['start_date'], exp2['start_date']) + return _cmp(exp1['start_date'], exp2['start_date']) # Retrieve the experiments fields_to_return_original = self.get_serializer_fields(request) @@ -140,8 +150,10 @@ class ListCreateExperimentsView(ListCreateContributionView): experiments = self.model.objects.from_author_and_public(request.user, author_name).select_related() serializer = self.get_serializer(experiments, many=True, fields=fields_to_return) - result = serializer.data - result.sort(_custom_compare) + if six.PY2: + result = sorted(serializer.data, cmp=_custom_compare) + else: + result = sorted(serializer.data, key=functools.cmp_to_key(_custom_compare)) processed_result = [] for experiment in result: diff --git a/beat/web/experiments/models/experiment.py b/beat/web/experiments/models/experiment.py index 60e589d7da04676bbb337ae09f93ad753e7bca62..d09485e832ccc399378f5f155037eb79ed10622a 100755 --- a/beat/web/experiments/models/experiment.py +++ b/beat/web/experiments/models/experiment.py @@ -48,6 +48,9 @@ from ...common.models import get_contribution_declaration_filename from ...common.models import get_contribution_description_filename from ...common.models import get_description from ...common.models import set_description +from ...common.models import get_declaration +from ...common.models import set_declaration +from ...common.models import get_declaration_string from ...common import storage from ...common.exceptions import ShareError @@ -749,23 +752,9 @@ class Experiment(Shareable): #_____ Properties __________ - def set_declaration(self, value): - if isinstance(value, dict): - value = simplejson.dumps(value, indent=4, cls=NumpyJSONEncoder) - - storage.set_file_content(self, 'declaration_file', self.declaration_filename(), value) - - def get_declaration(self): - return simplejson.loads(storage.get_file_content(self, 'declaration_file')) - - declaration = property(get_declaration, set_declaration) - description = property(get_description, set_description) - - - @property - def declaration_string(self): - return storage.get_file_content(self, 'declaration_file') + declaration = property(get_declaration, set_declaration) + declaration_string = property(beat.web.common.models.get_declaration_string) def schedule(self): @@ -788,6 +777,6 @@ class Experiment(Shareable): author = username or self.author name = name or self.name xp, _, __ = Experiment.objects.create_experiment(author, - self.toolchain, name, self.get_declaration(), + self.toolchain, name, self.declaration, self.short_description, self.description) return xp diff --git a/beat/web/experiments/serializers.py b/beat/web/experiments/serializers.py index c873f1e3cdc85dd9f40c562d309177adf5e166cf..fb6e31ef238339d88b454e15c2178a38ba25aae9 100755 --- a/beat/web/experiments/serializers.py +++ b/beat/web/experiments/serializers.py @@ -275,13 +275,13 @@ class ExperimentResultsSerializer(ShareableSerializer): return serializer.data def get_html_description(self, obj): - d = obj.description.decode('utf-8') + d = obj.description if len(d) > 0: return restructuredtext(d) return '' def get_description(self, obj): - return obj.description.decode('utf-8') + return obj.description def get_display_start_date(self, obj): if obj.start_date is None: diff --git a/beat/web/navigation/templates/navigation/contact.html b/beat/web/navigation/templates/navigation/contact.html index 8d0c4523150ffd86735abcba39533b806c646186..f4257e906461554db7b9a997e20a8d4233ab4aed 100644 --- a/beat/web/navigation/templates/navigation/contact.html +++ b/beat/web/navigation/templates/navigation/contact.html @@ -31,7 +31,9 @@ <ul class="list-group"> - <li class="list-group-item"><i class="fa fa-envelope"></i> <a href="mailto:beat.support@idiap.ch">beat.support@idiap.ch</a></li> + <li class="list-group-item"><i class="fa fa-envelope"></i> <a href="mailto:beat.support@idiap.ch">Email for support questions</a></li> + + <li class="list-group-item"><i class="fa fa-fire"></i> <a href="mailto:beat.security@idiap.ch">Email for security related issues</a></li> <li class="list-group-item"><i class="fa fa-globe"></i> <a href="https://www.beat-eu.org">https://www.beat-eu.org</a></li> diff --git a/beat/web/plotters/admin.py b/beat/web/plotters/admin.py index 83d0dd6a3f9fbe8f02b24cf9c08e2a4f3c6f263e..d3f9cc85ca64f06e4761660de3b177ce18e75a5f 100644 --- a/beat/web/plotters/admin.py +++ b/beat/web/plotters/admin.py @@ -27,11 +27,10 @@ '''Administrative add-on for the extended Django User model''' -import six - from django.contrib import admin from django import forms from django.core.files.base import ContentFile +from django.utils import six from ..common.texts import Messages @@ -134,7 +133,9 @@ class PlotterModelForm(forms.ModelForm): if 'declaration_file' in self.data and \ isinstance(self.data['declaration_file'], six.string_types): - self.data['declaration_file'] = ContentFile(self.data['declaration_file'], name='unsaved') + mutable_data = self.data.copy() + mutable_data['declaration_file'] = ContentFile(self.data['declaration_file'], name='unsaved') + self.data = mutable_data #---------------------------------------------------------- diff --git a/beat/web/plotters/api.py b/beat/web/plotters/api.py index 2dd32f4c16b2ccdf548901dc84f9f00b6b27e90e..48e283a44737846de146597b0ab9c0d4e2c1a51f 100644 --- a/beat/web/plotters/api.py +++ b/beat/web/plotters/api.py @@ -110,20 +110,13 @@ class ListPlotterParameterView(ListContributionView): class ListDefaultPlotterView(generics.ListAPIView): """ - List all available plotters parameters + List all available plotters """ model = DefaultPlotter serializer_class = DefaultPlotterSerializer - def list(self, request): - queryset = self.get_queryset() - - serializer = DefaultPlotterSerializer(queryset, many=True, context={'request': request}) - - return Response(serializer.data) def get_queryset(self): - queryset = DefaultPlotter.objects.all() - return queryset + return self.model.objects.all() #---------------------------------------------------------- diff --git a/beat/web/plotters/serializers.py b/beat/web/plotters/serializers.py index a3b0cb78f23b6cc4ed2f47667ed6c706f56881f2..6a8d5cb260f16ad0e16a29c6febd1b2482d31dc8 100644 --- a/beat/web/plotters/serializers.py +++ b/beat/web/plotters/serializers.py @@ -112,6 +112,7 @@ class PlotterParameterCreationFailedException(Exception): pass class PlotterParameterCreationSerializer(ContributionCreationSerializer): + data = serializers.JSONField(required=False) class Meta(ContributionCreationSerializer.Meta): model = PlotterParameter @@ -223,10 +224,9 @@ class FullPlotterParameterSerializer(PlotterParameterAllSerializer): def get_plotters(self, obj): all_plotters = Plotter.objects.all() - serializer = FullPlotterSerializer results = {} for plotter in all_plotters.iterator(): - serializer = FullPlotterSerializer(plotter, fields=['id', 'accessibility', 'modifiable', 'deletable', 'is_owner', 'name', 'fork_of', 'last_version', 'previous_version', 'short_description', 'description', 'version', 'creation_date', 'data', 'sample_data', 'declaration']) + serializer = FullPlotterSerializer(plotter, context=self.context, fields=['id', 'accessibility', 'modifiable', 'deletable', 'is_owner', 'name', 'fork_of', 'last_version', 'previous_version', 'short_description', 'description', 'version', 'creation_date', 'data', 'sample_data', 'declaration']) results[plotter.fullname()] = serializer.data return results diff --git a/beat/web/plotters/views.py b/beat/web/plotters/views.py index 286ab510b92b513926dfcae204482c110e4d0d69..6021c9315f457e2c90d93d38b45b8cdad26a5abc 100644 --- a/beat/web/plotters/views.py +++ b/beat/web/plotters/views.py @@ -29,7 +29,6 @@ import os import itertools import base64 import collections -import six import logging logger = logging.getLogger(__name__) @@ -37,6 +36,7 @@ logger = logging.getLogger(__name__) import simplejson from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest +from django.http import Http404 from django.conf import settings from django.shortcuts import render from django.shortcuts import get_object_or_404 @@ -44,6 +44,7 @@ from django.template import Context from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.views.generic import TemplateView +from django.utils import six from ..experiments.models import Experiment, Block, Result from ..dataformats.models import DataFormat @@ -406,29 +407,28 @@ def plot_sample(request): # Collect the data for the plot, check compatibility - default = None - - # check compatibility, fill up defaults - if default is None: - default = DefaultPlotter.objects.filter(plotter=final_plotter) + default = DefaultPlotter.objects.filter(plotter=final_plotter) - if not default and not final_plotter: - message = 'No plotter specified and no default for plot format %s' % final_plotter.fullname() - return HttpResponseBadRequest(message) + if not default.count() and not final_plotter: + message = 'No plotter specified and no default for plot format %s' % final_plotter.fullname() + return HttpResponseBadRequest(message) + # check compatibility, fill up defaults + if default.count(): default = default[0] #get the first and only # set defaults, if specific values have not already been set if not final_plotter: final_plotter = default.plotter + else: + default = None - if corefmt is None: #loads it once - corefmt = final_plotter.core_format() + if corefmt is None: #loads it once + corefmt = final_plotter.core_format() ## loads the data, for that particular result parsed = simplejson.loads(use['sample_data']) sample_data = corefmt.type().from_dict(parsed, casting='unsafe') - # checks the plotter is valid core_plotter = final_plotter.core() if not core_plotter.valid: @@ -438,7 +438,7 @@ def plot_sample(request): # resolves parameters, in order of priority final_parameters = {} - if default.parameter: #fills-up defaults for the type + if default and default.parameter: #fills-up defaults for the type final_parameters.update(simplejson.loads(default.parameter.data)) if user_parameter: #fills-up defaults from the user set final_parameters.update(simplejson.loads(user_parameter.data)) diff --git a/beat/web/search/serializers.py b/beat/web/search/serializers.py index cd82b03aa1908acf93f51d5cf2bbc0f334609f0a..429857f258eb3dc615b1f3e5f55d000fc124014b 100644 --- a/beat/web/search/serializers.py +++ b/beat/web/search/serializers.py @@ -26,6 +26,7 @@ ############################################################################### from rest_framework import serializers +from django.utils import six from ..common.serializers import VersionableSerializer from ..ui.templatetags.markup import restructuredtext @@ -35,8 +36,6 @@ from .fields import DictListField import simplejson as json -import six - class SearchResultSerializer(serializers.Serializer): name = serializers.SerializerMethodField() diff --git a/beat/web/toolchains/views.py b/beat/web/toolchains/views.py index 55ffa8b42527fd1df2c620eb2813f1f4e49d3bd3..5033ee8316e30bd360f01999a22b1d5c8c4c6d0e 100644 --- a/beat/web/toolchains/views.py +++ b/beat/web/toolchains/views.py @@ -32,11 +32,13 @@ from django.contrib.auth.decorators import login_required from django.conf import settings from django.contrib.auth.models import User from django.db.models.functions import Coalesce +from django.utils import six from .models import Toolchain from ..team.models import Team from ..reports.models import Report from ..common.texts import Messages +from ..common.utils import ensure_string from ..ui.templatetags.markup import restructuredtext from beat.core import prototypes @@ -72,7 +74,7 @@ def create(request, name=None): previous_version = previous_versions[0] - description = previous_version.description + description = ensure_string(previous_version.description) parameters['toolchain_version'] = previous_version.version + 1 parameters['declaration'] = previous_version.declaration_string.replace('\n', '') @@ -103,7 +105,8 @@ def fork(request, author, name, version): version=int(version) ) - description = fork_of.description + description = ensure_string(fork_of.description) + errors = ensure_string(fork_of.errors) parameters = {'toolchain_author': request.user.username, 'toolchain_name': name, @@ -112,7 +115,7 @@ def fork(request, author, name, version): '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 '', + 'errors': errors.replace('\n', '\\n'), 'edition': False, 'messages': Messages, } @@ -139,7 +142,8 @@ def edit(request, author, name, version): version=int(version) ) - description = toolchain.description + description = ensure_string(toolchain.description) + errors = ensure_string(toolchain.errors) # Render the page return render(request, @@ -151,7 +155,7 @@ def edit(request, author, name, version): 'short_description': toolchain.short_description, 'description': description.replace('\n', '\\n'), 'html_description': restructuredtext(description).replace('\n', ''), - 'errors': toolchain.errors.replace('\n', '\\n') if toolchain.errors is not None else '', + 'errors': errors.replace('\n', '\\n'), 'edition': True, 'messages': Messages, }) diff --git a/beat/web/ui/templatetags/gravatar.py b/beat/web/ui/templatetags/gravatar.py index b7b2876a1da5dd880f6aa0551789d0872eebd8a5..87566080d115541ce979c3939788849f8ceacf29 100644 --- a/beat/web/ui/templatetags/gravatar.py +++ b/beat/web/ui/templatetags/gravatar.py @@ -28,9 +28,9 @@ from django.conf import settings from django import template from django.template.defaultfilters import stringfilter +from django.utils import six import hashlib -import six register = template.Library() diff --git a/beat/web/utils/management/commands/list_active_users.py b/beat/web/utils/management/commands/list_active_users.py index df3ffd28e9adcafb9fa80fe92a4a398f67c228a4..2ee07a79b3aa46ce928ea016b0dda05f01303cdb 100644 --- a/beat/web/utils/management/commands/list_active_users.py +++ b/beat/web/utils/management/commands/list_active_users.py @@ -27,7 +27,7 @@ ############################################################################### -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from django.contrib.auth.models import User from django.contrib.sessions.models import Session from django.utils import timezone