""" Generic resource class. """ from django.utils.translation import ugettext as _ from authentication import NoAuthentication from django.core.urlresolvers import reverse as _reverse from django.http import Http404, HttpResponse, HttpResponseNotAllowed def load_put_and_files(request): """ Populates request.PUT and request.FILES from request.raw_post_data. PUT and POST requests differ only in REQUEST_METHOD, not in the way data is encoded. Therefore we can use Django's POST data retrieval method for PUT. """ if request.method == 'PUT': request.method = 'POST' request._load_post_and_files() request.method = 'PUT' request.PUT = request.POST del request._post def reverse(viewname, args=(), kwargs=None): """ Return the URL associated with a view and specified parameters. If the regular expression used specifies an optional slash at the end of the URL, add the slash. """ if not kwargs: kwargs = {} url = _reverse(viewname, None, args, kwargs) if url[-2:] == '/?': url = url[:-1] return url class HttpMethodNotAllowed(Exception): """ Signals that request.method was not part of the list of permitted methods. """ class ResourceBase(object): """ Base class for both model-based and non-model-based resources. """ def __init__(self, authentication=None, permitted_methods=None): """ authentication: the authentication instance that checks whether a request is authenticated permitted_methods: the HTTP request methods that are allowed for this resource e.g. ('GET', 'PUT') """ # Access restrictions if not authentication: authentication = NoAuthentication() self.authentication = authentication if not permitted_methods: permitted_methods = ["GET"] self.permitted_methods = [m.upper() for m in permitted_methods] def dispatch(self, request, target, *args, **kwargs): """ """ request_method = request.method.upper() if request_method not in self.permitted_methods: raise HttpMethodNotAllowed if request_method == 'GET': return target.read(request, *args, **kwargs) elif request_method == 'POST': return target.create(request, *args, **kwargs) elif request_method == 'PUT': load_put_and_files(request) return target.update(request, *args, **kwargs) elif request_method == 'DELETE': return target.delete(request, *args, **kwargs) else: raise Http404 def get_url(self): """ Returns resource URL. """ return reverse(self) # The four CRUD methods that any class that # inherits from Resource may implement: def create(self, request): raise Http404 def read(self, request): raise Http404 def update(self, request): raise Http404 def delete(self, request): raise Http404 class Resource(ResourceBase): """ Generic resource class that can be used for resources that are not based on Django models. """ def __init__(self, authentication=None, permitted_methods=None, mimetype=None): """ authentication: the authentication instance that checks whether a request is authenticated permitted_methods: the HTTP request methods that are allowed for this resource e.g. ('GET', 'PUT') mimetype: if the default None is not changed, any HttpResponse calls use settings.DEFAULT_CONTENT_TYPE and settings.DEFAULT_CHARSET """ ResourceBase.__init__(self, authentication, permitted_methods) self.mimetype = mimetype def __call__(self, request, *args, **kwargs): """ Redirects to one of the CRUD methods depending on the HTTP method of the request. Checks whether the requested method is allowed for this resource. """ # Check permission if not self.authentication.is_authenticated(request): response = HttpResponse(_('Authorization Required'), mimetype=self.mimetype) challenge_headers = self.authentication.challenge_headers() for k,v in challenge_headers.items(): response[k] = v response.status_code = 401 return response try: return self.dispatch(request, self, *args, **kwargs) except HttpMethodNotAllowed: response = HttpResponseNotAllowed(self.permitted_methods) response.mimetype = self.mimetype return response