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?
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)
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!
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)
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!
Just override the get_object()
eg.
def get_object(self):
return self.request.user
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
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.
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)
Related
I'm trying to filter lists according to:
the user can work with all of their lists
the user can use safe methods on public lists
I have this code:
In views.py:
class LinkListViewSet(viewsets.ModelViewSet,
generics.ListAPIView,
generics.RetrieveAPIView):
queryset = LinkList.objects.all()
serializer_class = LinkListSerializer
permission_classes = [IsOwnerOrPublic]
In permissions.py:
class IsOwnerOrPublic(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return obj.owner == request.user or (
obj.public and (request.method in SAFE_METHODS))
The problem is, I believe the view just skips checking the permission classes and returns all lists, and I am not sure why, or how to fix it.
It will only check the has_object_permission for requests that work with an object, so for example the RetrieveAPIView, not the ListAPIView.
You should filter for the latter, so we can make a custom IsOwnerOrPublicFilterBackend filter backend:
from django.db.models import Q
from rest_framework import filters
class IsOwnerOrPublicFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(Q(owner=request.user) | Q(public=True))
and then use that filter as filter_backend in the ModelViewSet:
class LinkListViewSet(viewsets.ModelViewSet):
queryset = LinkList.objects.all()
serializer_class = LinkListSerializer
filter_backends = [IsOwnerOrPublicFilterBackend]
permission_classes = [IsOwnerOrPublic]
I am using Django APIView to include all my CRUD operation in a single api endpoint. But later on I had to use filtering logic based on the query parameters that have been passed. Hence I found it difficult to include it in a get api of APIView and made a separate api using generic view, ListAPiview.
Here is the view:
class LeadsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk=None, *args, **kwargs):
id = pk
if id is not None:
abc = Lead.objects.get(id=id)
serializer = LeadSerializer(abc)
return serializer.data
def post(self,request,*args,**kwargs):
abc = LeadSerializer(data=request.data,many=True)
if abc.is_valid():
abc.save()
return Response(abc.data, status=status.HTTP_201_CREATED)
return Response(abc._errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request,pk, *args, **kwargs):
Now, I have to use filter class and also some custom filtering logic, I need to use get_queryset. Hence I have to create another api just for get method which I dont want.
class LeadAPIView(ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
queryset = Lead.objects.all().order_by('-date_created')
serializer_class = LeadSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
pagination_class = CustomPagination
# filterset_fields = [ 'email','first_name','last_name','phone']
filterset_class = LeadsFilter
def get_queryset(self):
source = self.request.GET.get("source", None) #
lead_status = self.request.GET.get("lead_status", None)
if source is not None:
source_values = source.split(",")
if lead_status is not None:
lead_status_values= lead_status.split(",")
return Lead.objects.filter(source__in=source_values,lead_status__in=lead_status_values)
else:
return Lead.objects.filter(source__in=source_values)
elif lead_status is not None:
lead_status_values = lead_status.split(",")
if source is not None:
source_values = source.split(",")
return Lead.objects.filter(lead_status__in=lead_status_values,source__in=source_values)
else:
return Lead.objects.filter(lead_status__in=lead_status_values)
return Lead.objects.all()
My question is, can I use get_queryset in the APIView instead of making another api?? Also, if I can use it, I assume I cant import filterset_class = LeadsFilter and also pagination? What will be the best approach??
My urls:
path('leads', LeadAPIView.as_view(), name='leads'),
path('lead', LeadsView.as_view(), name='leads-create'),
path('lead/<int:pk>', LeadsView.as_view()),
APIView stands for MVT framework. There are 2 types of cases.
If you want to return response to your django templates, you use views.
In cases of returning json, xml (in short response) objects, you use viewset terminology. Viewsets supports filter-class, pagination-class, serialization, queryset, (custom mixins and many more).
p.s. in viewset if you wanted to overwrite default queryset, you define get_queryset method. Views don't support this. Also please check for #action decorators in django.
I am trying to set up an API endpoint that returns a singular object.
Right now I have:
class ShoppingCartViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = ShoppingCartSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_paginated_response(self, data):
return Response(data)
def get_queryset(self):
return ShoppingCart.objects.filter(user=self.request.user)
Which uses the ListModelMixin and a filter to return one item, becasue each user has 1 shopping cart.
The issue is the filter function returns a queryset, but I only want a single item.
I attempted to use the RetrieveModelMixin but that doesn't run on the endpoint that I want. Retrieve runs on .../api/shopping-cart/id but I want to retrieve on .../api/shopping-cart because the filtering is done via the person who is logged in.
Any solutions?
I'm not sure if you have tried this but it should be worthwhile (although I'm not sure if it'll work)
def get_queryset(self):
return ShoppingCart.objects.filter(user=self.request.user)[:1]
Why not use simple api view instead of viewset class?
from django.http import Http404
from rest_framework import status, views
class ShoppingCartAPIView(views.APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_object(self):
# use .get if you are really sure it can only be one shopping cart per user
try:
return ShoppingCart.objects.get(user=self.request.user)
except ShoppingCart.DoesNotExist:
raise Http404()
def get(self, request):
obj = self.get_object()
data = ShoppingCartSerializer().to_representation(obj)
return Response(data, status=status.HTTP_200_OK)
You can even create logic for other http methods like post, delete.
Then add the url pattern api/shopping-cart as normal url (in urls.py), not router ones.
The way I ended up solving it was still using the ListModelMixin, as I needed to benefits of the viewset.
I overwrote the list() method with:
class ShoppingCartViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = ShoppingCartSerializer
# authentication_classes = (TokenAuthentication,)
# permission_classes = (IsAuthenticated,)
def get_paginated_response(self, data):
return Response(data)
def list(self, request, *args, **kwargs):
instance = ShoppingCart.objects.get(user=self.request.user)
serializer = self.get_serializer(instance)
return Response(serializer.data)
which returns me a singular item on the root url .../api/shopping-cart without having to pass parameters because it filters based on user.
I'm trying to wire up an API endpoint to allow admins to disable a user via a simple, body-less PATCH command. However, for some reason my View's get_queryset() always returns a 404:
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from .serializers import UserSerializer
# Disable a User
class DisableCompanyUserView(UpdateAPIView):
model = User
serializer_class = UserSerializer
lookup_url_kwarg = 'user_id'
def get_queryset(self):
print(str(self.kwargs)) # returns {'user_id': '12'}
return get_object_or_404(User, pk=self.kwargs[self.lookup_url_kwarg])
---snip---
I thought it might have been a type mismatch (i.e. self.kwargs['user_id'] was returning a string instead of an int) but I tried looking up the User by entering in 12 to the above return (as an integer, obviously) and still got back a 404. I also ripped out the shortcut function call, did the lookup manually, and still got back a 404:
try:
return User.objects.get(pk=self.kwargs[self.lookup_url_kwarg])
# return User.objects.get(pk=12) # also returns a 404
except User.DoesNotExist:
raise Http404()
I'm looking at the raw auth_user table right now and there's a record in there with an id of 12, so I know that that User exists. What am I doing wrong?
And if you try looking it up with
User.objects.get(id=theId)
Does that return any object?
pk is only a short form of id__iexact
EDIT
Have you tried manually doing
User.objects.filter(pk=yourID)[0]
This should get you results. Otherwise, try
User.objects.all()
And look if you can find the user you are looking for
tgdn helped me realize that I was doing something really dumb: I was trying to filter down to a specific user in get_queryset() when I should have been returning all users.
I was able to get things working like this:
# Update a User
class DisableCompanyUserView(UpdateAPIView):
model = User
serializer_class = UserSerializer
lookup_url_kwarg = 'user_id'
def get_queryset(self):
return User.objects.all()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Which I simplified down to this:
# Update a User
class DisableCompanyUserView(UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_url_kwarg = 'user_id'
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
I'm using the Django rest framework. I need to get the user on views.py but I not able to.
from app.models import SmsToSend
from app.serializers import SmsToSendSerializer
from rest_framework import generics
from app.permissions import IsOwner
from rest_framework import permissions
class SmsToSendList(generics.ListCreateAPIView):
queryset = SmsToSend.objects.all()
serializer_class = SmsToSendSerializer
permission_classes = (IsOwner, permissions.IsAuthenticated)
def pre_save(self, obj):
obj.owner = self.request.user
How can I call request.user here?
Best Regards,
You are very close. I think a lot of people spend time trying to figure this out. Define request as a parameter in your view method and you will have access to it like this.
def pre_save(self, request, obj):
obj.owner = request.user
You can get your queries like this.
request.REQUEST.get('<query>')
In a serializer method it is slightly different, you can get data like this.
request = self.context['request']
user = request.user
query = request.GET['<query>']
Hope this is helpful!
class SmsToSendList(generics.ListCreateAPIView):
#queryset = SmsToSend.objects.all()
serializer_class = SmsToSendSerializer
permission_classes = (IsOwner, permissions.IsAuthenticated)
def get_queryset(self):
user = self.request.user
return SmsToSend.objects.filter(owner=user)
def pre_save(self, obj):
obj.owner = self.request.user
Instead of the queryset I had to use get_queryset method
def get_queryset(self, *args, **kwargs):
user_id = self.request.user.id