REST API exception cleanup
diff --git a/planetstack/apigen/api.template.py b/planetstack/apigen/api.template.py
index b3438c0..ae67083 100644
--- a/planetstack/apigen/api.template.py
+++ b/planetstack/apigen/api.template.py
@@ -11,7 +11,7 @@
from django.conf.urls import patterns, url
from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
-from xosapibase import XOSRetrieveUpdateDestroyAPIView, XOSListCreateAPIView
+from xosapibase import XOSRetrieveUpdateDestroyAPIView, XOSListCreateAPIView, XOSNotAuthenticated
if hasattr(serializers, "ReadOnlyField"):
# rest_framework 3.x
@@ -181,7 +181,7 @@
def get_queryset(self):
if (not self.request.user.is_authenticated()):
- raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
+ raise XOSNotAuthenticated()
return {{ object.camel }}.select_by_user(self.request.user)
@@ -201,7 +201,7 @@
def get_queryset(self):
if (not self.request.user.is_authenticated()):
- raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
+ raise XOSNotAuthenticated()
return {{ object.camel }}.select_by_user(self.request.user)
# update() is handled by XOSRetrieveUpdateDestroyAPIView
diff --git a/planetstack/core/xoslib/methods/sliceplus.py b/planetstack/core/xoslib/methods/sliceplus.py
index 0539e62..cdb2f73 100644
--- a/planetstack/core/xoslib/methods/sliceplus.py
+++ b/planetstack/core/xoslib/methods/sliceplus.py
@@ -7,8 +7,7 @@
from django.forms import widgets
from core.xoslib.objects.sliceplus import SlicePlus
from plus import PlusSerializerMixin
-from xosapibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView
-from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
+from xosapibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
if hasattr(serializers, "ReadOnlyField"):
# rest_framework 3.x
@@ -82,7 +81,7 @@
current_user_can_see = self.request.QUERY_PARAMS.get('current_user_can_see', False)
if (not self.request.user.is_authenticated()):
- raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
+ raise XOSPermissionDenied("You must be authenticated in order to use this API")
slices = SlicePlus.select_by_user(self.request.user)
@@ -108,7 +107,7 @@
def get_queryset(self):
if (not self.request.user.is_authenticated()):
- raise RestFrameworkPermissionDenied("You must be authenticated in order to use this API")
+ raise XOSPermissionDenied("You must be authenticated in order to use this API")
return SlicePlus.select_by_user(self.request.user)
diff --git a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
index a055093..2779cd0 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
@@ -145,18 +145,21 @@
parsed_error=undefined;
width=640; // django stacktraces like wide width
}
+
console.log(responseText);
console.log(parsed_error);
+ if (parsed_error && ("detail" in parsed_error)) {
+ parsed_error = parsed_error["detail"];
+ }
+
if (parsed_error && ("error" in parsed_error)) {
+ if ((!parsed_error.reasons) && (parsed_error.fields)) {
+ // deal with me renaming 'reasons' to 'fields'
+ parsed_error.reasons = parsed_error.fields;
+ }
// this error comes from genapi views
$("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
- } else if (parsed_error && ("detail" in parsed_error)) {
- // this error response comes from rest_framework APIException
- parsed_error["error"] = "API Error";
- parsed_error["specific_error"] = parsed_error["detail"];
- parsed_error["reasons"] = [];
- $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
} else {
$("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: strip_scripts(responseText)}))
}
diff --git a/planetstack/xosapibase.py b/planetstack/xosapibase.py
index ea84d9a..c523943 100644
--- a/planetstack/xosapibase.py
+++ b/planetstack/xosapibase.py
@@ -2,9 +2,36 @@
from rest_framework import serializers
from rest_framework import generics
from rest_framework import status
+from rest_framework.exceptions import APIException
from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
+class XOSProgrammingError(APIException):
+ status_code=400
+ def __init__(self, why="programming error", fields={}):
+ APIException.__init__(self, {"error": "XOSProgrammingError",
+ "specific_error": why,
+ "fields": fields})
+
+class XOSPermissionDenied(RestFrameworkPermissionDenied):
+ def __init__(self, why="permission error", fields={}):
+ APIException.__init__(self, {"error": "XOSPermissionDenied",
+ "specific_error": why,
+ "fields": fields})
+
+class XOSNotAuthenticated(RestFrameworkPermissionDenied):
+ def __init__(self, why="you must be authenticated to use this api", fields={}):
+ APIException.__init__(self, {"error": "XOSNotAuthenticated",
+ "specific_error": why,
+ "fields": fields})
+
+class XOSValidationError(APIException):
+ status_code=403
+ def __init__(self, why="validation error", fields={}):
+ APIException.__init__(self, {"error": "XOSValidationError",
+ "specific_error": why,
+ "fields": fields})
+
class XOSRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
# To handle fine-grained field permissions, we have to check can_update
@@ -15,7 +42,7 @@
self.object = self.get_object_or_none()
if self.object is None:
- raise Exception("Use the List API for creating objects")
+ raise XOSProgrammingError("Use the List API for creating objects")
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
@@ -25,27 +52,21 @@
serializer.object.caller = request.user
if not serializer.is_valid():
- response = {"error": "validation",
- "specific_error": "not serializer.is_valid()",
- "reasons": serializer.errors}
- return Response(response, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- self.pre_save(serializer.object)
- except ValidationError as err:
- # full_clean on model instance may be called in pre_save,
- # so we have to handle eventual errors.
- response = {"error": "validation",
- "specific_error": "ValidationError in pre_save",
- "reasons": err.message_dict}
- return Response(response, status=status.HTTP_400_BAD_REQUEST)
+ raise XOSValidationError(fields=serializer._errors)
if not serializer.object.can_update(request.user):
- return Response(status=status.HTTP_400_BAD_REQUEST)
+ raise XOSPermissionDenied()
- self.object = serializer.save(force_update=True)
- self.post_save(self.object, created=False)
- return Response(serializer.data, status=status.HTTP_200_OK)
+ if (hasattr(self, "pre_save")):
+ # rest_framework 2.x
+ self.pre_save(serializer.object)
+ self.object = serializer.save(force_update=True)
+ self.post_save(self.object, created=False)
+ else:
+ # rest_framework 3.x
+ self.perform_update(serializer)
+
+ return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
@@ -64,7 +85,7 @@
# REST API drops the string attached to Django's PermissionDenied
# exception, and replaces it with a generic "Permission Denied"
if isinstance(exc, DjangoPermissionDenied):
- response=Response({'detail': str(exc)}, status=status.HTTP_403_FORBIDDEN)
+ response=Response({'detail': {"error": "PermissionDenied", "specific_error": str(exc), "fields": {}}}, status=status.HTTP_403_FORBIDDEN)
response.exception=True
return response
else:
@@ -73,21 +94,18 @@
class XOSListCreateAPIView(generics.ListCreateAPIView):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
- if not (serializer.is_valid()):
- response = {"error": "validation",
- "specific_error": "not serializer.is_valid()",
- "reasons": serializer.errors}
- return Response(response, status=status.HTTP_400_BAD_REQUEST)
+
+ # In rest_framework 3.x: we can pass raise_exception=True instead of
+ # raising the exception ourselves
+ if not serializer.is_valid():
+ raise XOSValidationError(fields=serializer._errors)
# now do XOS can_update permission checking
obj = serializer.object
obj.caller = request.user
if not obj.can_update(request.user):
- response = {"error": "validation",
- "specific_error": "failed can_update",
- "reasons": []}
- return Response(response, status=status.HTTP_400_BAD_REQUEST)
+ raise XOSPermissionDenied()
# stuff below is from generics.ListCreateAPIView
@@ -108,7 +126,7 @@
# REST API drops the string attached to Django's PermissionDenied
# exception, and replaces it with a generic "Permission Denied"
if isinstance(exc, DjangoPermissionDenied):
- response=Response({'detail': str(exc)}, status=status.HTTP_403_FORBIDDEN)
+ response=Response({'detail': {"error": "PermissionDenied", "specific_error": str(exc), "fields": {}}}, status=status.HTTP_403_FORBIDDEN)
response.exception=True
return response
else: