I'm experimenting with Tastypie and Django, and am wondering if what I'd like to do is possible. Let me explain...
I have a custom user resource (and model) as part of my api. I'd like to be able update users through this resource. To ensure that a user can only be updated by it's owner, I've enabled tastypie.authentication.BasicAuthentication
(this is done through the put_detail
method).
The problem is that I'd also like to be able to create users through this resource, which can't be done due to BasicAuthentication
since you need login credentials to access it.
So, I feel as though I've got the following options:
user
. Ideally, I'd like this second resource to be named user/create
, but the URL is never resolved correctly because it's picked up by the user
model.This is what I've tried with the 2nd approach...
class UserResource(ModelResource):
...
class Meta:
resource_name = 'users'
queryset = CustomUser.objects.all()
authentication = BasicAuthentication()
detail_allowed_methods = ['get', 'put']
def put_detail(self, request, **kwargs):
# stub
return HttpAccepted('User updated.')
class CreateUserResource(ModelResource):
class Meta:
resource_name = 'users/create'
queryset = CustomUser.objects.all()
# No authentication
list_allowed_methods = ['post']
def post_list(self, request, **kwargs):
# stub
return HttpCreated('User created.')
If I make a POST to api/v1/users/create
, it is never processed by CreateUserResource
, since tastypie interprets as a detail view for UsersResource
, and subsequently I get a 404.
So, is it possible to alter the order in which tastypie searches for the urls for it's resources? The order in which the resources are registered in my urls.py
file doesn't seem to make a difference. Or are there any alternative suggestions that may be better suited to what I'd like to implement?
Ok, I managed to do this using approach number 1, by overriding the dispatch
method for the UserResource
model. From the docs [link]:
dispatch
does a bunch of heavy lifting. It ensures:
- the requested HTTP method is in allowed_methods (
method_check
),- the class has a method that can handle the request (
get_list
),- the user is authenticated (
is_authenticated
),- the user is authorized (
is_authorized
),- & the user has not exceeded their throttle (
throttle_check
).At this point, dispatch actually calls the requested method (
get_list
).
Now, I wanted to bypass the is_authenticated
step, but only if the request
is a "POST" to the 'list' type. The code for my implementation is below...
from django.http import HttpResponse
from tastypie.http import HttpAccepted, HttpBadRequest, HttpCreated, HttpNoContent, HttpNotImplemented
from tastypie.authentication import BasicAuthentication
from tastypie.exceptions import ImmediateHttpResponse
from tastypie.fields import IntegerField, ToManyField
from tastypie.resources import ModelResource, convert_post_to_put
from <your_user_module> import User
class UserResource(ModelResource):
...
class Meta:
resource_name = 'users'
queryset = User.objects.all()
authentication = BasicAuthentication()
list_allowed_methods = ['get', 'post']
detail_allowed_methods = ['get']
# Bypasses the authentication process if the request is a
# POST to the 'list'. The code within the 'if' block is
# taken from 'tastypie.resources.Resource.dispatch()'
def dispatch_list(self, request, **kwargs):
if request.method == 'POST':
request_type = 'list'
allowed_methods = getattr(self._meta, "%s_allowed_methods" % request_type, None)
if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
request_method = super(UserResource, self).method_check(request, allowed=allowed_methods)
method = getattr(self, "%s_%s" % (request_method, request_type), None)
if method is None:
raise ImmediateHttpResponse(response=HttpNotImplemented())
# Authentication would normally happen here...
# ... simply comment it out.
# super(UserResource, self).is_authenticated(request)
super(UserResource, self).throttle_check(request)
# All clear. Process the request.
request = convert_post_to_put(request)
response = method(request, **kwargs)
# Add the throttled request.
super(UserResource, self).log_throttled_access(request)
# If what comes back isn't a ``HttpResponse``, assume that the
# request was accepted and that some action occurred. This also
# prevents Django from freaking out.
if not isinstance(response, HttpResponse):
return HttpNoContent()
return response
else:
return super(UserResource, self).dispatch_list(request, **kwargs)
# The normally required authentication is bypassed
# (through the overridden dispatch_list) method above.
def post_list(self, request, **kwargs):
# Process the request as usual...
return HttpCreated('User created.')