diff --git a/beat/web/experiments/api.py b/beat/web/experiments/api.py index ea7d6d27553963aa1671e94e5e840319a35e49ce..4912704bd5de8df23b0a767f3e948c2e99c1ed4a 100755 --- a/beat/web/experiments/api.py +++ b/beat/web/experiments/api.py @@ -42,50 +42,56 @@ from rest_framework import generics from rest_framework import serializers from rest_framework.views import APIView from rest_framework.reverse import reverse +from rest_framework.errors import ParseError import beat.core.hash import beat.core.algorithm import beat.core.toolchain from .models import Experiment -from .models import Block, CachedFile from .serializers import ExperimentSerializer, ExperimentResultsSerializer from .permissions import IsDatabaseAccessible from ..common.responses import BadRequestResponse, ForbiddenResponse -from ..common.api import ShareView, ListContributionView, ListCreateContributionView, RetrieveUpdateDestroyContributionView +from ..common.api import ( + ShareView, + ListContributionView, + ListCreateContributionView, + RetrieveUpdateDestroyContributionView, +) from ..common.mixins import CommonContextMixin from ..common.exceptions import ShareError from ..common.serializers import SharingSerializer from ..common.utils import validate_restructuredtext, ensure_html -from ..backend.models import Environment - -from ..algorithms.models import Algorithm - from ..toolchains.models import Toolchain -#---------------------------------------------------------- +# ---------------------------------------------------------- class ListExperimentsView(ListContributionView): """ Return all accessible experiments """ + model = Experiment serializer_class = ExperimentSerializer def get(self, request, *args, **kwargs): fields_to_return = self.get_serializer_fields(request) - all_contributions = self.get_queryset().order_by('author__username', 'name').select_related() + all_contributions = ( + self.get_queryset().order_by("author__username", "name").select_related() + ) - serializer = self.get_serializer(all_contributions, many=True, fields=fields_to_return) + serializer = self.get_serializer( + all_contributions, many=True, fields=fields_to_return + ) return Response(serializer.data) -#---------------------------------------------------------- +# ---------------------------------------------------------- class ListCreateExperimentsView(ListCreateContributionView): @@ -93,6 +99,7 @@ class ListCreateExperimentsView(ListCreateContributionView): Read/Write end point that list the experiments available from a given author and allows the creation of new experiments """ + model = Experiment serializer_class = ExperimentSerializer @@ -100,7 +107,6 @@ class ListCreateExperimentsView(ListCreateContributionView): # happy if we don't declare one writing_serializer_class = ExperimentSerializer - def get(self, request, author_name): def _getStatusLabel(status): return [s for s in Experiment.STATUS if s[0] == status][0][1] @@ -119,37 +125,48 @@ class ListCreateExperimentsView(ListCreateContributionView): # Put the pending experiments first, the running/scheduled ones next and the # others at the end (ordered by end date) - if exp1['status'] == PENDING: - if exp2['status'] != PENDING: + if exp1["status"] == PENDING: + if exp2["status"] != PENDING: return -1 - elif exp1['creation_date'] != exp2['creation_date']: - return _cmp(exp1['creation_date'], exp2['creation_date']) + elif exp1["creation_date"] != exp2["creation_date"]: + return _cmp(exp1["creation_date"], exp2["creation_date"]) else: return 1 - elif exp2['status'] == PENDING: - if exp1['status'] != PENDING: + elif exp2["status"] == PENDING: + if exp1["status"] != PENDING: return 1 - tier3_states = [_getStatusLabel(Experiment.DONE), _getStatusLabel(Experiment.FAILED)] + 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']) - elif (exp1['status'] in tier3_states) and (exp2['status'] not in tier3_states): + if (exp1["status"] in tier3_states) and (exp2["status"] in tier3_states): + 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): + 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) - mandatory_fields = set(['status', 'creation_date', 'start_date', 'end_date']) + mandatory_fields = set(["status", "creation_date", "start_date", "end_date"]) fields_to_return = list(mandatory_fields.union(fields_to_return_original)) - experiments = self.model.objects.from_author_and_public(request.user, author_name).select_related() + 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) + serializer = self.get_serializer( + experiments, many=True, fields=fields_to_return + ) if six.PY2: result = sorted(serializer.data, cmp=_custom_compare) else: @@ -169,72 +186,83 @@ class ListCreateExperimentsView(ListCreateContributionView): def post(self, request, author_name): data = request.data - if 'name' in data: - if not(isinstance(data['name'], six.string_types)): - return BadRequestResponse('Invalid name') + if "name" in data: + if not (isinstance(data["name"], six.string_types)): + return BadRequestResponse("Invalid name") - max_length = self.model._meta.get_field('name').max_length - current_length = len(data['name']) + max_length = self.model._meta.get_field("name").max_length + current_length = len(data["name"]) if current_length > max_length: - return BadRequestResponse('name too long, max is {}, current is {}'.format(max_length, current_length)) + return BadRequestResponse( + "name too long, max is {}, current is {}".format( + max_length, current_length + ) + ) - name = re.sub(r'[\W]', '-', data['name']) + name = re.sub(r"[\W]", "-", data["name"]) else: name = None - if 'short_description' in data: - if not(isinstance(data['short_description'], six.string_types)): - return BadRequestResponse('Invalid short_description') + if "short_description" in data: + if not (isinstance(data["short_description"], six.string_types)): + return BadRequestResponse("Invalid short_description") - max_length = self.model._meta.get_field('short_description').max_length - current_length = len(data['short_description']) + max_length = self.model._meta.get_field("short_description").max_length + current_length = len(data["short_description"]) if current_length > max_length: - return BadRequestResponse('Short description too long, max is {}, current is {}'.format(max_length, current_length)) + return BadRequestResponse( + "Short description too long, max is {}, current is {}".format( + max_length, current_length + ) + ) - short_description = data['short_description'] + short_description = data["short_description"] else: - short_description = '' + short_description = "" - if 'description' in data: - if not(isinstance(data['description'], six.string_types)): - raise serializers.ValidationError({'description': 'Invalid description data'}) - description = data['description'] + if "description" in data: + if not (isinstance(data["description"], six.string_types)): + raise serializers.ValidationError( + {"description": "Invalid description data"} + ) + description = data["description"] try: validate_restructuredtext(description) except ValidationError as errors: - raise serializers.ValidationError({'description': [error for error in errors]}) + raise serializers.ValidationError( + {"description": [error for error in errors]} + ) else: description = None - if 'toolchain' not in data: - return BadRequestResponse('Must indicate a toolchain name') + if "toolchain" not in data: + return BadRequestResponse("Must indicate a toolchain name") - if not(isinstance(data['toolchain'], six.string_types)): - return BadRequestResponse('Invalid toolchain name') + if not (isinstance(data["toolchain"], six.string_types)): + return BadRequestResponse("Invalid toolchain name") - if 'declaration' not in data: - return BadRequestResponse('Must indicate a declaration') + if "declaration" not in data: + return BadRequestResponse("Must indicate a declaration") - if not(isinstance(data['declaration'], dict)) and \ - not(isinstance(data['declaration'], six.string_types)): - return BadRequestResponse('Invalid declaration') + if not (isinstance(data["declaration"], dict)) and not ( + isinstance(data["declaration"], six.string_types) + ): + return BadRequestResponse("Invalid declaration") - if isinstance(data['declaration'], dict): - declaration = data['declaration'] + if isinstance(data["declaration"], dict): + declaration = data["declaration"] else: - declaration_string = data['declaration'] + declaration_string = data["declaration"] try: declaration = simplejson.loads(declaration_string) - except: - return BadRequestResponse('Invalid declaration data') - + except simplejson.errors.JSONDecodeError: + return BadRequestResponse("Invalid declaration data") # Retrieve the toolchain - core_toolchain = beat.core.toolchain.Storage(settings.PREFIX, - data['toolchain']) + core_toolchain = beat.core.toolchain.Storage(settings.PREFIX, data["toolchain"]) - if core_toolchain.version == 'unknown': - return BadRequestResponse('Unknown toolchain version') + if core_toolchain.version == "unknown": + return BadRequestResponse("Unknown toolchain version") if core_toolchain.username is None: core_toolchain.username = request.user.username @@ -246,11 +274,13 @@ class ListCreateExperimentsView(ListCreateContributionView): version=core_toolchain.version, ) except Toolchain.DoesNotExist: - return Response('Toolchain %s not found' % data['toolchain'], status=404) - + return Response("Toolchain %s not found" % data["toolchain"], status=404) # Create the experiment object in the database - existing_names = map(lambda x: x.name, Experiment.objects.filter(author=request.user, toolchain=db_toolchain)) + existing_names = map( + lambda x: x.name, + Experiment.objects.filter(author=request.user, toolchain=db_toolchain), + ) if name is None: name = str(uuid.uuid4()) while name in existing_names: @@ -273,173 +303,202 @@ class ListCreateExperimentsView(ListCreateContributionView): # Send the result result = { - 'name': experiment.fullname(), - 'url': reverse('api_experiments:all') + experiment.fullname() + '/', + "name": experiment.fullname(), + "url": reverse("api_experiments:all") + experiment.fullname() + "/", } response = Response(result, status=201) - response['Location'] = result['url'] + response["Location"] = result["url"] return response -#---------------------------------------------------------- +# ---------------------------------------------------------- class RetrieveUpdateDestroyExperimentView(RetrieveUpdateDestroyContributionView): """ Read/Write/Delete endpoint for a given experiment """ + model = Experiment serializer_class = ExperimentResultsSerializer - def get_queryset(self): - author_name = self.kwargs.get('author_name') - object_name = self.kwargs.get('object_name') + author_name = self.kwargs.get("author_name") + object_name = self.kwargs.get("object_name") user = self.request.user - return self.model.objects.for_user(user, True).filter(author__username__iexact=author_name, name__iexact=object_name) - + return self.model.objects.for_user(user, True).filter( + author__username__iexact=author_name, name__iexact=object_name + ) - def get(self, request, author_name, toolchain_author_name, toolchain_name, version, name): + def get( + self, request, author_name, toolchain_author_name, toolchain_name, version, name + ): if toolchain_author_name is None: toolchain_author_name = author_name - experiment = get_object_or_404(self.model.objects.for_user(request.user, True), - author__username=author_name, - toolchain__author__username=toolchain_author_name, - toolchain__name=toolchain_name, - toolchain__version=version, - name=name - ) + experiment = get_object_or_404( + self.model.objects.for_user(request.user, True), + author__username=author_name, + toolchain__author__username=toolchain_author_name, + toolchain__name=toolchain_name, + toolchain__version=version, + name=name, + ) - fields_to_return = self.get_serializer_fields(request, allow_sharing=(request.user == experiment.author)) + fields_to_return = self.get_serializer_fields( + request, allow_sharing=(request.user == experiment.author) + ) serializer = self.get_serializer(experiment, fields=fields_to_return) return Response(serializer.data) - - def put(self, request, author_name, toolchain_author_name, toolchain_name, version, name): + def put( + self, request, author_name, toolchain_author_name, toolchain_name, version, name + ): if version is None: - raise ValidationError({'version': 'A version number must be provided'}) + raise ValidationError({"version": "A version number must be provided"}) if toolchain_author_name is None: toolchain_author_name = author_name - experiment = get_object_or_404(self.model, - author__username=author_name, - toolchain__author__username=toolchain_author_name, - toolchain__name=toolchain_name, - toolchain__version=version, - name=name - ) + experiment = get_object_or_404( + self.model, + author__username=author_name, + toolchain__author__username=toolchain_author_name, + toolchain__name=toolchain_name, + toolchain__version=version, + name=name, + ) try: data = request.data except ParseError as e: - raise serializers.ValidationError({'data': str(e)}) + raise serializers.ValidationError({"data": str(e)}) else: if not data: - raise serializers.ValidationError({'data': 'Empty'}) + raise serializers.ValidationError({"data": "Empty"}) if experiment.status != Experiment.PENDING: # We always allow documentation-only updates - allowed_fields = ['short_description', 'description'] + allowed_fields = ["short_description", "description"] # When an experiment is done/failed, allow to change its name if experiment.status in (Experiment.DONE, Experiment.FAILED): - allowed_fields.append('name') + allowed_fields.append("name") if len(data.keys()) > len(allowed_fields): - return BadRequestResponse('Experiment is not in pending state') + return BadRequestResponse("Experiment is not in pending state") for key in data.keys(): if key not in allowed_fields: - return BadRequestResponse('Experiment is not in pending state') + return BadRequestResponse("Experiment is not in pending state") # Available fields (not returned by default): # - html_description - if 'fields' in request.GET: - fields_to_return = request.GET['fields'].split(',') + if "fields" in request.GET: + fields_to_return = request.GET["fields"].split(",") else: fields_to_return = [] - if 'name' in data: - if not(isinstance(data['name'], six.string_types)): - raise serializers.ValidationError({'name': 'Invalid name'}) - name = data['name'].replace(' ', '_') + if "name" in data: + if not (isinstance(data["name"], six.string_types)): + raise serializers.ValidationError({"name": "Invalid name"}) + name = data["name"].replace(" ", "_") if name == experiment.name: name = None else: name = None - if 'short_description' in data: - if not(isinstance(data['short_description'], six.string_types)): - raise serializers.ValidationError({'short_description': 'Invalid short_description'}) - short_description = data['short_description'] + if "short_description" in data: + if not (isinstance(data["short_description"], six.string_types)): + raise serializers.ValidationError( + {"short_description": "Invalid short_description"} + ) + short_description = data["short_description"] else: short_description = None - if 'description' in data: - if not(isinstance(data['description'], six.string_types)): - raise serializers.ValidationError({'description': 'Invalid description data'}) - description = data['description'] + if "description" in data: + if not (isinstance(data["description"], six.string_types)): + raise serializers.ValidationError( + {"description": "Invalid description data"} + ) + description = data["description"] try: validate_restructuredtext(description) except ValidationError as errors: - raise serializers.ValidationError({'description': [error for error in errors]}) + raise serializers.ValidationError( + {"description": [error for error in errors]} + ) else: description = None - if 'declaration' in data: - if not(isinstance(data['declaration'], dict)) and \ - not(isinstance(data['declaration'], six.string_types)): - raise serializers.ValidationError({'declaration': 'Invalid declaration'}) + if "declaration" in data: + if not (isinstance(data["declaration"], dict)) and not ( + isinstance(data["declaration"], six.string_types) + ): + raise serializers.ValidationError( + {"declaration": "Invalid declaration"} + ) - if isinstance(data['declaration'], dict): - declaration = data['declaration'] + if isinstance(data["declaration"], dict): + declaration = data["declaration"] declaration_string = simplejson.dumps(declaration, indent=4) else: - declaration_string = data['declaration'] + declaration_string = data["declaration"] try: declaration = simplejson.loads(declaration_string) - except: - raise serializers.ValidationError({'declaration' :'Invalid declaration data'}) + except simplejson.errors.JSONDecodeError: + raise serializers.ValidationError( + {"declaration": "Invalid declaration data"} + ) - if 'description' in declaration: + if "description" in declaration: if short_description is not None: - raise serializers.ValidationError({'short_description': 'A short description is already provided in the experiment declaration'}) + raise serializers.ValidationError( + { + "short_description": "A short description is already provided in the experiment declaration" + } + ) - short_description = declaration['description'] + short_description = declaration["description"] elif short_description is not None: - declaration['description'] = short_description + declaration["description"] = short_description declaration_string = simplejson.dumps(declaration, indent=4) else: declaration = None - if (short_description is not None) and (len(short_description) > self.model._meta.get_field('short_description').max_length): - raise serializers.ValidationError({'short_description': 'Short description too long'}) - + if (short_description is not None) and ( + len(short_description) + > self.model._meta.get_field("short_description").max_length + ): + raise serializers.ValidationError( + {"short_description": "Short description too long"} + ) - if 'toolchain' in data: - if not(isinstance(data['toolchain'], six.string_types)): - raise serializers.ValidationError({'toolchain': 'Invalid toolchain name'}) + if "toolchain" in data: + if not (isinstance(data["toolchain"], six.string_types)): + raise serializers.ValidationError( + {"toolchain": "Invalid toolchain name"} + ) - toolchain_name = data['toolchain'] + toolchain_name = data["toolchain"] if toolchain_name == experiment.toolchain.fullname(): toolchain_name = None else: toolchain_name = None - # Handle the (optional) modification of the toolchain if toolchain_name is not None: - core_toolchain = beat.core.toolchain.Storage(settings.PREFIX, - toolchain_name) + core_toolchain = beat.core.toolchain.Storage( + settings.PREFIX, toolchain_name + ) - if core_toolchain.version == 'unknown': - return BadRequestResponse('Error, unknown toolchain version') + if core_toolchain.version == "unknown": + return BadRequestResponse("Error, unknown toolchain version") if core_toolchain.username is None: core_toolchain.username = request.user.username @@ -451,18 +510,22 @@ class RetrieveUpdateDestroyExperimentView(RetrieveUpdateDestroyContributionView) version=core_toolchain.version, ) except Toolchain.DoesNotExist: - return Response('Toolchain %s not found' % toolchain_name, status=404) + return Response("Toolchain %s not found" % toolchain_name, status=404) else: db_toolchain = experiment.toolchain - # Handle the (optional) modification of the name if (name is not None) or (toolchain_name is not None): - existing_names = map(lambda x: x.name, Experiment.objects.filter(author=request.user, toolchain=db_toolchain)) - name_to_check = (name if name is not None else experiment.name) + existing_names = map( + lambda x: x.name, + Experiment.objects.filter(author=request.user, toolchain=db_toolchain), + ) + name_to_check = name if name is not None else experiment.name if name_to_check in existing_names: - return BadRequestResponse("The name '" + name_to_check + "' is already used") + return BadRequestResponse( + "The name '" + name_to_check + "' is already used" + ) if name is not None: experiment.name = name @@ -470,7 +533,7 @@ class RetrieveUpdateDestroyExperimentView(RetrieveUpdateDestroyContributionView) # Handle the (optional) modification of the description if (short_description is not None) and (declaration is None): tmp_declaration = experiment.declaration - tmp_declaration['description'] = short_description + tmp_declaration["description"] = short_description experiment.declaration = tmp_declaration if description is not None: @@ -489,89 +552,101 @@ class RetrieveUpdateDestroyExperimentView(RetrieveUpdateDestroyContributionView) core_obj = experiment.core() if not core_obj.valid: - error_details = "The experiment isn't valid, due to the following errors:\n%s\n" % '\n'.join(core_obj.errors) + error_details = ( + "The experiment isn't valid, due to the following errors:\n%s\n" + % "\n".join(core_obj.errors) + ) return BadRequestResponse(error_details) # Send the result result = { - 'name': experiment.fullname(), - 'url': reverse('api_experiments:all') + experiment.fullname() + '/', - 'view_url': experiment.get_absolute_url(), + "name": experiment.fullname(), + "url": reverse("api_experiments:all") + experiment.fullname() + "/", + "view_url": experiment.get_absolute_url(), } # Retrieve the description in HTML format (if necessary) - if 'html_description' in fields_to_return: + if "html_description" in fields_to_return: description = experiment.description if len(description) > 0: - result['html_description'] = ensure_html(description) + result["html_description"] = ensure_html(description) else: - result['html_description'] = '' + result["html_description"] = "" response = Response(result) - response['Location'] = result['url'] + response["Location"] = result["url"] return response - - def delete(self, request, author_name, toolchain_author_name, toolchain_name, version, name): + def delete( + self, request, author_name, toolchain_author_name, toolchain_name, version, name + ): if toolchain_author_name is None: toolchain_author_name = author_name # Retrieve the experiment - experiment = get_object_or_404(Experiment, - author__username=author_name, - toolchain__author__username=toolchain_author_name, - toolchain__name=toolchain_name, - toolchain__version=version, - name=name - ) + experiment = get_object_or_404( + Experiment, + author__username=author_name, + toolchain__author__username=toolchain_author_name, + toolchain__name=toolchain_name, + toolchain__version=version, + name=name, + ) # Check that the experiment can still be deleted - if not(experiment.deletable()): - return ForbiddenResponse("The experiment isn't deletable anymore (needed by an attestation)") + if not (experiment.deletable()): + return ForbiddenResponse( + "The experiment isn't deletable anymore (needed by an attestation)" + ) # Deletion of the experiment experiment.delete() return Response(status=204) -#---------------------------------------------------------- +# ---------------------------------------------------------- class StartExperimentView(APIView): """ Start to run an experiment """ + permission_classes = [permissions.IsAuthenticated, IsDatabaseAccessible] - def post(self, request, author_name, toolchain_author_name, toolchain_name, version, name): + def post( + self, request, author_name, toolchain_author_name, toolchain_name, version, name + ): if toolchain_author_name is None: toolchain_author_name = author_name # Retrieve the experiment - experiment = get_object_or_404(Experiment.objects.for_user(request.user, True), - author__username=author_name, - toolchain__author__username=toolchain_author_name, - toolchain__name=toolchain_name, - toolchain__version=version, - name=name - ) + experiment = get_object_or_404( + Experiment.objects.for_user(request.user, True), + author__username=author_name, + toolchain__author__username=toolchain_author_name, + toolchain__name=toolchain_name, + toolchain__version=version, + name=name, + ) self.check_object_permissions(request, experiment) experiment.schedule() # Send the result - result = { 'name': experiment.fullname(), - 'url': reverse('api_experiments:all') + experiment.fullname() + '/', - } + result = { + "name": experiment.fullname(), + "url": reverse("api_experiments:all") + experiment.fullname() + "/", + } response = Response(result, status=200) - response['Location'] = result['url'] + response["Location"] = result["url"] return response -#---------------------------------------------------------- +# ---------------------------------------------------------- class CancelExperimentView(APIView): @@ -581,45 +656,51 @@ class CancelExperimentView(APIView): permission_classes = [permissions.IsAuthenticated] - def post(self, request, author_name, toolchain_author_name, toolchain_name, version, name): + def post( + self, request, author_name, toolchain_author_name, toolchain_name, version, name + ): if toolchain_author_name is None: toolchain_author_name = author_name # Retrieve the experiment - experiment = get_object_or_404(Experiment, - author__username=author_name, - toolchain__author__username=toolchain_author_name, - toolchain__name=toolchain_name, - toolchain__version=version, - name=name - ) + experiment = get_object_or_404( + Experiment, + author__username=author_name, + toolchain__author__username=toolchain_author_name, + toolchain__name=toolchain_name, + toolchain__version=version, + name=name, + ) experiment.cancel() return Response(status=200) -#---------------------------------------------------------- +# ---------------------------------------------------------- -class RetrieveExperimentResultsFromAttestationView(CommonContextMixin, generics.RetrieveAPIView): +class RetrieveExperimentResultsFromAttestationView( + CommonContextMixin, generics.RetrieveAPIView +): """ Return the results of the experiment corresponding to given attestation """ + queryset = Experiment.objects.all() serializer_class = ExperimentResultsSerializer permission_classes = [permissions.AllowAny] - lookup_field = 'attestation__number' - lookup_url_kwarg = 'attestation_number' + lookup_field = "attestation__number" + lookup_url_kwarg = "attestation_number" def get(self, request, *args, **kwargs): - serializer = self.get_serializer(self.get_object(), fields=['results']) + serializer = self.get_serializer(self.get_object(), fields=["results"]) return Response(serializer.data) -#---------------------------------------------------------- +# ---------------------------------------------------------- class ShareExperimentView(ShareView): @@ -627,32 +708,35 @@ class ShareExperimentView(ShareView): This view allows to share an experiment with other users and/or teams """ + model = Experiment serializer_class = SharingSerializer - def post(self, request, author_name, toolchain_author_name, toolchain_name, version, name): - self.kwargs['object_name'] = toolchain_name + def post( + self, request, author_name, toolchain_author_name, toolchain_name, version, name + ): + self.kwargs["object_name"] = toolchain_name serializer = self.get_serializer(data=request.data) if not serializer.is_valid(): return BadRequestResponse(serializer.errors) data = serializer.data - users = data.get('users', None) - teams = data.get('teams', None) - algorithms_infos = request.data.get('algorithms_infos', None) + users = data.get("users", None) + teams = data.get("teams", None) + algorithms_infos = request.data.get("algorithms_infos", None) # Retrieve the experiment if toolchain_author_name is None: toolchain_author_name = author_name - experiment = get_object_or_404(self.model, - author__username=author_name, - toolchain__author__username=toolchain_author_name, - toolchain__name=toolchain_name, - toolchain__version=version, - name=name - ) - + experiment = get_object_or_404( + self.model, + author__username=author_name, + toolchain__author__username=toolchain_author_name, + toolchain__name=toolchain_name, + toolchain__version=version, + name=name, + ) # Check that the experiment is done if experiment.end_date is None: @@ -660,7 +744,9 @@ class ShareExperimentView(ShareView): # Share the experiment try: - experiment.share(users=users, teams=teams, algorithms_infos=algorithms_infos) + experiment.share( + users=users, teams=teams, algorithms_infos=algorithms_infos + ) except ShareError as e: return BadRequestResponse(e.errors)