I am using Django REST Framework (DRF) for my API. In particular, I have set up several routes using the ModelViewSet, without much additional coding. In particular, I have a route like
/api/v1/collection/<key>
From all the possible keys, I do need to block one specific key, say special. Any request with any HTTP verb to /api/v1/collection/special should result in a HTTP 404 error.
I can think of two approaches:
Override some method on the ViewSet hierarchy. Which one?
In urls.py, set up a higher-precedence URL route that intercepts this URL, like path("collection/special", view=<404View>, name="collection-exception"). Does this make sense? What would be the appropriate exception view to route to?
What is the recommended approach? Any of the above, or something else?
If you are using the ModelViewSet then you should overwrite the get_queryset() method. If key value is wrong then raise the Exception otherwise return the queryset:
from rest_framework.exceptions import NotFound
class MyModelViewSet(viewsets.ModelViewSet):
# ...
def get_queryset(self):
key = self.kwargs.get("key")
if key == "special":
raise NotFound()
return MyModel.objecs.filter(pk=key)
Related
First of all, I hope the title of this question is clear enough. The following lines will make things clearer if it is not the case.
I have different users in my database. Some are part of the staff and some are just regular users.
In my API, I have a /users route. My idea is that when a staff member requests it (GET), he will see only the staff users and when a regular user will request it, he will only see the regular users.
Whether or not the user which makes the request is a member of the staff or not is an information which is stored in the JWT token.
Therefore, I used the following code:
class CustomUserList(generics.ListCreateAPIView):
serializer_class = CustomUserSerializer
def get_queryset(self):
token = self.request.META.get('HTTP_AUTHORIZATION')[7:] # [7:] to get rid of 'Bearer '
is_staff = jwt.decode(token, None, None)['is_staff']
print(is_staff)
queryset = CustomUser.objects.filter(is_staff=is_staff)
return queryset
This code works but is there a more direct / logical way to accomplish this ?
It feels a bit off storing what is essentially a server-side permission/setting on the client side (even though some people do use JWT for that). What if a user is promoted to/demoted from staff? You'd have to revoke all such client tokens? Also, what if you need to attach more conditions to these queries?
I think a slightly more flexible approach is to store some kind of a user id in the JWT token, then use a TokenAuthentication class with JWT to establish the identity of the user, attach it to something like request.user and then in your get_queryset method filter by request.user.is_staff. That way, you can attach any context/filters/permissions to the authenticated users on the server side and don't need to rely on the client to present explicit claim(s) to filter out the objects they can access. You'd end up with an extra call to the database to populate request.user, but you might end up needing that anyway.
Authenticating with a JWT token in the header retrieves the user so your view will have access to request.user and all of the user's attributes in every method after the dispatch() method (the first method that is run after .as_view() is triggered when sending a request to an endpoint).
If you're using djangorestframework-simplejwt, the DRF recommended JWT package, all you need to do is this:
class CustomUserList(generics.ListCreateAPIView):
queryset = CustomUser.objects.all()
serializer_class = CustomUserSerializer
def get_queryset(self):
queryset = super().get_queryset()
if self.request.user.is_staff:
queryset = queryset.filter(is_staff=True)
return queryset
In my website, people can see images given from REST APIs without login. I want to make some of my APIs not to require a user token. So, I had some research and I found the way below.
Since I need to use this way for production, I want to make sure if it is a safe way. The stores that will be given from the API have just store data such as name, urls, description, and images. Any of them are not related to any users.
Is it okay for me to use in production? Just so you know, I use Django REST Framework to serve data to frontend and use React js to show them in frontend side.
from rest_framework.decorators import authentication_classes, permission_classes
#authentication_classes([])
#permission_classes([])
class ListAllStores(APIView):
def get(self, request, format=None):
all_stores = Store.objects.all()
serializer = StoreSerializer(all_stores, many=True)
return Response(data=serializer.data)
You can try something link sending user type on header of each request.
And for the API without token send userType as anonymous or something like that
And for the API with token send userType as customer or something like that.
Create a method which will be called first thing from the each end points and validate the Request header.
If you want to make it more general you can map this end point and type of user as meta data setting, So next time if you have some '/image' end point which was allowed only to the customer user type but now you want it should be allowed to anon also so that can be easily done using that meta data file without changing the code.
You can be explicit by using AllowAny permission class, which will let public access to that endpoint.
from rest_framework import permissions, views
class ListAllStores(views.APIView):
permission_classes = (
permissions.AllowAny,
)
def get(self, request, format=None):
all_stores = Store.objects.all()
serializer = StoreSerializer(all_stores, many=True)
return Response(data=serializer.data)
I have a serializer that accepts two objects, one of which is always constant once a user has authenticated.
The serializer, of course, should not need to know these details; it should know only that it serializes and deserializes an object with two fields.
Further, the user, having authenticated, should only have to pass in the value he wishes to set to the non-constant field via the viewset.
So what I need is a hook somewhere that intercepts the data before the serializer receives it, sets the constant field to the user's set value, and then allows processing to proceed as usual.
Is there any such hook in DRF? The solutions I've seen are all very hacky: context (coupling of view and serialization logic); validations (more coupling); and so forth. In theory what we want is:
data = union(userFields, fixedFieldsForView)
Serializer(data=data)
where the data part is somehow handled in the view only. Note also this is only during creation and update of objects, not reads (which are filtered already by get_queryset).
If the question is about adding additional parameters to the serializer, then yes:
save the sterilizer with the extra arguments serializer.save(extra_arg=value, extra_arg2=value)
override the view's perform_create and perform_update to call the serializer.save
We took the following approach. Feedback welcome.
def get_serializer(self, *args, **kwargs):
if self.request.method == 'POST' and 'data' in kwargs:
kwargs['data']['fixed_value'] = self.get_user_fixed_value()
return super(OurModel, self).get_serializer(*args, **kwargs)
So when a user POSTs to the endpoint, regardless the setting of fixed_value, that value will always be set correctly.
Still feels like a bit of a hack, but works for us for now.
There is a common pattern to return serializer.data after an object has been successfully saved. However, if the to_internal_value returns a Django object (such as in a PrimeryKeyRelatedField), then the Response would produce this error:
raise TypeError(repr(o) + " is not JSON serializable")
A few solutions to this:
Run that data the other way, i.e. FooSerializer(data=serializer.data)
Manually replace it, i.e. serializer.data['field'] = serializer.data['field'].pk
Inherit something somewhere so that the Response can accept Django objects and default to __str__ representation (but probably impossible unless there's a way for isinstance to know if it's a subclass of models.Model)
Don't return the [entire] object. The other side obviously has a copy of the data they sent, so there's no reason to send back the fields that weren't changed by the server.
Which of these solutions is most Pythonic, or is there a better solution not listed here?
There is a common pattern to return serializer.data after an object has been successfully saved.
No, this may be a common anti-pattern.
It returns serializer.validated_data which is different.
I'm working on a pretty big project right now where every view should be accessible via a normal request and an ajax request via the same url. I'm looking for ideas on how to create a small framework to handle this in a very generic way. Depending on if the view is called via ajax or not it needs to render a different template and return a json instead of a HttpResponse object. I'd like to collect any ideas on this topic - the main goal should be not to avoid the dry principle and make code that is as reusable as possible. I was already considering different options as generic views, decorators on views etc, but I'm open to anything. So please let me hear your suggestions or point me towards any readymade snippets you know!
This article seems to be quite a good tutorial on how to work with both ajax and regular requests. The request object has a method is_ajax() which will look for HTTP_X_REQUESTED_WITH: XMLHttpRequest. This will of course depend on these values being set correctly by the javascript sending the request.
From the article:
from django.http import HttpResponse
from django.core import serializers
from django.shortcuts import render_to_response
from your_app.models import ExampleModel
def xhr_test(request, format):
obj = ExampleModel.objects.all()
if request.is_ajax():
data = serializers.serialize('json', obj)
return HttpResponse(data,'json')
else:
return render_to_response('template.html', {'obj':obj}, context=...)
Or, you could use django-piston which is a RESTful framework for Django. I use this module in my project. You can define resources (sort of like views), and depending on either the mime-type or format passed to your url, it will emit either html, xml, or json. This will probably be the best way to go if every single view (or a large majority) need to be returned in different formats.
I have used a decorator for this. Have the view return the context, the template, and an alternate template.
If the Ajax version wants to return data, the third return value can be the data object to turn into JSON.