'How can I make a Django REST framework /me/ call?
Suppose I have a ViewSet
:
class ProfileViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows a user's profile to be viewed or edited.
"""
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
...and a HyperlinkedModelSerializer
:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
read_only_fields = ('user',)
I have my urls.py
set up as:
router.register(r'profiles', api.ProfileViewSet, base_name='profile')
This lets me access e.g. /api/profile/1/
fine.
I want to set up a new endpoint on my API (similar to the Facebook API's /me/
call) at /api/profile/me/
to access the current user's profile - how can I do this with Django REST Framework?
Solution 1:[1]
You could create a new method in your view class using the list_route
decorator, like:
class ProfileViewSet(viewsets.ModelViewSet):
@list_route()
def me(self, request, *args, **kwargs):
# assumes the user is authenticated, handle this according your needs
user_id = request.user.id
return self.retrieve(request, user_id)
See the docs on this for more info on @list_route
I hope this helps!
Solution 2:[2]
Using the solution by @Gerard was giving me trouble:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly..
Taking a look at the source code for retrieve() it seems the user_id
is not used (unused *args
)
This solution is working:
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from rest_framework import filters
from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.decorators import list_route
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from ..serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
User = get_user_model()
queryset = User.objects.all()
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('username', 'email', 'usertype')
search_fields = ('username', 'email', 'usertype')
@list_route(permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
User = get_user_model()
self.object = get_object_or_404(User, pk=request.user.id)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
Accessing /api/users/me
replies with the same data as /api/users/1
(when the logged-in user is user with pk=1
)
Solution 3:[3]
You can override the get_queryset method by filtering the queryset by the logged in user, this will return the logged in user's profile in the list view (/api/profile/).
def get_queryset(self):
return Profile.objects.filter(user=self.request.user)
or
def get_queryset(self):
qs = super(ProfileViewSet, self).get_queryset()
return qs.filter(user=self.request.user)
or override the retrieve method like so, this will return the profile of the current user.
def retrieve(self, request, *args, **kwargs):
self.object = get_object_or_404(Profile, user=self.request.user)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
Solution 4:[4]
From Gerard's answer and looking at the error pointed out by delavnog, I developed the following solution:
class ProfileViewSet(viewsets.ModelViewSet):
@list_route(methods=['GET'], permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
self.kwargs.update(pk=request.user.id)
return self.retrieve(request,*args, **kwargs)
Notes:
- ModelViewSet inherits GenericAPIView and the logic to get an object is implemented in there.
- You need to check if the user is authenticated, otherwise request.user will not be available. Use at least
permission_classes=[IsAuthenticated]
. - This solution is for GET but you may apply the same logic for other methods.
- DRY assured!
Solution 5:[5]
Just override the get_object()
eg.
def get_object(self):
return self.request.user
Solution 6:[6]
Just providing a different way. I did it like this:
def get_object(self):
pk = self.kwargs['pk']
if pk == 'me':
return self.request.user
else:
return super().get_object()
This allows other detail_routes in the ViewSet to work like /api/users/me/activate
Solution 7:[7]
I've seen quite a few fragile solutions so I thought I'll respond with something more up-to-date and safer. More importantly you don't need a separate view, since me
simply acts as a redirection.
@action(detail=False, methods=['get', 'patch'])
def me(self, request):
self.kwargs['pk'] = request.user.pk
if request.method == 'GET':
return self.retrieve(request)
elif request.method == 'PATCH':
return self.partial_update(request)
else:
raise Exception('Not implemented')
It's important to not duplicate the behaviour of retrieve
like I've seen in some answers. What if the function retrieve
ever changes? Then you end up with a different behaviour for /me
and /<user pk>
If you only need to handle GET requests, you could also use Django's redirect
. But that will not work with POST or PATCH.
Solution 8:[8]
Considering a OneToOneField relationship between the Profile
and the User
models with related_name='profile'
, I suggest the following as the @list_route
has been deprecated since DRF 3.9
class ProfileViewSet(viewsets.GenericViewSet):
serializer_class = ProfileSerializer
@action(methods=('GET',), detail=False, url_path='me', url_name='me')
def me(self, request, *args, **kwargs):
serializer = self.get_serializer(self.request.user.profile)
return response.Response(serializer.data)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gerard |
Solution 2 | blueFast |
Solution 3 | |
Solution 4 | Community |
Solution 5 | Michael Henry |
Solution 6 | sanfilippopablo |
Solution 7 | |
Solution 8 | Dos |