Exemple #1
0
class DeepLinkSelector(APIView):
    @never_cache
    def get(self, request: Request, project_id, alias) -> Response:
        """Get a deep-links available to the client.

        No specific permissions are needed here, because this just rewrites a
        URL and the client can handle the potential permission error in a more
        user-friendly manner.
        ---
        serializer: DeepLinkSerializer
        """
        params = [f'pid={project_id}', f'link={alias}']
        url = f'{reverse("catmaid:home")}?{"&".join(params)}'
        return HttpResponseRedirect(url)

    @method_decorator(requires_user_role_for_any_project([UserRole.Browse]))
    @never_cache
    def head(self, request: Request, project_id, alias) -> Response:
        """Get a deep-links available to the client.
        ---
        serializer: DeepLinkSerializer
        """
        try:
            deep_link = DeepLink.objects.get(project_id=project_id,
                                             alias=alias)
            if not deep_link.is_public and request.user != deep_link.user:
                raise PermissionError('Can not find or access link')
            return Response()
        except DeepLink.DoesNotExist:
            return Response('Link not found', status=status.HTTP_404_NOT_FOUND)

    @method_decorator(requires_user_role_for_any_project([UserRole.Annotate]))
    @never_cache
    def delete(self, request: Request, project_id, alias) -> Response:
        """Delete a deep-links available to the client.
        ---
        serializer: DeepLinkSerializer
        """
        try:
            deep_link = DeepLink.objects.get(project_id=project_id,
                                             alias=alias)
            can_edit_or_fail(request.user, deep_link.id, 'catmaid_deep_link')
            deep_link_id = deep_link.id
            deep_link.delete()
            return Response({'deleted_id': deep_link_id})
        except DeepLink.DoesNotExist:
            return Response('Link not found', status=status.HTTP_404_NOT_FOUND)
Exemple #2
0
class ClientDatastoreList(APIView):
    @method_decorator(
        requires_user_role_for_any_project(
            [UserRole.Browse, UserRole.Annotate]))
    @never_cache
    def get(self, request: Request, format=None) -> Response:
        """List key-value store datastores used by the client.
        ---
        serializer: ClientDatastoreSerializer
        """
        datastores = ClientDatastore.objects.all()
        serializer = ClientDatastoreSerializer(datastores, many=True)
        return Response(serializer.data)

    @method_decorator(
        requires_user_role_for_any_project(
            [UserRole.Browse, UserRole.Annotate]))
    def post(self, request: Request, format=None) -> Response:
        """Create a key-value store datastore for the client.

        The request user must not be anonymous and must have browse, annotate
        or administer permissions for at least one project.
        ---
        parameters:
        - name: name
          description: |
            String key for the datastore. This will be used in URLs so may only
            contain alphanumeric characters and hyphens.
          required: true
          type: string
          paramType: form
        serializer: ClientDatastoreSerializer
        """
        if request.user == get_anonymous_user(
        ) or not request.user.is_authenticated:
            raise PermissionDenied('Unauthenticated or anonymous users ' \
                                   'can not create datastores.')
        name = request.POST['name']
        if not name:
            raise ValidationError('A name for the datastore must be provided.')

        datastore = ClientDatastore(name=name)
        datastore.full_clean()
        datastore.save()
        serializer = ClientDatastoreSerializer(datastore)
        return Response(serializer.data)
Exemple #3
0
class DeepLinkByIdSelector(APIView):
    @method_decorator(requires_user_role_for_any_project([UserRole.Annotate]))
    @never_cache
    def delete(self, request: Request, project_id, link_id) -> Response:
        """Delete a deep-links available to the client.
        ---
        serializer: DeepLinkSerializer
        """
        try:
            deep_link = DeepLink.objects.get(project_id=project_id, id=link_id)
            can_edit_or_fail(request.user, deep_link.id, 'catmaid_deep_link')
            deep_link.delete()
            return Response({'deleted_id': link_id})
        except DeepLink.DoesNotExist:
            return Response('Link not found', status=status.HTTP_404_NOT_FOUND)
Exemple #4
0
class DeepLinkDetails(APIView):
    @method_decorator(requires_user_role_for_any_project([UserRole.Browse]))
    @never_cache
    def get(self, request: Request, project_id, alias) -> Response:
        """Get details on a deep-link.
        ---
        serializer: DeepLinkSerializer
        """
        try:
            deep_link = DeepLink.objects.get(project_id=project_id,
                                             alias=alias)
            if not deep_link.is_public and request.user != deep_link.user:
                raise PermissionError('Can not find or access link')
            serializer = DeepLinkSerializer(deep_link)
            return Response(serializer.data)
        except DeepLink.DoesNotExist:
            return Response('Link not found', status=status.HTTP_404_NOT_FOUND)
Exemple #5
0
class ClientDataList(APIView):
    @method_decorator(
        requires_user_role_for_any_project(
            [UserRole.Browse, UserRole.Annotate]))
    @never_cache
    def get(self, request: Request, name=None, format=None) -> Response:
        """List key-value data in a datastore for the client.

        Returns key-values belong to the request user or no user, optionally
        filtering for those pairs belong to a specific project or no project.
        ---
        parameters:
        - name: name
          description: |
            String key for the **datastore** with which this key-value entry is
            associated.
          required: true
          type: string
          paramType: path
        - name: project_id
          description: |
            ID of a project to associate this data with, if any.
          required: false
          type: integer
          paramType: query
        serializer: ClientDataSerializer
        """
        datastore = get_object_or_404(ClientDatastore, name=name)
        data = ClientData.objects.filter(datastore=datastore,
                                         user_id=request.user.id) | \
               ClientData.objects.filter(datastore=datastore,
                                         user_id=None)

        project_id = request.GET.get('project_id', None)
        if project_id:
            project_id = int(project_id)
            project = get_object_or_404(Project, pk=project_id)
            if not check_user_role(request.user, project,
                                   [UserRole.Browse, UserRole.Annotate]):
                raise PermissionDenied('User lacks the appropriate ' \
                                       'permissions for this project.')

            data = data.filter(project_id=project_id) | data.filter(
                project_id=None)
        else:
            data = data.filter(project_id=None)

        serializer = ClientDataSerializer(data, many=True)
        return Response(serializer.data)

    @method_decorator(
        requires_user_role_for_any_project(
            [UserRole.Browse, UserRole.Annotate]))
    def put(self,
            request: Request,
            name: Optional[int] = None,
            format=None) -> Response:
        """Create or replace a key-value data entry for the client.

        Each entry is associated with a datastore, an optional project, an
        optional user, and a key. Creating a request that duplicates this
        quadruple will replace rather than create the value in the key-value
        pair.

        Entries associated with neither a project nor user are considered
        global; those associated with a project but no user are project-
        default; those associated with a user but no project are user-default;
        and those associated with both a project and a user are user-project
        specific. When listing key-value data, all four of these values, if
        existing, will be returned.
        ---
        parameters:
        - name: name
          description: |
            String key for the **datastore** with which this key-value entry is
            associated.
          required: true
          type: string
          paramType: path
        - name: project_id
          description: |
            ID of a project to associate this data with, if any.
          required: false
          type: integer
          paramType: form
        - name: ignore_user
          description: |
            Whether to associate this key-value entry with the instance rather
            than the request user. Only project administrators can do this
            for project-associated instance data, and only super users can do
            this for global data (instance data not associated with any
            project).
          required: false
          type: boolean
          default: false
          paramType: form
        - name: key
          description: A key for this entry.
          required: true
          type: string
          paramType: form
        - name: value
          description: A value for this entry. Must be valid JSON.
          required: true
          type: string
          paramType: form
        - name: format
          description: This function parameter is ignored
          required: false
          type: Any
          default: None
        response_serializer: ClientDataSerializer
        """
        if request.user == get_anonymous_user(
        ) or not request.user.is_authenticated:
            raise PermissionDenied('Unauthenticated or anonymous users ' \
                                   'can not create data.')
        datastore = get_object_or_404(ClientDatastore, name=name)

        key = request.data.get('key', None)
        if not key:
            raise ValidationError('A key for the data must be provided.')

        value = request.data.get('value', None)
        if not value:
            raise ValidationError('A value for the data must be provided.')
        # Validate JSON by reserializing.
        try:
            value = json.loads(value)
        except ValueError as exc:
            raise ValidationError('Data value is invalid JSON: ' + str(exc))

        project_id = request.data.get('project_id', None)
        project = None
        if project_id:
            project_id = int(project_id)
            project = get_object_or_404(Project, pk=project_id)
            if not check_user_role(request.user, project,
                                   [UserRole.Browse, UserRole.Annotate]):
                raise PermissionDenied('User lacks the appropriate ' \
                                       'permissions for this project.')

        ignore_user = get_request_bool(request.data, 'ignore_user', False)
        if ignore_user and not project_id:
            if not request.user.is_superuser:
                raise PermissionDenied('Only super users can create instance ' \
                                       'data.')
        if ignore_user:
            if not check_user_role(request.user, project, [UserRole.Admin]):
                raise PermissionDenied('Only administrators can create ' \
                                       'project default data.')
        user = None if ignore_user else request.user

        try:
            data = ClientData.objects.get(datastore=datastore,
                                          key=key,
                                          project=project,
                                          user=user)
            data.value = value
            data.full_clean()
            data.save()
            return Response(status=status.HTTP_204_NO_CONTENT)
        except ClientData.DoesNotExist:
            data = ClientData(datastore=datastore,
                              key=key,
                              value=value,
                              project=project,
                              user=user)
            data.full_clean()
            data.save()
            serializer = ClientDataSerializer(data)
            return Response(serializer.data)
Exemple #6
0
class DeepLinkList(APIView):
    @method_decorator(requires_user_role_for_any_project([UserRole.Browse]))
    @never_cache
    def get(self, request: Request, project_id) -> Response:
        """List deep-links available to the client.
        ---
        serializer: SimpleDeepLinkSerializer
        """
        only_own = get_request_bool(request.GET, 'only_own', False)
        only_private = get_request_bool(request.GET, 'only_private', False)

        filter_term = (Q(is_public=True)
                       | Q(user_id=request.user.id)) & Q(project_id=project_id)

        if only_own:
            filter_term = filter_term & Q(user_id=request.user.id)

        if only_private:
            filter_term = filter_term & Q(is_public=False)

        deep_links = DeepLink.objects.filter(filter_term)
        serializer = SimpleDeepLinkSerializer(deep_links, many=True)
        return Response(serializer.data)

    @method_decorator(requires_user_role_for_any_project([UserRole.Annotate]))
    def post(self, request: Request, project_id) -> Response:
        """Create a deep-link.

        The request user must not be anonymous and must have annotate
        permissions.
        ---
        serializer: DeepLinkSerializer
        """
        if request.user == get_anonymous_user(
        ) or not request.user.is_authenticated:
            raise PermissionError('Unauthenticated or anonymous users ' \
                                   'can not create persistent deep links.')

        project_id = int(project_id)

        alias = request.POST.get('alias')
        if alias:
            if not re.match(r'^[a-zA-Z0-9-_\.]+$', alias):
                raise ValueError(
                    "Only alphanumeric characters, '-', '_' and '.' allowed")
        else:
            n_links = DeepLink.objects.filter(project_id=project_id).count()
            alias = make_unique_id()

        params = {
            'project_id': project_id,
            'user': request.user,
            'alias': alias,
        }

        if 'is_public' in request.POST:
            params['is_public'] = get_request_bool(request.POST, 'is_public')

        if 'location_x' in request.POST:
            params['location_x'] = float(request.POST['location_x'])

        if 'location_y' in request.POST:
            params['location_y'] = float(request.POST['location_y'])

        if 'location_z' in request.POST:
            params['location_z'] = float(request.POST['location_z'])

        if 'active_treenode_id' in request.POST:
            params['active_treenode_id'] = int(
                request.POST['active_treenode_id'])

        if 'active_connector_id' in request.POST:
            params['active_connector_id'] = int(
                request.POST['active_connector_id'])

        if 'active_skeleton_id' in request.POST:
            params['active_skeleton_id'] = int(
                request.POST['active_skeleton_id'])

        if 'closest_node_to_location' in request.POST:
            params['closest_node_to_location'] = get_request_bool(
                request.POST, 'closest_node_to_location')

        if 'follow_id_history' in request.POST:
            params['follow_id_history'] = get_request_bool(
                request.POST, 'follow_id_history')

        if 'layered_stacks' in request.POST:
            params['layered_stacks'] = get_request_bool(
                request.POST, 'layered_stacks')

        if 'layout' in request.POST:
            params['layout'] = request.POST['layout']

        if 'tool' in request.POST:
            params['tool'] = request.POST['tool']

        if 'show_help' in request.POST:
            params['show_help'] = get_request_bool(request.POST, 'show_help')

        if 'password' in request.POST:
            params['password'] = make_password(request.POST('password'))

        if 'message' in request.POST:
            params['message'] = request.POST['message']

        # TBA: data_view

        deeplink = DeepLink(**params)
        deeplink.save()
        serializer = DeepLinkSerializer(deeplink)

        # Stacks
        stacks = get_request_list(request.POST, 'stacks', map_fn=float)
        if stacks:
            # Nested lists of 2-tuples: [[stack_id, scale_level]]
            for s in stacks:
                stack_link = DeepLinkStack(
                    **{
                        'project_id': project_id,
                        'user_id': request.user.id,
                        'deep_link': deeplink,
                        'stack_id': int(s[0]),
                        'zoom_level': s[1],
                    })
                stack_link.save()

        # Stack groups
        if 'stack_group' in request.POST:
            sg_id = int(request.POST['stack_group'])
            sg_zoom_levels = get_request_list(request.POST,
                                              'stack_group_scale_levels',
                                              map_fn=float)
            sg_link = DeepLinkStackGroup(
                **{
                    'project_id': project_id,
                    'user_id': request.user.id,
                    'deeplink': deeplink,
                    'stack_group_id': sg_id,
                    'zoom_levels': sg_zoom_levels,
                })
            sg_link.save()

        return Response(serializer.data)