Gitlab CSE Unil

views.py 12.5 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
25
26
27
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
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
# ================================
# RestFramework Permission Classes
# ================================

class AccessibleAnObj(BasePermission):
    """
    RestFramework Object-level permission to allow acces to objects related to an accessible AnObj
    Assumes the model instance has an `annotable` attribute.
    """
    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

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
87
class AnObjViewSet(viewsets.ModelViewSet):
88
89
90
    """
    Viewset for owned AnObj, requested by user who is the owner.
    """
Julien Furrer's avatar
Julien Furrer committed
91
92
    model = AnObj
    serializer_class = AnObjSerializer
93
    # lookup_field = 'uuid'
Julien Furrer's avatar
Julien Furrer committed
94

95
96
97
98
99
100
101
102
103
    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
104
105

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

116
        return AnObj.objects.select_related('envparam').filter(q).distinct()
117

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

122
        if request.method != 'GET':
123
            user_model = get_user_model()
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
            members = []
            for member_data in request.DATA.get('members', []):
                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)
152
153
154

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

155
156
157
158
159
160
161
162
163
164
165
166
    @detail_route(methods=['patch'])
    def set_publish_mode(self, request, pk=None):
        user = request.user
        anobj = AnObj.objects.get(pk=pk)
        new_publish_mode = int(request.DATA.get('publish_mode', 0))
        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:
167
168
169
            # if user == anobj.owner:
            # if user in anobj.owners.all():
            if anobj.is_owned(user.id):
170
171
172
173
174
                membership = AnObjMembership.objects.create(anobj=anobj, user=user, publish_mode=new_publish_mode)
            else:
                raise Http404()

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

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

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

185
186
187
188
    @detail_route(methods=['patch'])
    def env_param(self, request, pk=None):
        pass

189

190
191
192
193
194
195
196
class UAnObjViewSet(AnObjViewSet):
    """
    Accessing AnObj by uuid. extents AnObj
    """
    lookup_field = 'uuid'


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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
222
223
        # q = Q(owner=user)
        q = Q(owners=user)
224
        if self.action in ('list', 'retrieve', 'members'):
225
226
            q = q | Q(members=user)

227
        return AnObj.objects.filter(q).distinct()
228

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    @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})
    #
    # @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})
    #
258

259

260
261
262
263
264
265
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>
    """
266
    model = get_user_model()
267
268
269
270
271
272
273
274
275
276
277
278
    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
279
280
281
282
283
284
285
286


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

    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

296
    def create(self, request, *args, **kwargs):
Julien Furrer's avatar
Julien Furrer committed
297
        """
298
299
300
301
        Override create to set annotation's owner to request.owner
        :param request:
        :param args:
        :param kwargs:
Julien Furrer's avatar
Julien Furrer committed
302
303
        :return:
        """
304
305
306
        request.data['owner'] = request.user.id
        request.data['owner_id'] = request.user.id
        return super(AnnotationViewSet, self).create(request, *args, **kwargs)
Julien Furrer's avatar
Julien Furrer committed
307

308
309
310
311
312
313
314
315
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
    # 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)
341
342


343
class SharedAnnotationViewSet(viewsets.ReadOnlyModelViewSet):
344
345
346
    """

    """
347
348
    model = Annotation
    serializer_class = AnnotationSerializer
349

350
351
    def get_queryset(self):
        """
352
353
        Return the shared annotations for this anobj

354
355
        anots = anobj.annotations.filter(owner__anobjmembership__publish_mode__gte=<publish_level>, owner__anobjmembership__anobj=anobj).select_related('owner')
        """
356
        anobj = get_object_or_404(AnObj, pk=self.kwargs.get('anobjs_pk', -1))
357
358
        # 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
359
360
361
362
363
364
365

        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)