blob: c523943bb3a1a6f2ddf7dc94746f09295881c5fb [file] [log] [blame]
Scott Baker8974e552015-02-10 19:26:00 -08001from rest_framework.response import Response
2from rest_framework import serializers
3from rest_framework import generics
4from rest_framework import status
Scott Baker9fd6c1c2015-02-16 23:34:31 -08005from rest_framework.exceptions import APIException
Scott Baker8974e552015-02-10 19:26:00 -08006from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
7from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
8
Scott Baker9fd6c1c2015-02-16 23:34:31 -08009class XOSProgrammingError(APIException):
10 status_code=400
11 def __init__(self, why="programming error", fields={}):
12 APIException.__init__(self, {"error": "XOSProgrammingError",
13 "specific_error": why,
14 "fields": fields})
15
16class XOSPermissionDenied(RestFrameworkPermissionDenied):
17 def __init__(self, why="permission error", fields={}):
18 APIException.__init__(self, {"error": "XOSPermissionDenied",
19 "specific_error": why,
20 "fields": fields})
21
22class XOSNotAuthenticated(RestFrameworkPermissionDenied):
23 def __init__(self, why="you must be authenticated to use this api", fields={}):
24 APIException.__init__(self, {"error": "XOSNotAuthenticated",
25 "specific_error": why,
26 "fields": fields})
27
28class XOSValidationError(APIException):
29 status_code=403
30 def __init__(self, why="validation error", fields={}):
31 APIException.__init__(self, {"error": "XOSValidationError",
32 "specific_error": why,
33 "fields": fields})
34
Scott Baker8974e552015-02-10 19:26:00 -080035class XOSRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
36
37 # To handle fine-grained field permissions, we have to check can_update
38 # the object has been updated but before it has been saved.
39
40 def update(self, request, *args, **kwargs):
41 partial = kwargs.pop('partial', False)
42 self.object = self.get_object_or_none()
43
44 if self.object is None:
Scott Baker9fd6c1c2015-02-16 23:34:31 -080045 raise XOSProgrammingError("Use the List API for creating objects")
Scott Baker8974e552015-02-10 19:26:00 -080046
47 serializer = self.get_serializer(self.object, data=request.DATA,
48 files=request.FILES, partial=partial)
49
50 assert(serializer.object is not None)
51
52 serializer.object.caller = request.user
53
54 if not serializer.is_valid():
Scott Baker9fd6c1c2015-02-16 23:34:31 -080055 raise XOSValidationError(fields=serializer._errors)
Scott Baker8974e552015-02-10 19:26:00 -080056
57 if not serializer.object.can_update(request.user):
Scott Baker9fd6c1c2015-02-16 23:34:31 -080058 raise XOSPermissionDenied()
Scott Baker8974e552015-02-10 19:26:00 -080059
Scott Baker9fd6c1c2015-02-16 23:34:31 -080060 if (hasattr(self, "pre_save")):
61 # rest_framework 2.x
62 self.pre_save(serializer.object)
63 self.object = serializer.save(force_update=True)
64 self.post_save(self.object, created=False)
65 else:
66 # rest_framework 3.x
67 self.perform_update(serializer)
68
69 return Response(serializer.data, status=status.HTTP_200_OK)
Scott Baker8974e552015-02-10 19:26:00 -080070
71 def destroy(self, request, *args, **kwargs):
72 obj = self.get_object()
Scott Baker3d85e0c2015-02-10 20:01:46 -080073 obj.caller = request.user
Scott Baker8974e552015-02-10 19:26:00 -080074 if obj.can_update(request.user):
Scott Baker3d85e0c2015-02-10 20:01:46 -080075 # this is the guts of DestroyModelMixin, copied here so that we
76 # can use the obj with caller set in it,
77 self.pre_delete(obj)
78 obj.delete()
79 self.post_delete(obj)
80 return Response(status=status.HTTP_204_NO_CONTENT)
Scott Baker8974e552015-02-10 19:26:00 -080081 else:
82 return Response(status=status.HTTP_400_BAD_REQUEST)
83
84 def handle_exception(self, exc):
85 # REST API drops the string attached to Django's PermissionDenied
86 # exception, and replaces it with a generic "Permission Denied"
87 if isinstance(exc, DjangoPermissionDenied):
Scott Baker9fd6c1c2015-02-16 23:34:31 -080088 response=Response({'detail': {"error": "PermissionDenied", "specific_error": str(exc), "fields": {}}}, status=status.HTTP_403_FORBIDDEN)
Scott Baker8974e552015-02-10 19:26:00 -080089 response.exception=True
90 return response
91 else:
92 return super(XOSRetrieveUpdateDestroyAPIView, self).handle_exception(exc)
93
94class XOSListCreateAPIView(generics.ListCreateAPIView):
95 def create(self, request, *args, **kwargs):
96 serializer = self.get_serializer(data=request.DATA, files=request.FILES)
Scott Baker9fd6c1c2015-02-16 23:34:31 -080097
98 # In rest_framework 3.x: we can pass raise_exception=True instead of
99 # raising the exception ourselves
100 if not serializer.is_valid():
101 raise XOSValidationError(fields=serializer._errors)
Scott Baker8974e552015-02-10 19:26:00 -0800102
103 # now do XOS can_update permission checking
104
105 obj = serializer.object
106 obj.caller = request.user
107 if not obj.can_update(request.user):
Scott Baker9fd6c1c2015-02-16 23:34:31 -0800108 raise XOSPermissionDenied()
Scott Baker8974e552015-02-10 19:26:00 -0800109
110 # stuff below is from generics.ListCreateAPIView
111
112 if (hasattr(self, "pre_save")):
113 # rest_framework 2.x
114 self.pre_save(serializer.object)
115 self.object = serializer.save(force_insert=True)
116 self.post_save(self.object, created=True)
117 else:
118 # rest_framework 3.x
119 self.perform_create(serializer)
120
121 headers = self.get_success_headers(serializer.data)
122 return Response(serializer.data, status=status.HTTP_201_CREATED,
123 headers=headers)
124
125 def handle_exception(self, exc):
126 # REST API drops the string attached to Django's PermissionDenied
127 # exception, and replaces it with a generic "Permission Denied"
128 if isinstance(exc, DjangoPermissionDenied):
Scott Baker9fd6c1c2015-02-16 23:34:31 -0800129 response=Response({'detail': {"error": "PermissionDenied", "specific_error": str(exc), "fields": {}}}, status=status.HTTP_403_FORBIDDEN)
Scott Baker8974e552015-02-10 19:26:00 -0800130 response.exception=True
131 return response
132 else:
133 return super(XOSListCreateAPIView, self).handle_exception(exc)
134