How to get the authentication information in ModelViewSet? - python

I am using rest framework with ModelViewSet
class DrawingViewSet(viewsets.ModelViewSet):
queryset = m.Drawing.objects.all()
serializer_class = s.DrawingSerializer
filterset_fields = ['user']
def list(self, request):
queryset = m.Drawing.objects.all()
serializer = s.DrawingSerializer(queryset, many=True)
return Response(serializer.data)
With this script, I can use filter such as /?user=1
Howver this user=1 is not necesary when authentication information is used in script.
(Because one user needs to fetch only data belonging to him/herself)
How can I get the authentication data in list?

If you want to return data based on user him/herself, there is no need to pass user id. If your user is authenticated, you can have his/her ID via request so:
from rest_framework.permissions import IsAuthenticated
class DrawingViewSet(viewsets.ModelViewSet):
...
permission_classes = (IsAuthenticated,)
def list(self, request):
queryset = m.Drawing.objects.filter(user=request.user)
serializer = s.DrawingSerializer(queryset, many=True)
return Response(serializer.data)

yes you can do like this as the above answer or you can override the get_queryset method as well.
def get_queryset(self):
if self.request.user.is_authenticated:
return whatever you want to return
else:
return None

Related

Provide request data from view into serializer ModelViewSet Django

I try to make a custom list function inside ProductViewSet because I need to download an extra field - the highest product price in database. How can I provide the request argument from def list into serializer? I mean right now I get error 'NoneType' object has no attribute 'user' in this line: if request.user.is_authenticated.
So how can I fix it that he can read self.context.get('request') correctly?
class ProductViewSet(viewsets.ModelViewSet):
...
def list(self, request):
queryset = Product.objects.all()
serializer = ProductSerializer(queryset, many=True)
return Response(serializer.data)
class ProductSerializer(serializers.ModelSerializer):
...
class Meta:
model = Product
...
def get_is_followed(self, obj):
request = self.context.get('request')
if request.user.is_authenticated:
return Product.objects.filter(id=obj.id, followers=request.user.id).exists()
I want to work it like a default ModelViewSet list but with an extra field.
You have used ModelViewSet which already has a serializer_class attribute. You can simply provide the serializer_class and the serializer context is taken care by DRF itself. So, instead of writing
serializer = ProductSerializer(queryset, many=True) you should write in this way:
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
If your concern is only the request object as a context to the serializer then there is no need to override the list method in the ProductViewSet. By default three contexts (request, format and view) are passed to the serializer but if you need extra contexts then you can always override def get_serializer_context(self) method in the view.
This is the default signature of the get_serializer_context:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}

Django Swagger won't allow me to use POST method (no parameters shown)

I'm using djangorestframework together with drf-spectacular modules for a Django project, and I'm trying to build some basic API methods for my Project model. Its structure looks like this:
from django.db import models
# Create your models here.
class Project(models.Model):
title = models.CharField(max_length = 128)
description = models.TextField()
image = models.URLField()
date = models.DateTimeField(auto_now_add=True)
I also have a serializer for the model, looking like this:
from rest_framework import serializers
from api.models.Project import Project
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['title', 'description', 'image', 'date']
Then, in views.py, I created two functions: project_list_view, which either lets you to GET all the Project objects from the database, or lets you POST a new object. And finally, project_detail_view, which lets you GET a Project object by typing in its pk (integer id). These are my two functions:
#api_view(['GET', 'POST'])
def project_list_view(request):
if request.method == 'GET':
projects = Project.objects.all()
serializer = ProjectSerializer(projects, many=True)
return Response(serializer.data)
elif request.method == "POST":
serializer = ProjectSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#api_view(['GET'])
def project_detail_view(request, pk):
if request.method == "GET":
try:
project = Project.objects.get(pk = pk)
serializer = ProjectSerializer(project, many = False)
return Response(serializer.data, status = status.HTTP_200_OK)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
The GET from project_list_view and project_detail_view work, but my problem lays in the POST method.
My Swagger is set to display its API Schema when accessing http://127.0.0.1:8000/docs/, and as I said, GET methods work properly, but when I'm trying to click on "Try it out" at the POST method, the fields are not displayed. I can only press "Execute" without actually being able to complete anything. After I click on "Execute", Swagger returns a 404 Bad Request response.
This is how POST looks like in Swagger:
My question is: Why won't Swagger display fields for each parameter of the model? Thank you.
Swagger Grabs the fields from a serializer_class variable.
I really recommend you change the format to Class-Based Views.
Something using mixins or generic class.
Your view could be like
class ProjectView(mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = ProjectSerializer
queryset = Project.objects.all()
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
More on Mixins and Generic Views

How to filter through the list/retrieve function on Model ViewSet

Anyone know How can I implement a search filter on list/retrieve function to a viewset?
I'm trying to use DRF Searh-filter on ViewSet but it's not working (it doesn't return the object filtered).
What I want to return is something like --> /store/1/locker/1/controller?controller={name_controller}
view.py
class ControllerViewSet(viewsets.ModelViewSet):
serializer_class = ControllerSerializer
queryset = Controller.objects.all()
filter_backends = [filters.SearchFilter]
search_fields = ['controller']
def list(self, request, store_pk=None, locker_pk=None):
queryset = Controller.objects.filter(locker__store=store_pk, locker=locker_pk)
serializer = ControllerSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None, store_pk=None, locker_pk=None):
queryset = Controller.objects.filter(pk=pk, locker=locker_pk, locker__store=store_pk)
locker = get_object_or_404(queryset, pk=pk)
serializer = ControllerSerializer(locker, context={'request': request})
return Response(serializer.data)
You set a filter backend, but then you overrode the code that would have called it (list(), retrieve()).
Seems like the only reason you overrode those methods is to filter the queryset by store and locker pks. This can be done in one place to affect all requests and without breaking existing code.
def get_queryset(self):
locker_pk = self.kwargs["locker_pk"] # named parameters in url appear in self.kwargs
store_pk = self.kwargs["store_pk"]
return super().get_queryset().filter(locker=locker_pk, locker__store=store_pk)
And that's it. DRF will call this method to get queryset, filter it as configured, and serialize the data with your serializer automatically. No need to manually implement what's already provided.
Note: ensure your pks are valid with regex. You don't want your app to crash if someone requests /store/x/locker/y/controller, right? The regex should be \d+ for any integer.

how do i skip TokenAuthentication class for only get requests django restframework

I want to have TokenAuthentication class for post request. But for get request I dont want this authentication class. How can I achieve this?
class EmailViewSet(viewsets.ModelViewSet):
queryset = Email.objects.all()
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = EmailSerializer
def create(self, request, *args, **kwargs):
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
What exactly happens here is that TokenAuthentication class get applied to all type of requests. But I want this class only for POST requests not for GET request.
I think you can override the get_authenticators function:
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
if self.request.method == "POST":
return [TokenAuthentication()]
return super(EmailViewSet, self).get_authenticators()
And, remove the authentication_classes from the view.

Return the current user with Django Rest Framework

I am currently developing an API using Django.
However, I would like to create a view that returns the current User with the following endpoint: /users/current/.
To do so, I created a list view and filtered the queryset on the user that made the request. That works, but the result is a list, not a single object. Combined with pagination, the result looks way too complicated and inconsistent compared to other endpoints.
I also tried to create a detail view and filtering the queryset, but DRF complains that I provided no pk or slug.
With something like this you're probably best off breaking out of the generic views and writing the view yourself.
#api_view(['GET'])
def current_user(request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
You could also do the same thing using a class based view like so...
class CurrentUserView(APIView):
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Of course, there's also no requirement that you use a serializer, you could equally well just pull out the fields you need from the user instance.
#api_view(['GET'])
def current_user(request):
user = request.user
return Response({
'username': user.username,
'email': user.email,
...
})
The best way is to use the power of viewsets.ModelViewSet like so:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_object(self):
pk = self.kwargs.get('pk')
if pk == "current":
return self.request.user
return super().get_object()
viewsets.ModelViewSet is a combination of mixins.CreateModelMixin + mixins.RetrieveModelMixin + mixins.UpdateModelMixin + mixins.DestroyModelMixin + mixins.ListModelMixin + viewsets.GenericViewSet. If you need just list all or get particular user including currently authenticated you need just replace it like this
class UserViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
# ...
Instead of using full power of ModelViewSet you can use mixins. There is RetrieveModelMixin used to retrieve single object just like it is mentioned here - http://www.django-rest-framework.org/api-guide/viewsets/#example_3
class UserViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserSerializer
def get_object(self):
return self.request.user
If you need also update your model, just add UpdateModelMixin.
If you must use the generic view set for some reason, you could do something like this,
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def get_object(self):
return self.request.user
def list(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
retrieve method is called when the client requests a single instance using an identifier like a primary key /users/10 would trigger the retrieve method normally. Retrieve itself calls get_object. If you want the view to always return the current used then you could modify get_object and force list method to return a single item instead of a list by calling and returning self.retrieve inside it.
I used a ModelViewSet like this:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') == 'current' and request.user:
kwargs['pk'] = request.user.pk
return super().dispatch(request, *args, **kwargs)
Use this way to get logged in user data in django rest framework
class LoggedInUserView(APIView):
def get(self, request):
serializer = UserSerializer(self.request.user)
return Response(serializer.data)
Add the api in urls.py file.
path('logged_in_user', LoggedInUserView.as_view())

Categories