Reputation: 18735
I'm trying to create an api for our users. I've decided to use Django-rest-framework
.
Users need only GET requests. Each GET request has to have 'token' attribute to determine which data should we return.
So I've created a function which tries to get user
object from request
object.
def get_user(request):
token = request.GET.get('token')
if not token:
return HttpResponseForbidden()
user = get_object_or_404(User,auth_token=token)
return user
I want to use this function in my view:
@api_view(['GET'])
def products(request):
user = get_user(request)
products = user.products.all()
serializer = ProductSerializer(products, many=True)
return JsonResponse(serializer.data, safe=False)
The problem is that when I doesn't provide token
attribute, it raises:
AttributeError at /api/products/
Exception Value: 'HttpResponseForbidden' object has no attribute 'products'
Instead of proper response.
I can do:
@api_view(['GET'])
def products(request):
user_or_response = get_user(request)
if user_or_response.__class__ != User:
return _or_response
products = _or_response.products.all()
serializer = ProductSerializer(products, many=True)
return JsonResponse(serializer.data, safe=False)
As you can see, it doesn't automatically return forbidden response, instead, it returns it as user. I can return it in products
view but maybe there is a better way to do this with Django-rest-framework
.
What should I do? Do you have a better approach or any advice?
Upvotes: 0
Views: 1141
Reputation: 6096
Yeah, the HttpResponseForbidden
is returned into your function, not from the view.
In this situation, you can try the following approach
def get_user(request):
token = request.GET.get('token')
if not token:
return False
user = get_object_or_404(User,auth_token=token)
return user
@api_view(['GET'])
def products(request):
user = get_user(request)
if not user:
return HttpResponseForbidden()
products = user.products.all()
serializer = ProductSerializer(products, many=True)
return JsonResponse(serializer.data, safe=False)
You can also potentially raise the exception instead of returning it in get_user
and then handle it with a try/except
inside the view.
Either way, it's just a little cleaner than comparing .__class__
to User
.
Upvotes: 1
Reputation: 82028
So, right now it looks like you're muddling the difference between your model and your view. The model is the thing that looks up data. It generally should not be returning response code or handling validation with errors.
Python's behavior is correct, however. You are returning an object from get_user
and then you are telling it to expect a products property on the returned object.
If I were you, I would do this:
def get_user(request):
token = request.GET.get('token')
if not token:
return
user = get_object_or_404(User,auth_token=token)
return user
@api_view(['GET'])
def products(request):
user = get_user(request)
if not user:
return HttpForbiddenResponse()
products = user.products.all()
serializer = ProductSerializer(products, many=True)
return JsonResponse(serializer.data, safe=False)
I would NOT check the class. Not only does this not fair very well in Pythonic philosophy, but it also means that you can't do something like create an AdminUser(User)
class. If you really want to return an arbitrary value from get_user
, then I would do:
@api_view(['GET'])
def products(request):
user = get_user(request)
if not hasattr(user, 'products'):
return HttpForbiddenResponse()
That will check for a defined property, which matches OOP and a slightly more Pythonic approach.
Upvotes: 1