Problom in Django REST Framework IsAdminUser permission class - django-rest-framework

This permission return True for staff_user's, what is problem ?
class IsAdminUser(BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_staff)

you can use built in IsAdmin permission from rest_framework permissions
example:
from rest_framework.permissions import IsAdminUser
if you are using generics views, an example of how to use this permission is add a permission_classes where you define the queryset or serializer_class
serializer_class = ModelSerializer
permission_classes = [IsAdminUser]
or if you are using functional api views, u can first import
from rest_framework.decorators import permission_classes as perm
and then before defining the function add this
#api_view(['GET']
#perm([IsAdminUser])
def functionalApiView(request):
...

Related

Is it safe to use caching on get_object in django class-based view

I use some permission checks inside overridden check_permissions. I get the instance I will work with using get_object. I know that after this the get_object method will be called again - during retrieve or destroy methods. So I use caching (#lru_cache). See full example below.
from functools import lru_cache
from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import RetrieveDestroyAPIView
class UserView(RetrieveDestroyAPIView):
permission_classes = [ProjectAdminOnly]
serializer_class = UserSerializer
#lru_cache
def get_object(self, *args, **kwargs):
return UserService.get(self.kwargs['user_id'])
def check_permissions(self, request):
super().check_permissions(request)
user = self.get_object()
# Only within the same project
if self.request.user.project != user.project:
raise PermissionDenied()
This code passes my tests successfully, but I wonder is there any unsafe situation in which the caching will do the bad service for me?

How to send PUT request to ModelViewSet without passing a primary key in the url?

I am particularly interested in using ModelViewSet
for solving the challenge of updating the logged in user's profile. I am using the following definition:
from rest_framework import viewsets
class ProfileRetrieveUpdate(viewsets.ModelViewSet):
serializer_class = UserProfileSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return UserProfile.objects.filter(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
By overriding get_queryset, I am able to get the expected behavior, i.e. when I access the endpoints (say profile/), I get the profile of the logged in user. However, if I have to update the profile, I can only access PUT by going to profile/6/. Is there a way I can get ModelViewSet to expose PUT at the same endpoint i.e. profile/?
You can register ModelViewSet HTTP method under any path you want.
path(
"profile",
ProfileRetrieveUpdate.as_view(
{"put": "partial_update", "get": "retrieve"}
),
name="profile-retrieve-update",
)
You will have to adjust other things as well as you don't provide pk.
Instead of overriding get_queryset method you need to adjust get_object for your needs.
from rest_framework import viewsets
from rest_framework.generics import get_object_or_404
class ProfileRetrieveUpdate(viewsets.ModelViewSet):
serializer_class = UserProfileSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
queryset = UserProfile.objects.all()
def get_object(self):
obj = get_object_or_404(self.queryset, user=self.request.user)
self.check_object_permissions(self.request, obj)
return obj
def perform_update(self, serializer):
serializer.save(user=self.request.user)
Now if you PUT profile/ it should work as expected.

django-rest-framework-simplejwt disable refresh

Is there a way to disable refresh token?
Take away refresh field from the response.
Thank you #alamshafi2263. I think you give the perfect direction.
Don't know why, I still git the refresh item from server response, not Django Shell. (also see the debug variables in photo).
So I just simply data.pop('refresh', None) it out, then problem solved.
Thank you for your time and code.
The Easy Way
Write a view of your own extending the TokenObtainPairView and override the post method.
# in your views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
serializer.validated_data.pop('refresh', None)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
# in your urls.py
urlpatterns = [
path('api/token/', MyTokenView.as_view()),
]
The Complicated but Nicer Way
You need to create a serializer extending TokenObtainSerializer and then define a Custom View as above. This time put your new serializer as the serializer_class of this view and forget about the post method.
# in your serializers.py
from rest_framework_simplejwt.serializers import TokenObtainSerializer
from rest_framework_simplejwt.tokens import RefreshToken
class MyTokenObtainSerializer(TokenObtainSerializer):
#classmethod
def get_token(cls, user):
return RefreshToken.for_user(user)
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['access'] = str(refresh.access_token)
return data
# in your views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import MyTokenObtainSerializer
class MyTokenView(TokenObtainPairView):
serializer_class = MyTokenObtainSerializer
# in your urls.py
urlpatterns = [
path('api/token/', MyTokenView.as_view()),
]
You can parse the user.tokens() dictionary
user.tokens()['access']
user.tokens()['refresh]
And pass them to "tokens" key while in Response

Set 2 permmissions for 2 different actions in ListCreateAPIView in django-rest-framework

I am developing an end-point where users can get the categories and only admin can create them. I am using Django and DjangoRestFramework. More specifically I am using ListCreateAPIView.
Here is my code.
class TagView(ListCreateAPIView):
serializer_class = TagSerializer
queryset = Tag.objects.all()
permission_classes = [IsAdminUser, ]
My Task: I need to set two permission for list and create, they are AllowAny and IsAdminUser.
Any ideas or suggestions and answers are welcome. Thanks beforehand.
I recommend using rest_condition library. In your case, you can code like this:
...
from rest_condition import And, Or
from rest_framework.permissions import BasePermission
class IsPostMethod(BasePermission):
def has_permission(self, request, view):
return request.method.upper() == 'POST'
class IsSafeMethod(BasePermission):
def has_permission(self, request, view):
return request.method.upper() in ('OPTIONS', 'HEAD', 'GET')
class TagView(ListCreateAPIView):
serializer_class = TagSerializer
queryset = Tag.objects.all()
permission_classes = [
Or(
And(IsPostMethod, IsAdminUser),
And(IsSafeMethod, AllowAny),
)
]
Use method_decorator in combination with permission_required on top of your API class, like this:
#method_decorator(permission_required('permission_name'), name='method_name')
class TagView ...
I would suggest using custom permissions, It worked pretty well, and clean.
from rest_framework.permissions import SAFE_METHODS, BasePermission
class IsAdminOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
return request.user.is_superuser

User specific Serializers in Django Rest Framework

I am creating Serializers in my DRF and so far it's working good the problem is that it is showing data of all the users
serializers.py
from rest_framework import serializers
from .models import Quiz
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Quiz
fields = ('foo', 'bar')
How do I make my Serializers User Specific such that it only returns the data of the user who is using the app?
Views.py
class TodoView(viewsets.ModelViewSet):
serializer_class = TodoSerializer
queryset = Quiz.objects.all()
User specific filtering has nothing to do with serializers. Serializers are used to convert complex python objects to/from native python datatypes that can be rendered in JSON/XML etc. It is convenient to do your filtering in your view. Here is an example by using mixins:
# views.py
from .models import Quiz
from .serializers import TodoSerializer
from rest_framework import mixins, viewsets
from rest_framework.response import Response
class TodoListViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
queryset = models.Quiz.objects.all()
serializer_class = TodoSerializer
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# filter your todos here
queryset = queryset.filter(user=request.user)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

Resources