Django : TokenAuthentication, setting permissions on endpoints - python

I am using Django Rest Framework to build my API and I am quite new to it.
I set up a TokenAuthentication method, and I am now trying to filter the result of my queryset depending on this token.
Basically, I have a GET endpoint (let's say "/wallet"), and I want the /wallet endpoint to give the wallet of the specific user sending the query.
My approach was to redefine the get_queryset method in my ViewSet but I can't figure out how to get the token, and how to filter the results.
Also, anynomous users shouldn't be allowed to access that endpoint.
Here is my ViewSet, I think I need some customisation here :
class WalletViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows wallets to be viewed or edited.
"""
queryset = Wallet.objects.filter()
serializer_class = WalletSerializer

My approach was to redefine the get_queryset method in my ViewSet but I can't figure out how to get the token, and how to filter the results.
Django REST framework TokenAuthentication is linked to the user. Therefore I would advice that you filter against the user which should be available in the view through self.request.user
Also, anynomous users shouldn't be allowed to access that endpoint.
Check the permission section of the documentation for that.

To allow only authenticated users to access your viewset just do
class WalletViewSet(viewsets.ModelViewSet):
queryset = Wallet.objects.filter()
serializer_class = WalletSerializer
permission_classes = (IsAuthenticated,)
To get the specific user wallet you could get the user from request and than get it's wallet with a simple model query. The end result beig:
class WalletViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows wallets to be viewed or edited.
"""
queryset = Wallet.objects.filter()
serializer_class = WalletSerializer
permission_classes = (IsAuthenticated,)
def retrieve(self, request, pk, format=None):
wallet = Wallet.objects.get(user=request.user)
Response(WalletSerializer(wallet))

Related

How do I specify a custom lookup field for a DRF action on a viewset?

I would like to specify a custom lookup field on the action (different from the viewset default "pk"), i.e.
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field="uuid", # this does not work unfortunately
)
def get_something(self, request, uuid=None):
pass
But the router does not generate the correct urls:
router = DefaultRouter()
router.register(r"test", TestViewSet)
router.urls
yields url:
'^test/(?P<pk>[^/.]+)/something/$'
instead of
'^test/(?P<uuid>[^/.]+)/something/$'
I do not want to change the lookup field for the whole viewset though and have been unsuccessful in finding a way to do this for the action itself after debugging through the router url generation. I did notice that model viewsets have this method:
get_extra_action_url_map(self)
but am unsure how to get it to be called to generate custom urls or if it is even relevant. Any help would be great thanks!
According to their docs you could use a regex lookup field. Their example uses a CBV instead of a request based view.
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
This could work:
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
)
def get_something(self, request, uuid=None):
pass
I think it will create much confusion for your API consumers if you have 2 different resource identification on the same resource.
You can name that action query_by_uuid or just allow them to use list_view to filter by uuid if you only want to represent the object tho. (so consumers can use /test/?uuid= to retrieve data)
But if you really want to do it, you can simply override get_object method to filter for your custom action tho:
def get_object(self):
if self.action == 'do_something':
return get_object_or_404(self.get_queryset(), uuid=self.kwargs['pk'])
return super().get_object()
Here is a bit hacky solution for generate uuid in router with detail=False.
#action(detail=False, url_path=r'(?P<uuid>[^/.]+)/do_something')
def do_something(self, request, uuid=None):
pass

How to prevent authenticated users from making direct API request (Django rest framework)

When a Django Rest Framework Site is hosted, can authenticated users make direct request to the api .. or does CORS prevent them?
CORS states that every request that is not made from from the same origin will be blocked
provided that it is not in the "allowed hosts". (it concerns requests made from other domains and can be overcome by making the request on the server-side)
CORS will not handle requests made individually by someone if they are from their machine directly
It depends on what you mean by
can authenticated users make direct request to the api
You can always prevent someone from accessing something in the view
from django.http import HttpResponseForbidden
# But this will block them no matter wheter thy are accessing directly or not
if request.user.is_authenticated:
return HttpResponseForbidden()
But in short, yes everyone can make a request to the API directly and there is nothing wrong about it really.
EDIT : What you were really asking had nothing to do withs CORS but with DRM
What you want to do is use custom methods for certain types of requests e.g GET or POST Or more specifically have users only access specific information.
Here is a very simple way to do that
views.py (Writting your fully custom views)
#Mixins allow you to access only specific Methods like create,delete,list etc...
# mixins.CreateModelMixin and mixins.ListModelMixin will almost do for everything
from rest_framework import mixins, viewsets
#Take here as an example an API endpoint for creating users
class userCreation(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = UserSerializer #Here the serializers does not event matter you can choose something random
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
#Here you handle user creations
#Access POST data, validations etc
Or if you don't want to write everything by your self
Serializers.py (Just limiting the fields)
#Here is an example where you only want to check if a username or an email exists
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username','email']
views.py
class UserUsernameView(mixins.ListModelMixin,viewsets.GenericViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()

Handle POST request query param from browsable API in Django Rest Framework

I have a requirement of having a query param in a POST request with the JSON payload in the body.
Now in django-rest-framework I have been able to do that by overriding the perform_create method of ModelViewSet in my view class, but what I can do to have it appear in browsable API. I tried django-filter and its backend but they are geared towards filtering(obviously) in a GET request. I don't want to filter but want to just use that query param as a parameter to run celery tasks during perform_create.
This is how my viewset looks right now.
class AbcViewSet(viewsets.ModelViewSet):
queryset = Abc.objects.all()
serializer_class = AbcSerializer
filterset_fields = ('fk_field', )
def perform_create(self, serializer):
query_param1 = self.request.query_params.get('param', None)
...schedule celery task using the query_param1 and save the model...
my request for this will be like http://127.0.0.1:8000/abc/?param=xyz
If you notice above I want to be able to fill thatparam using browsable API. Trying to search I have a feeling it will involve creating some kind of HTML view rendering for the param but If its more easier available from browsable API that would definitely help. Right now I don't see any way to send any query params other than filters.
You can call the browsable API with the specific query parameter like for example:
http://localhost:8000/yoururl?param=111 and when performing the POST the self.request.query_params will yield <QueryDict: {'param': ['111']}>.

Difference between ViewSet and GenericViewSet in Django rest framework

I have a Django rest framework GenericViewset for which I am trying to set up pagination as follows:
#settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':
'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20
}
#views.py
class PolicyViewSet(viewsets.GenericViewSet):
def list(self, request):
queryset = Policy.objects.all()
page = self.paginate_queryset(queryset)
serializer = PolicySerializer(page, many=True)
return self.get_paginated_response(serializer.data)
This works as expected.However, if i try to do the same with just a normal Viewset as follows:
#views.py
class PolicyViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Policy.objects.all()
page = self.paginate_queryset(queryset)
serializer = PolicySerializer(page, many=True)
return self.get_paginated_response(serializer.data)
I get an error stating:
'PolicyViewSet' object has no attribute 'paginate_queryset'
How do i set up pagination with a normal Viewset. What is the difference between a GenericViewset and Viewset in DRF ?
Pagination is only performed automatically if you're using the generic
views or viewsets
Read the docs
And to answer your second question What is the difference between a GenericViewset and Viewset in DRF
DRF has two main systems for handling views:
APIView: This provides some handler methods, to handle the http verbs: get, post, put, patch, and delete.
ViewSet: This is an abstraction over APIView, which provides actions as methods:
list: read only, returns multiple resources (http verb: get). Returns a list of dicts.
retrieve: read only, single resource (http verb: get, but will expect an id). Returns a single dict.
create: creates a new resource (http verb: post)
update/partial_update: edits a resource (http verbs: put/patch)
destroy: removes a resource (http verb: delete)
GenericViewSet: There are many GenericViewSet, the most common being ModelViewSet. They inherit from GenericAPIView and have a full implementation of all of the actions: list, retrieve, destroy, updated, etc. Of course, you can also pick some of them, read the docs.
just inherit also from GenericViewSet.
For example:
#views.py
class PolicyViewSet(viewsets.ViewSet, viewsets.GenericViewSet):
def list(self, request):
queryset = Policy.objects.all()
page = self.paginate_queryset(queryset)
serializer = PolicySerializer(page, many=True)
return self.get_paginated_response(serializer.data)
How do i set up pagination with a normal Viewset?
If you want to use "pagination_class" in your viewset, so you should use GenericViewSet instead of ViewSet.
From the django rest-framework source code:
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
The APIView does not contains the pagination it is implemented inside GenericAPIView
To use pagination inside ViewSet you should set the pagination class and follow the source code of GenericAPIView
The GenericAPIView contain the additional code which make it more structured. In case of usual cases GenericAPIView does the same task with less code. For complex scenarios when you want to customize more GenericAPIView would not provide any additional advantage.

In Django Rest Framework, how is the object passed to a class based view from urls.py used?

I've been following the Django Rest Framework tutorial here http://www.django-rest-framework.org/tutorial/3-class-based-views/
This code in particular is intriguing:
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
I know that the url function called in urls.py returns a function like entry point to the class based view when using the as_view method. Is request the object corresponding to this?
If it is, then where does the class access it and its attributes when a GET is performed? I cannot see a reference to request in the getmethod of SnippetList.
If it is not, then what does request refer to, and how does the class based view access the data in the client's request?

Categories

Resources