"""
View classes to help facilitate the creation of REST APIs
"""
import json
from django.core.exceptions import PermissionDenied, ValidationError
from django.http import HttpResponse, Http404
from django.views.generic.base import View
from django.conf import settings
from fusionbox.core.serializers import FusionboxJSONEncoder
[docs]class JsonResponseMixin(object):
"""
Sets the response MIME type to ``application/json`` and serializes the
context obj as a JSON string.
"""
[docs] def render_to_response(self, obj, **response_kwargs):
"""
Returns an ``HttpResponse`` object instance with Content-Type:
application/json.
The response body will be the return value of ``self.serialize(obj)``
"""
return HttpResponse(self.serialize(obj), content_type='application/json', **response_kwargs)
[docs] def serialize(self, obj):
"""
Returns a json serialized string object encoded using
`fusionbox.core.serializers.FusionboxJSONEncoder`.
"""
kwargs = {}
if settings.DEBUG:
kwargs['indent'] = 4
kwargs['separators'] = (',', ': ')
return json.dumps(obj, cls=FusionboxJSONEncoder, **kwargs)
[docs] def http_method_not_allowed(self, *args, **kwargs):
"""
Returns super after setting the Content-Type header to
``application/json``
"""
resp = super(JsonResponseMixin, self).http_method_not_allowed(*args, **kwargs)
resp['Content-Type'] = 'application/json'
return resp
[docs]class JsonRequestMixin(object):
"""
Adds a ``data`` method on the view instance. It returns the GET parameters
if it is a GET request. It will return the python representation of the
JSON sent with the request body.
"""
[docs] def data(self):
"""
Helper class for parsing JSON POST data into a Python object.
"""
if self.request.method == 'GET':
return self.request.GET
else:
assert self.request.META['CONTENT_TYPE'].startswith('application/json')
return json.loads(self.request.body)
[docs]class RestView(JsonResponseMixin, JsonRequestMixin, View):
"""
Inherit this base class to implement a REST view.
This view will handle:
- authentication (throuh the ``auth`` method)
- dispatching to the proper HTTP method function
- returning a proper error status code.
It also implements a default response for the OPTIONS HTTP request method.
"""
[docs] def auth(*args, **kwargs):
"""
Hook for implementing custom authentication.
Raises ``NotImplementedError`` by default. Subclasses must overwrite
this.
"""
raise NotImplementedError("If you really want no authentication, override this method")
[docs] def dispatch(self, *args, **kwargs):
"""
Authenticates the request and dispatches to the correct HTTP method
function (GET, POST, PUT,...).
Translates exceptions into proper JSON serialized HTTP responses:
- ValidationError: HTTP 409
- Http404: HTTP 404
- PermissionDenied: HTTP 403
- ValueError: HTTP 400
"""
try:
self.auth(*args, **kwargs)
return super(RestView, self).dispatch(*args, **kwargs)
except ValidationError as e:
return self.render_to_response(e.message_dict, status=409)
except Http404 as e:
return self.render_to_response(str(e), status=404)
except PermissionDenied as e:
return self.render_to_response(str(e), status=403)
except ValueError as e:
return self.render_to_response(str(e), status=400)
[docs] def options(self, request, *args, **kwargs):
"""
Implements a OPTIONS HTTP method function returning all allowed HTTP
methods.
"""
allow = []
for method in self.http_method_names:
if hasattr(self, method):
allow.append(method.upper())
r = self.render_to_response(None)
r['Allow'] = ','.join(allow)
return r