Source code for beat.web.code.api

#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

###############################################################################
#                                                                             #
# 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/.           #
#                                                                             #
###############################################################################

from django.utils import six
from django.shortcuts import get_object_or_404
from django.core.exceptions import ValidationError

from rest_framework import generics
from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.exceptions import PermissionDenied, ParseError
from rest_framework import serializers

from ..common.responses import ForbiddenResponse
from ..common.api import ShareView, RetrieveUpdateDestroyContributionView
from ..common.utils import validate_restructuredtext, ensure_html
from ..common.serializers import DiffSerializer

from ..code.models import Code
from .serializers import CodeSharingSerializer, CodeSerializer

import simplejson as json

[docs]class ShareCodeView(ShareView): serializer_class = CodeSharingSerializer
[docs] def do_share(self, obj, data): users = data.get('users', None) teams = data.get('teams', None) public = data.get('status') == 'public' obj.share(public=public, users=users, teams=teams)
[docs]class DiffView(generics.RetrieveAPIView): model = Code serializer_class = DiffSerializer
[docs] def get(self, request, author1, name1, version1, author2, name2, version2): # Retrieve the objects try: object1 = self.model.objects.get(author__username__iexact=author1, name__iexact=name1, version=int(version1)) except: return Response('%s/%s/%s' % (author1, name1, version1), status=404) try: object2 = self.model.objects.get(author__username__iexact=author2, name__iexact=name2, version=int(version2)) except: return Response('%s/%s/%s' % (author2, name2, version2), status=404) # Check that the user can access them has_access, open_source, _ = object1.accessibility_for(request.user) if not ((request.user == object1.author) or \ (has_access and open_source)): return ForbiddenResponse("You cannot access the source-code of \"%s\"" % object1.fullname()) has_access, open_source, _ = object2.accessibility_for(request.user) if not ((request.user == object2.author) or \ (has_access and open_source)): return ForbiddenResponse("You cannot access the source-code of \"%s\"" % object2.fullname()) # Compute the diff serializer = self.get_serializer({'object1': object1, 'object2': object2}) return Response(serializer.data)
[docs]class RetrieveUpdateDestroyCodeView(RetrieveUpdateDestroyContributionView): model = Code serializer_class = CodeSerializer
[docs] def do_update(self, request, author_name, object_name, version=None): if version is None: raise ValidationError({'version': 'A version number must be provided'}) try: data = request.data except ParseError as e: raise serializers.ValidationError({'data': str(e)}) else: if not data: raise serializers.ValidationError({'data': 'Empty'}) if 'short_description' in data: if not(isinstance(data['short_description'], six.string_types)): raise ValidationError({'short_description': 'Invalid short_description data'}) 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'] try: validate_restructuredtext(description) except ValidationError as errors: raise serializers.ValidationError({'description': [error for error in errors]}) else: description = None if 'declaration' in data: if isinstance(data['declaration'], dict): json_declaration = data['declaration'] declaration = json.dumps(json_declaration, indent=4) elif isinstance(data['declaration'], six.string_types): declaration = data['declaration'] try: json_declaration = json.loads(declaration) except: raise serializers.ValidationError({'declaration': 'Invalid declaration data'}) else: raise serializers.ValidationError({'declaration': 'Invalid declaration data'}) if 'description' in json_declaration: if short_description is not None: raise serializers.ValidationError({'short_description': 'A short description is already provided in the declaration'}) short_description = json_declaration['description'] elif short_description is not None: json_declaration['description'] = short_description declaration = json.dumps(json_declaration, indent=4) else: declaration = None json_declaration = None if (short_description is not None) and (len(short_description) > self.model._meta.get_field('short_description').max_length): raise ValidationError({'short_description': 'Short description too long'}) if 'code' in data: if not(isinstance(data['code'], six.string_types)): raise ValidationError({'code': 'Invalid code data'}) code = data['code'] else: code = None # Retrieve the object db_object = get_object_or_404(self.model, author__username__iexact=author_name, name__iexact=object_name, version=version) # Check that the object can still be modified (if applicable, the # documentation can always be modified) if ((declaration is not None) or (code is not None)) and \ not(db_object.modifiable()): raise PermissionDenied("The {} isn't modifiable anymore (either shared with someone else, or needed by an attestation)".format(db_object.model_name())) # Modification of the documentation if (short_description is not None) and (declaration is None): tmp_declaration = db_object.declaration tmp_declaration['description'] = short_description db_object.declaration = tmp_declaration if description is not None: db_object.description = description # Modification of the declaration modified = False if declaration is not None: db_object.declaration = declaration modified = True # Modification of the source code if code is not None: db_object.source_code = code modified = True db_object.save() return modified, db_object
[docs] def get(self, request, *args, **kwargs): db_objects = self.get_queryset() if db_objects.count() == 0: return Response(status=404) db_object = db_objects[0] version = int(self.kwargs.get('version', -1)) if version != -1 and db_object.version != version: return Response(status=404) # Check that the user can access it (has_access, open_source, accessibility) = db_object.accessibility_for(request.user) # Process the query string # Other available fields (not returned by default): # - html_description # - history # - referenced_dataformats # - needed_dataformats # - attestations fields_to_remove = [] if ((request.user != db_object.author) and not(open_source)) or db_object.is_binary(): fields_to_remove = ['code'] fields_to_return = self.get_serializer_fields(request, allow_sharing=(request.user == db_object.author), exclude_fields=fields_to_remove) serializer = self.get_serializer(db_object, fields=fields_to_return) return Response(serializer.data)
[docs] def put(self, request, author_name, object_name, version=None): (modified, db_object) = self.do_update(request, author_name, object_name, version) # Available fields (not returned by default): # - html_description if 'fields' in request.GET: fields_to_return = request.GET['fields'].split(',') else: return Response(status=204) result = {} # Retrieve the description in HTML format if 'html_description' in fields_to_return: description = db_object.description if len(description) > 0: result['html_description'] = ensure_html(description) else: result['html_description'] = '' return Response(result)