api.py 10.7 KB
Newer Older
André Anjos's avatar
André Anjos committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#!/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.shortcuts import get_object_or_404
29
from rest_framework import exceptions as drf_exceptions
André Anjos's avatar
André Anjos committed
30
from rest_framework import generics
31
from rest_framework import permissions as drf_permissions
32
from rest_framework import status
André Anjos's avatar
André Anjos committed
33 34 35 36
from rest_framework.response import Response
from rest_framework.reverse import reverse

from . import is_true
37 38 39 40 41 42 43 44 45 46 47 48
from . import permissions as beat_permissions
from .exceptions import BaseCreationError
from .exceptions import ShareError
from .mixins import CommonContextMixin
from .mixins import SerializerFieldsMixin
from .models import Contribution
from .models import Versionable
from .serializers import CheckNameSerializer
from .serializers import ContributionSerializer
from .serializers import DiffSerializer
from .serializers import SharingSerializer
from .utils import py3_cmp
André Anjos's avatar
André Anjos committed
49 50 51 52


class CheckContributionNameView(CommonContextMixin, generics.CreateAPIView):
    serializer_class = CheckNameSerializer
53
    permission_classes = [drf_permissions.IsAuthenticated]
André Anjos's avatar
André Anjos committed
54 55 56

    def get_serializer_context(self):
        context = super(CheckContributionNameView, self).get_serializer_context()
57
        context["model"] = self.model
André Anjos's avatar
André Anjos committed
58 59 60 61 62 63 64 65 66
        return context

    def post(self, request):
        response = super(CheckContributionNameView, self).post(request)
        response.status_code = status.HTTP_200_OK
        return response


class ShareView(CommonContextMixin, generics.CreateAPIView):
67
    permission_classes = [beat_permissions.IsAuthor]
André Anjos's avatar
André Anjos committed
68 69 70
    serializer_class = SharingSerializer

    def get_queryset(self):
71 72 73 74 75 76 77 78 79
        author_name = self.kwargs.get("author_name")
        object_name = self.kwargs.get("object_name")
        version = self.kwargs.get("version")
        return get_object_or_404(
            self.model,
            author__username__iexact=author_name,
            name__iexact=object_name,
            version=version,
        )
André Anjos's avatar
André Anjos committed
80 81

    def do_share(self, obj, data):
82 83
        users = data.get("users", None)
        teams = data.get("teams", None)
André Anjos's avatar
André Anjos committed
84 85 86 87
        obj.share(users=users, teams=teams)

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
88
        serializer.is_valid(raise_exception=True)
André Anjos's avatar
André Anjos committed
89 90 91 92 93 94 95
        data = serializer.data

        object_db = self.get_queryset()

        try:
            self.do_share(object_db, data)
        except ShareError as e:
96 97 98
            # ShareError happens when someone does not have access to
            #  all dependencies that should be shared.
            raise drf_exceptions.PermissionDenied(e.errors)
André Anjos's avatar
André Anjos committed
99 100 101 102

        return Response(object_db.sharing_preferences())


103 104 105
class ListContributionView(
    CommonContextMixin, SerializerFieldsMixin, generics.ListAPIView
):
André Anjos's avatar
André Anjos committed
106 107
    model = Contribution
    serializer_class = ContributionSerializer
108
    permission_classes = [drf_permissions.AllowAny]
André Anjos's avatar
André Anjos committed
109 110 111 112 113 114

    def get_queryset(self):
        return self.model.objects.for_user(self.request.user, True)

    def get(self, request, *args, **kwargs):
        fields_to_return = self.get_serializer_fields(request)
115 116 117
        limit_to_latest_versions = is_true(
            request.query_params.get("latest_versions", False)
        )
André Anjos's avatar
André Anjos committed
118 119

        all_contributions = self.get_queryset().select_related()
120 121 122 123
        if hasattr(self.model, "author"):
            all_contributions = all_contributions.order_by(
                "author__username", "name", "-version"
            )
André Anjos's avatar
André Anjos committed
124
        else:
125
            all_contributions = all_contributions.order_by("name", "-version")
André Anjos's avatar
André Anjos committed
126 127 128 129

        if limit_to_latest_versions:
            all_contributions = self.model.filter_latest_versions(all_contributions)
            # Sort the data formats and sends the response
130
            all_contributions.sort(lambda x, y: py3_cmp(x.fullname(), y.fullname()))
André Anjos's avatar
André Anjos committed
131

132 133 134
        serializer = self.get_serializer(
            all_contributions, many=True, fields=fields_to_return
        )
André Anjos's avatar
André Anjos committed
135 136 137
        return Response(serializer.data)


138 139 140
class ListCreateBaseView(
    CommonContextMixin, SerializerFieldsMixin, generics.ListCreateAPIView
):
André Anjos's avatar
André Anjos committed
141
    def get_serializer(self, *args, **kwargs):
142
        if self.request.method == "POST":
André Anjos's avatar
André Anjos committed
143 144 145 146 147
            self.serializer_class = self.writing_serializer_class
        return super(ListCreateBaseView, self).get_serializer(*args, **kwargs)

    def get(self, request, *args, **kwargs):
        fields_to_return = self.get_serializer_fields(request)
148

149 150 151
        limit_to_latest_versions = is_true(
            request.query_params.get("latest_versions", False)
        )
André Anjos's avatar
André Anjos committed
152 153 154 155 156 157 158 159 160 161 162

        objects = self.get_queryset().select_related()

        if limit_to_latest_versions:
            objects = self.model.filter_latest_versions(objects)

        serializer = self.get_serializer(objects, many=True, fields=fields_to_return)
        return Response(serializer.data)

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
163 164 165 166 167 168 169 170
        serializer.is_valid(raise_exception=True)
        try:
            if hasattr(self.model, "author"):
                db_object = serializer.save(author=request.user)
            else:
                db_object = serializer.save()
        except BaseCreationError as e:
            raise drf_exceptions.APIException(e.errors)
André Anjos's avatar
André Anjos committed
171 172

        result = {
173 174 175 176 177 178
            "name": db_object.name,
            "full_name": db_object.fullname(),
            "url": reverse("{}:all".format(self.namespace))
            + db_object.fullname()
            + "/",
            "object_view": reverse(
179 180
                "{}:view".format(self.namespace.split("_")[1]),
                args=db_object.fullname().split("/"),
181
            ),
André Anjos's avatar
André Anjos committed
182 183 184
        }

        response = Response(result, status=201)
185
        response["Location"] = result["url"]
André Anjos's avatar
André Anjos committed
186 187 188
        return response


189 190 191
class ListCreateContributionView(ListCreateBaseView):
    permission_classes = [beat_permissions.IsAuthorOrReadOnly]

André Anjos's avatar
André Anjos committed
192 193
    def get_queryset(self):
        user = self.request.user
194
        author_name = self.kwargs.get("author_name")
André Anjos's avatar
André Anjos committed
195 196 197 198 199 200 201 202 203
        return self.model.objects.from_author_and_public(user, author_name)


class DiffView(generics.RetrieveAPIView):
    model = Versionable
    serializer_class = DiffSerializer

    def get(self, request, author1, name1, version1, author2, name2, version2):
        # Retrieve the objects
204 205 206 207 208 209
        object1 = get_object_or_404(
            self.model,
            author__username__iexact=author1,
            name__iexact=name1,
            version=int(version1),
        )
André Anjos's avatar
André Anjos committed
210

211 212 213 214 215 216
        object2 = get_object_or_404(
            self.model,
            author__username__iexact=author2,
            name__iexact=name2,
            version=int(version2),
        )
André Anjos's avatar
André Anjos committed
217 218 219 220

        # Check that the user can access them
        accessibility = object1.accessibility_for(request.user)
        if not accessibility[0]:
221
            raise drf_exceptions.PermissionDenied(object1.fullname())
André Anjos's avatar
André Anjos committed
222 223 224

        accessibility = object2.accessibility_for(request.user)
        if not accessibility[0]:
225
            raise drf_exceptions.PermissionDenied(object2.fullname())
André Anjos's avatar
André Anjos committed
226 227

        # Compute the diff
228
        serializer = self.get_serializer({"object1": object1, "object2": object2})
André Anjos's avatar
André Anjos committed
229 230 231
        return Response(serializer.data)


232
class RetrieveUpdateDestroyContributionView(
233
    CommonContextMixin, SerializerFieldsMixin, generics.RetrieveUpdateDestroyAPIView
234
):
André Anjos's avatar
André Anjos committed
235
    model = Contribution
236 237 238 239
    permission_classes = [
        beat_permissions.IsAuthorOrReadOnly,
        beat_permissions.IsModifiableOrRead,
    ]
André Anjos's avatar
André Anjos committed
240

241 242 243 244 245
    def get_serializer(self, *args, **kwargs):
        if self.request.method == "PUT":
            self.serializer_class = self.writing_serializer_class
        return super().get_serializer(*args, **kwargs)

246
    def get_object(self):
247 248 249 250 251 252 253
        kwargs = dict(
            version=self.kwargs["version"], name__iexact=self.kwargs["object_name"]
        )

        if hasattr(self.model, "author"):
            kwargs["author__username__iexact"] = self.kwargs["author_name"]

André Anjos's avatar
André Anjos committed
254
        user = self.request.user
255
        try:
256
            obj = self.model.objects.for_user(user, True).get(**kwargs)
257
        except self.model.DoesNotExist:
258
            raise drf_exceptions.NotFound()
259
        return obj
André Anjos's avatar
André Anjos committed
260

261 262
    def get(self, request, *args, **kwargs):
        db_object = self.get_object()
263
        self.check_object_permissions(request, db_object)
André Anjos's avatar
André Anjos committed
264 265

        # Process the query string
266 267 268 269
        allow_sharing = False

        if hasattr(db_object, "author"):
            allow_sharing = request.user == db_object.author
André Anjos's avatar
André Anjos committed
270

271 272 273
        fields_to_return = self.get_serializer_fields(
            request, allow_sharing=allow_sharing
        )
André Anjos's avatar
André Anjos committed
274 275 276 277

        serializer = self.get_serializer(db_object, fields=fields_to_return)
        return Response(serializer.data)

278
    def perform_destroy(self, instance):
André Anjos's avatar
André Anjos committed
279
        # Check that the object can be deleted
280
        if not (instance.deletable()):
281
            raise drf_exceptions.MethodNotAllowed(
282
                "The {} can't be deleted anymore (needed by an attestation, an algorithm or another data format)".format(
283
                    instance.model_name()
284 285
                )
            )
286
        return super().perform_destroy(instance)