Django : class based view with multiple permissions - python

I am trying to create a class based view with different permissions per function. I have already created my permissions in a file that I import :
utils.py
from rest_framework.permissions import BasePermission
from rest_framework.authentication import TokenAuthentication
from rest_framework.views import APIView
class IsOwner(BasePermission):
"""
Check if the user who made the request is owner.
Use like that : permission_classes = [IsOwner]
"""
def has_object_permission(self, request, view, obj):
# if request.method in permissions.SAFE_METHODS:
# return True
return obj.user == request.user
class IsAdmin(BasePermission):
"""
Check if the user who made the request is admin.
Use like that : permission_classes = [IsAdmin]
"""
def has_permission(self, request, view):
return request.user.is_admin
class BaseView(APIView):
"""
Check if a user is authenticated
"""
authentication_classes = [
TokenAuthentication,
]
class AdminOrOwnerView(APIView):
"""
Check if a user is admin or owner
"""
authentication_classes = ( IsOwner | IsAdmin,)
I want to create a class with different method. The GET method would allow admins to have a view of all users. The POST method would allow any user to connect.
I have already created the serializer for each of these methods but I cannot assign different permissions per method.
This is my class :
class Users(APIView):
def get(self, request):
"""Only for admin"""
try:
user = Users.objects.all()
except User.DoesNotExist():
return HttpResponse(status=404)
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
"""For everyone"""
serializer = RegistrationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.error)
How I can apply my permissions on each of the methods separately ?
Thank you in advance for your help

You just need to create a permission class like this:
class CustomPermissionClass(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET':
# logic for GET method
elif request.method == 'POST'
# logic for POST metod
# default logic
And add it to your view:
class Users(APIView):
permission_classes = [CustomPermissionClass]

Related

How to log in and check, if user is staff, can access to url or use a views class?

At my question, my problem is
How to log in and check, if user is staff, can access to url or use a views class ?
I want to user login on website (Not admin-console) and user is staff (in admin-console) can post.
And someone just have a account create on web (they dont have permission staff) thay just can login.
Thank you so much !
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title','content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
If you want only the staff user can access the view then you can use the UserPassesTestMixin and LoginRequiredMixin like this:
class StaffRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
return self.request.user.is_staff
Now in your PostCreateView you can implement like this:
class PostCreateView(StaffRequiredMixin, CreateView):
......
You can use user.is_staff to check this.
Ref: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff
Create a file named: permissions.py
And then you can do something like below:
from rest_framework.permissions import BasePermission
class PermissionMixin:
'''
Initializes user permissions
'''
def __init__(self):
self._actions = ()
self._user = False
self._admin = False
def _initialize_permissions(self, request):
self._actions = settings.ALLOWED_ACTIONS
self._user = request.user and request.user.is_active
self._admin = self._user and request.user.is_superuser
class AdminsOnlyPermission(BasePermission, PermissionMixin):
'''
Determines operations that can be performed by admins only
'''
def has_permission(self, request, view):
self._initialize_permissions(request)
if view.action in self._actions:
return self._admin
else:
return False
If you are using DRF, you can use IsAdminUser:
from rest_framework import mixins, viewsets
from rest_framework.permissions import IsAdminUser
from my_serializers import SomeSerializer
class SomeView(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = SomeSerializer
queryset = SomeSerializer.Meta.model.objects.all()
permission_classes = [IsAdminUser]
It checks that there is a user logged in and that is has is_staff in true.

Validate user on update request in Django REST framework

I want to have an API where a user can update his own listings. Currently any authenticated user can update any listing which I found using Postman. I want to validate the user so that the API returns an error as a response if the user is not trying to update his own listing. Here is my code:
# serializers.py
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = Listing
fields = '__all__'
# api.py
class ListingViewSet(ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly]
serializer_class = ListingSerializer
def get_queryset(self):
return Listing.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# urls.py
router = routers.DefaultRouter()
router.register('api/listings', ListingViewSet, 'listings')
urlpatterns = router.urls
You just need to overwrite the perform_update function:
def perform_update(self, serializer):
obj = self.get_object()
if self.request.user != obj.created_by: # Or how ever you validate
raise PermissionDenied('User is not allowed to modify listing')
serializer.save()
You will need:
from django.core.exceptions import PermissionDenied
You can limit the object for all method change the main queryset. In this case if an inapropiate user try to access an invalid object the api return 404.
def get_queryset(self):
return Listing.objects.filter(owner=self.request.user)

Django-filters does not work with the Viewset

I have been trying to use django-filters but the objects are not getting filtered. Also, the permission is not working for the partial_update views
I have a Viewset which has the basic actions like - list(), retrieve(), destroy(), partial_update() and few other actions, and trying to apply filter for the same.
After some research I found that since I am creating the queryset via filters I will have to override the get_queryset() method. However, that also doesn't seem to be working. Does the filter works only with ModelViewSet or ListApiView?
ViewSet -
class PostViewSet(viewsets.ViewSet):
"""
The Endpoint to list, retrieve, create and delete Posts.
"""
filter_backends = (DjangoFilterBackend, )
# filterset_class = PostFilter
filter_fields = ('pet_age', 'pet_gender', 'breed')
def get_permissions(self):
if self.action == 'partial_update' or self.action == 'update':
permission_classes = [IsPostAuthor, ]
elif self.action == 'create' or self.action == 'destroy':
permission_classes = [IsAuthenticated, ]
else:
permission_classes = [AllowAny, ]
return[permission() for permission in permission_classes]
def get_queryset(self):
return Post.objects.active() # This is implemented via custom Manager
def list(self, request, *args, **kwargs):
"""
Method for Post listing. It can be accessed by anyone.
"""
serializer = PostListSerializer(self.get_queryset(), many=True, context={"request": request})
return Response(serializer.data)
# REST CODE TRUNCATED
Permission -
class IsPostAuthor(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.user.is_authenticated:
if view.action in ['partial_update', 'update']:
return obj.user.id == request.user.id
return False
return False
PostFilter -
class PostFilter(filters.FilterSet):
class Meta:
model = Post
fields = ('pet_age', 'pet_gender', 'breed', )
Manager -
class PostManager(models.Manager):
def active(self):
return self.filter(post_status='Active')
Any help will be highly appreciated.
Okay, So finally found the solution from DRF Docs. The issue was that in case of normal ViewSet you have to override the method filter_queryset() and return the appropriate queryset accordingly. Then use the queryset under filter_queryset as mentioned by Aman -
serializer = PostListSerializer(self.filter_queryset(self.get_queryset()), many=True, context={"request": request})
Below is the code for reference for those who are still facing issues -
filter_queryset -
def filter_queryset(self, queryset):
filter_backends = (DjangoFilterBackend, )
# Other condition for different filter backend goes here
for backend in list(filter_backends):
queryset = backend().filter_queryset(self.request, queryset, view=self)
return queryset
You have overwrite the list method, so it's not working to work call the filter_queryet method.
def list(self, request, *args, **kwargs):
"""
Method for Post listing. It can be accessed by anyone.
"""
serializer = PostListSerializer(self.filter_queryset(self.get_queryset()), many=True, context= .
{"request": request})
return Response(serializer.data)

Where can I to write my logic in APIView?

In the CreateAPIView I can override the create method to add my logic:
class OpenstackAccountCreateAPIView(CreateAPIView):
"""
create openstack account
"""
serializer_class = OpenstackAccountCreateSerializer
def create(self, request, *args, **kwargs):
# put my logic here
......
But if I have a APIView, where can I to write my logic?
class OpenstackAccountLoginAPIView(APIView):
serializer_class = OpenstackAccountLoginSerializer
# where can I put my login logic?
My OpenstackAccountLoginSerializer in serializers.py:
class OpenstackAccountLoginSerializer(Serializer):
password = serializers.CharField()
You can overide the method of the verb you want. Probably to create an account you'll want POST. Like this:
class OpenstackAccountLoginAPIView(APIView):
serializer_class = OpenstackAccountLoginSerializer
def post(self, request, format=None):
# Logic goes here, defining resp with whatever you want to respond.
return Response(resp)
Than you can call it with the POST verb, same as when using create with CreateAPIView as in the documentation here.
class OpenstackAccountLoginAPIView(APIView):
serializer_class = OpenstackAccountLoginSerializer
def get(self, request):
"""
Return a list of all users.
"""
def post(self, request):
"""
Create users.
"""

Retrieving current user inside Serializer Method in Django Rest API

I have a serializer for user profiles in Django Rest:
class UserProfileSerializer(serializers.ModelSerializer):
......................
......................
status = serializers.SerializerMethodField()
def get_status(self, obj):
user = self.context['request'].user
if obj.user.userprofile in user.followed_userprofiles_set.all():
return "following"
else:
return "notfollowing"
class Meta:
model = UserProfile
fields = (...., 'status',...)
And I have two views that use this serializer:
class Followers(APIView):
def get(self, request, format=None):
#user who follow current user
users = request.user.userprofile.followers.all()
userprofiles= UserProfile.objects.filter(user__in=users)
serializer = UserProfileSerializer(userprofiles, many=True)
return Response(serializer.data)
and
class Friends(mixins.ListModelMixin, generics.GenericAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def get_queryset(self):
.................
.................
return queryset
One view is using APIView and other is using genericAPIView. When i request from genericAPIView, its working properly. But when i request from APIView, its giving me key error. How to retrieve the current user inside serializer method when APIView is used?
Since you are manually instantiating the UserProfileSerializer in your APIView class without passing the context, KeyError exception gets raised.
You should pass the request in context parameter when instantiating the UserProfileSerializer in your APIView.
class Followers(APIView):
def get(self, request, format=None):
#user who follow current user
users = request.user.userprofile.followers.all()
userprofiles= UserProfile.objects.filter(user__in=users)
context = {'request':request} # prepare serializer context
serializer = UserProfileSerializer(userprofiles, many=True, context=context) # pass context
return Response(serializer.data)

Categories

Resources