Source code for restless.views

from django.views.generic import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

from django.conf import settings
from django.http import HttpResponse
from .http import Http200, Http500, HttpError

import traceback
import json

__all__ = ['Endpoint']


[docs]class Endpoint(View): """ Class-based Django view that should be extended to provide an API endpoint (resource). To provide GET, POST, PUT, HEAD or DELETE methods, implement the corresponding get(), post(), put(), head() or delete() method, respectively. If you also implement authenticate(request) method, it will be called before the main method to provide authentication, if needed. Auth mixins use this to provide authentication. The usual Django "request" object passed to methods is extended with a few more attributes: * request.content_type - the content type of the request * request.params - a dictionary with GET parameters * request.data - a dictionary with POST/PUT parameters, as parsed from either form submission or submitted application/json data payload * request.raw_data - string containing raw request body The view method should return either a HTTPResponse (for example, a redirect), or something else (usually a dictionary or a list). If something other than HTTPResponse is returned, it is first serialized into :py:class:`restless.http.JSONResponse` with a status code 200 (OK), then returned. The authenticate method should return either a HttpResponse, which will shortcut the rest of the request handling (the view method will not be called), or None (the request will be processed normally). Both methods can raise a :py:class:`restless.http.HttpError` exception instead of returning a HttpResponse, to shortcut the request handling and immediately return the error to the client. """ @staticmethod def _parse_content_type(content_type): if ';' in content_type: ct, params = content_type.split(';', 1) try: params = dict(param.split('=') for param in params.split()) except: params = {} else: ct = content_type params = {} return ct, params def _parse_body(self, request): if request.method not in ['POST', 'PUT', 'PATCH']: return ct, ct_params = self._parse_content_type(request.content_type) if ct == 'application/json': charset = ct_params.get('charset', 'utf-8') try: data = request.body.decode(charset) request.data = json.loads(data) except Exception as ex: raise HttpError(400, 'invalid JSON payload: %s' % ex) elif ((ct == 'application/x-www-form-urlencoded') or (ct.startswith('multipart/form-data'))): request.data = dict((k, v) for (k, v) in request.POST.items()) else: request.data = request.body def _process_authenticate(self, request): if hasattr(self, 'authenticate') and callable(self.authenticate): auth_response = self.authenticate(request) if isinstance(auth_response, HttpResponse): return auth_response elif auth_response is None: pass else: raise TypeError('authenticate method must return ' 'HttpResponse instance or None') @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): request.content_type = request.META.get('CONTENT_TYPE', 'text/plain') request.params = dict((k, v) for (k, v) in request.GET.items()) request.data = None request.raw_data = request.body try: self._parse_body(request) authentication_required = self._process_authenticate(request) if authentication_required: return authentication_required response = super(Endpoint, self).dispatch(request, *args, **kwargs) except HttpError as err: response = err.response except Exception as ex: if settings.DEBUG: response = Http500(str(ex), traceback=traceback.format_exc()) else: raise if not isinstance(response, HttpResponse): response = Http200(response) return response