Gitlab CSE Unil

views.py 13 KB
Newer Older
Julien Furrer's avatar
Julien Furrer committed
1
2
# coding=utf-8
from __future__ import unicode_literals
3
from django.contrib.auth import get_user_model
4
from django.core.exceptions import PermissionDenied
Julien Furrer's avatar
Julien Furrer committed
5
from django.http.response import Http404
6
from django.db.models import Q
7
from django.shortcuts import get_object_or_404
Julien Furrer's avatar
Julien Furrer committed
8

9
from adim.models import AnObj, AnObjMembership, Annotation
10
11
12
from adim.serializers import AnObjSerializer, SharedAnObjSerializer, AnObjListSerializer, \
    AnnotationSerializer, UserSerializer
from adim.permissions import has_anobj_access
Julien Furrer's avatar
Julien Furrer committed
13
from rest_framework import viewsets
14
from rest_framework.response import Response
Julien Furrer's avatar
Julien Furrer committed
15
from rest_framework.decorators import detail_route
16
from rest_framework.permissions import BasePermission, SAFE_METHODS
Julien Furrer's avatar
Julien Furrer committed
17
18


19
20
21
22
23
24
# ================================
# RestFramework Permission Classes
# ================================

class AccessibleAnObj(BasePermission):
    """
25
    RestFramework Object-level permission to allow access to objects related to an accessible AnObj
26
27
    Assumes the model instance has an `annotable` attribute.
    """
28

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    def has_object_permission(self, request, view, obj):
        try:
            return has_anobj_access(request, obj.annotable)
        except AnObj.DoesNotExist:
            return False
        except AttributeError:
            return False


class IsOwnerOrReadOnly(BasePermission):
    """
    RestFramework Object-level permission to only allow owners of an object to edit it.
    Assumes the model instance has an `owner` or an `owners` attribute.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in SAFE_METHODS:
            return True

        # Instance must have an attribute named `owner` or `owners`
        if hasattr(obj, 'owner'):
            return obj.owner == request.user
        elif hasattr(obj, 'owners'):
            return obj.owners.filter(id=request.user.id).exists()
        else:
            return False

58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class WritableAnObjOrReadonly(BasePermission):
    """
    RestFramework Object-level permission to only allow modification on objects
    related to an unlocked AnObj
    Assumes the model instance has an `annotable` attribute.
    """

    def has_permission(self, request, view):
        # For CREATE action, there is no annotation yet, so we have to fetch AnObj first to check for locked state
        if view.action == 'create':
            try:
                annotable = AnObj.objects.get(pk=request.data.get('annotable', -1))
                return not annotable.locked
            except AnObj.DoesNotExist:
                return False
        return True

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in SAFE_METHODS:
            return True

        return not obj.annotable.locked


# ======================
# RestFramework ViewSets
# ======================

Julien Furrer's avatar
Julien Furrer committed
89
class AnObjViewSet(viewsets.ModelViewSet):
90
91
92
    """
    Viewset for owned AnObj, requested by user who is the owner.
    """
Julien Furrer's avatar
Julien Furrer committed
93
94
    model = AnObj
    serializer_class = AnObjSerializer
95

96
    # lookup_field = 'uuid'
Julien Furrer's avatar
Julien Furrer committed
97

98
99
100
101
102
103
104
105
106
    def get_serializer_class(self):
        """
        Use a simplified serializer for listing and
        the complete one for other actions
        """
        if self.action == 'list':
            return AnObjListSerializer
        else:
            return self.serializer_class
Julien Furrer's avatar
Julien Furrer committed
107
108

    def get_queryset(self):
109
110
111
112
113
        """
        Return only owner's anobjs for write actions and
        return also shared anobjs to guests for read actions
        """
        user = self.request.user
114
115
        # q = Q(owner=user)
        q = Q(owners=user)
116
117
118
        if self.action == 'list':
            q = q | Q(members=user)

119
        return AnObj.objects.select_related('envparam').filter(q).distinct()
120

121
    @detail_route(methods=['get', 'patch', 'post', 'delete'])
122
123
124
    def members(self, request, pk=None):
        anobj = self.get_object()

125
        if request.method != 'GET':
126
            user_model = get_user_model()
127
            members = []
128
            for member_data in request.data.get('members', []):
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
                try:
                    q = Q(pk=-1)
                    if member_data.get('id'):
                        q = Q(pk=member_data.get('id'))
                    elif member_data.get('username'):
                        q = Q(username=member_data.get('username'))
                    members.append(user_model.objects.get(q))

                except user_model.DoesNotExist:
                    if member_data.get('username'):
                        opts_members = anobj.sharing_opts.get('members', [])

                        if request.method in ('PATCH', 'POST'):
                            opts_members.append(member_data.get('username'))
                        elif request.method == 'DELETE':
                            opts_members.remove(member_data.get('username'))

                        anobj.sharing_opts['members'] = opts_members
                        anobj.save()

            if request.method in ('PATCH', 'POST'):
                # anobj.members.add(*members)
                anobj.add_members(*members)
            elif request.method == 'DELETE':
                # anobj.members.remove(*members)
                anobj.remove_members(*members)
155
156
157

        return Response({'users': UserSerializer(instance=anobj.members.all(), many=True).data})

158
159
160
161
    @detail_route(methods=['patch'])
    def set_publish_mode(self, request, pk=None):
        user = request.user
        anobj = AnObj.objects.get(pk=pk)
162
        new_publish_mode = int(request.data.get('publish_mode', 0))
163
164
165
166
167
168
169
        if new_publish_mode not in xrange(3):
            raise ValueError("Invalid publish mode")

        # membership = get_object_or_404(AnObjMembership, anobj__id=pk, user=user)
        try:
            membership = AnObjMembership.objects.get(anobj=anobj, user=user)
        except AnObjMembership.DoesNotExist:
170
171
172
            # if user == anobj.owner:
            # if user in anobj.owners.all():
            if anobj.is_owned(user.id):
173
174
175
176
177
                membership = AnObjMembership.objects.create(anobj=anobj, user=user, publish_mode=new_publish_mode)
            else:
                raise Http404()

        if new_publish_mode == 2 and not (
178
179
                    anobj.allow_public_publishing or anobj.is_owned(user.id)):
            # anobj.allow_public_publishing or user in anobj.owners.all()):
180
181
182
183
184
185
186
187
            raise PermissionDenied

        if membership.publish_mode != new_publish_mode:
            membership.publish_mode = new_publish_mode
            membership.save()

        return Response({'publish_mode': membership.publish_mode})

188
189
190
191
    @detail_route(methods=['patch'])
    def env_param(self, request, pk=None):
        pass

192

193
194
195
196
197
198
199
class UAnObjViewSet(AnObjViewSet):
    """
    Accessing AnObj by uuid. extents AnObj
    """
    lookup_field = 'uuid'


200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
class SharedAnObjViewSet(AnObjViewSet):
    """
    ViewSet for shared AnObj, requested by a user
    who is not the owner but a member.
    """
    serializer_class = SharedAnObjSerializer

    def get_serializer_class(self):
        """
        Use a simplified serializer for listing and
        the complete one for other actions
        """
        if self.action == 'list':
            return AnObjListSerializer
        else:
            return self.serializer_class

    def get_queryset(self):
        """
        Return only owner's anobjs for write actions and
        return also shared anobjs to guests for read actions
        """
        # return AnObj.objects.filter(members=self.request.user)

        user = self.request.user
225
226
        # q = Q(owner=user)
        q = Q(owners=user)
227
        if self.action in ('list', 'retrieve', 'members'):
228
229
            q = q | Q(members=user)

230
        return AnObj.objects.filter(q).distinct()
231

232
233
234
235
236
237
238
239
    @detail_route(methods=['get'])
    def members(self, request, pk=None):
        anobj = self.get_object()
        if anobj.allow_public_publishing:
            users = UserSerializer(instance=anobj.members.all(), many=True).data
        else:
            users = []
        return Response({'users': users})
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
        #
        # @detail_route(methods=['patch'])
        # def set_publish_mode(self, request, pk=None):
        #     user = request.user
        #     anobj = AnObj.objects.get(pk=pk)
        #     membership = get_object_or_404(AnObjMembership, anobj__id=pk, user=user)
        #
        #     new_publish_mode = int(request.DATA.get('publish_mode', 0))
        #     if new_publish_mode not in xrange(3):
        #         raise ValueError("Invalid publish mode")
        #
        #     if new_publish_mode == 2 and not (
        #             anobj.allow_public_publishing or user == anobj.owner):
        #         raise PermissionDenied
        #
        #     if membership.publish_mode != new_publish_mode:
        #         membership.publish_mode = new_publish_mode
        #         membership.save()
        #
        #     return Response({'publish_mode': membership.publish_mode})
        #
261

262

263
264
265
266
267
268
class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    List of registered users for an AnObj.
    The anobj uuid is given as a querystring variable
    ?uuid=<the-uuid-value-for-the-anobj>
    """
269
    model = get_user_model()
270
271
272
273
274
275
276
277
278
279
280
281
    serializer_class = UserSerializer

    def get_queryset(self):
        uuid = self.request.GET.get('uuid', "")
        q = Q(anobjs__uuid=uuid) | Q(shared_anobjs__uuid=uuid)
        return self.model.objects.filter(q)
        # try:
        #
        #     anobj = AnObj.objects.get(uuid=self.request.GET.get('uuid', ""))
        #     return self.model.objects.filter(pk=anobj.owner_id)
        # except AnObj.DoesNotExist:
        #     return []
Julien Furrer's avatar
Julien Furrer committed
282
283
284
285
286
287
288
289


class AnnotationViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allow annotations to be viewed or edited
    """
    model = Annotation
    serializer_class = AnnotationSerializer
290
    permission_classes = (AccessibleAnObj, IsOwnerOrReadOnly, WritableAnObjOrReadonly)
Julien Furrer's avatar
Julien Furrer committed
291
292
293
294
295
296
297
298

    def get_queryset(self):
        if self.request.user.has_perm('adim.can_review_exercise'):
            queryset = Annotation.objects.all()
        else:
            queryset = Annotation.objects.filter(owner=self.request.user)
        return queryset

299
    def create(self, request, *args, **kwargs):
Julien Furrer's avatar
Julien Furrer committed
300
        """
301
302
303
304
        Override create to set annotation's owner to request.owner
        :param request:
        :param args:
        :param kwargs:
Julien Furrer's avatar
Julien Furrer committed
305
306
        :return:
        """
307
308
309
310
311
312
313
314
        owner_str_id = str(request.user.id)
        if (
            owner_str_id not in request.data['owner'] or
            owner_str_id not in request.data['owner_id']
        ):
            raise AttributeError("Wrong owner for annotation")
        # request.data['owner'] = request.user.id
        # request.data['owner_id'] = request.user.id
315
        return super(AnnotationViewSet, self).create(request, *args, **kwargs)
Julien Furrer's avatar
Julien Furrer committed
316

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
        # def list(self, request):
        #     return Response([])
        #
        # def pre_save(self, obj):
        #     """
        #     For new annotation, check annotable access permission
        #     and set the owner of the annotation
        #     :param obj:
        #     :return:
        #     """
        #     if obj.annotable.locked:
        #         raise Exception("Annotable locked")
        #
        #     # ok
        #     if obj.id:
        #         if obj.owner != self.request.user:
        #             raise Exception("Annotable access forbidden")
        #
        #     else:
        #         # user = self.request.user
        #         # anobj_q = Q(pk=obj.annotable_id) & (Q(owner=user) | Q(members=user))
        #         try:
        #             anobj = AnObj.objects.get(pk=obj.annotable_id)
        #             if not has_anobj_access(self.request, anobj):
        #                 raise Exception("Annotable access forbidden")
        #
        #         except AnObj.DoesNotExist:
        #             raise Exception("Annotable access forbidden")
        #         # if not AnObj.objects.filter(anobj_q).exists():
        #
        #         obj.owner = self.request.user
        #
        #     super(AnnotationViewSet, self).pre_save(obj)
350
351


352
class SharedAnnotationViewSet(viewsets.ReadOnlyModelViewSet):
353
354
355
    """

    """
356
357
    model = Annotation
    serializer_class = AnnotationSerializer
358

359
360
    def get_queryset(self):
        """
361
362
        Return the shared annotations for this anobj

363
364
        anots = anobj.annotations.filter(owner__anobjmembership__publish_mode__gte=<publish_level>, owner__anobjmembership__anobj=anobj).select_related('owner')
        """
365
        anobj = get_object_or_404(AnObj, pk=self.kwargs.get('anobjs_pk', -1))
366
367
        # publish_level = 1 if self.request.user in anobj.owners.all() else 2
        publish_level = 1 if anobj.is_owned(self.request.user.id) else 2
368
369
370
371
372
373
374

        q = (
            Q(annotable=anobj) &
            Q(owner__anobjmembership__publish_mode__gte=publish_level) &
            Q(owner__anobjmembership__anobj=anobj)
        )
        return Annotation.objects.filter(q).exclude(owner=self.request.user)