I have a problem using Django Rest Framework's permission_classes attribute.
The view I want to protect is defined as:
class Test(APIView):
permission_classes = [IsLoggedIn]
def get(self, request):
return Response("aaa")
I have also tried having the class inherit GenericAPIVIew.
permissions.py
from rest_framework import permissions
class IsLoggedIn(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return False
I'm trying to get the permission to at least always refuse the request, but it seems that the has_object_permission method is never getting called.
You have to override the has_permission(...) method instead of has_object_permission(...)
class IsLoggedIn(permissions.BasePermission):
def has_permission(self, request, view):
return False
I found this in drf offical document:
Limitations of object level permissions
For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.
Often when you're using object level permissions you'll also want to filter the queryset appropriately, to ensure that users only have visibility onto instances that they are permitted to view.
Because the get_object() method is not called, object level permissions from the has_object_permission() method are not applied when creating objects. In order to restrict object creation you need to implement the permission check either in your Serializer class or override the perform_create() method of your ViewSet class.
Related
I'm trying to use the following permission class in many apps in the project, the only change needed is the model class that user data is checked from.
Permission class:
class IsAuthorOrForbidden(permissions.BasePermission):
"""
Check if the requesting user the author or not
"""
def __init__(self, modelClass):
self.modelClass = modelClass
def has_permission(self, request, view):
# get the needed model instance or 404 if not available
instance = get_object_or_404(self.modelClass, pk=view.kwargs['pk'])
# Check if the requesting user is the author
if instance.user == request.user:
return True
return False
The permission class in the view class:
class TextNoteGenerateShareKeyAPIView(generics.UpdateAPIView):
"""
Generate a new text note share key
"""
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,
IsAuthorOrForbidden(modelClass=TextNote))
...
When I run the tests, i get this error:
return [permission() for permission in self.permission_classes]
TypeError: 'IsAuthorOrForbidden' object is not callable
Is it possible to do it like this or should I write this permission class in every app in my project?
You can use object level permission:
class IsAuthorOrForbidden(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.user == request.user
and add it to the view like this:
permission_classes = (IsAuthenticated,
IsAuthorOrForbidden)
Explanation on why the error is occurring
To understand the error, you need to see the implementation of get_permissions method in GitHub source code:
#copy pasted from GitHub
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
Here it is making a list of objects from self.permission_classes, meaning permission_classes are suppose to have Class, not objects.
You are adding a IsAuthorOrForbidden object to the permission classes, where you need to add the class. If you want to put a override, then it should be done here:
class TextNoteGenerateShareKeyAPIView(generics.UpdateAPIView):
def get_permissions(self):
return [IsAuthenticated(), IsAuthorOrForbidden(modelClass=TextNote)]
But this is a hackish solution where solution in the original answer is the proper way to implement in DRF.
For those who are using "viewsets" and want to allow only the author to access the content/items you can simply use a mixin like below and use that mixin in whichever viewsets that requires.
# mixin
class FilterByUserMixin:
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
return queryset.filter(user=self.request.user)
# viewsets
class WhateverViewSet(FilterByUserMixin, viewsets.ModelViewSet):
One way to make the OP's code work is to add a __call__ method to the permission class that just returns the object.
class IsAuthorOrForbidden(permissions.BasePermission):
def __call__(self):
return self
This will avoid the TypeError that was being thrown when the permission_classes are being instantiated into objects, by simply returning the object when it is called. This allows for passing arguments to the permission_classes like the OP wanted.
here is my views.py code
class DirectView(mixins.CreateModelMixin):
serializer_class=DirectSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def post(self,request,*args,**kwargs):
return self.create(request,*args,**kwargs)
and my urls.py
path('direct/',DirectView.as_view(),name='direct'),
but whenever i tried to run the server i get an error as
AttributeError: type object 'DirectView' has no attribute 'as_view'
i don't understand what the issue is ?
Your DirectView class must inherit from a View class in Django in order to use as_view.
from django.views.generic import View
class DirectView(mixins.CreateModelMixin, View):
If you're using the rest framework, maybe the inheritance you need here is CreateAPIView or GenericAPIView (with CreateModelMixin) which is the API equivalent of the View class mentioned above.
If we are looking into the source code of mixins.CreateModelMixin, we could see it's inherited from object (builtin type) and hence it's independent of any kind of inheritance other than builtin type.
Apart from that, Mixin classes are special kind of multiple inheritance. You could read more about Mixins here. In short, Mixins provides additional functionality to the class (kind of helper class).
So, What's the solution to this problem?
Solution - 1 : Use CreateAPIView
Since you are trying to extend the functionality of CreateModelMixin, it's highly recomended to use this DRF builtin view as,
from rest_framework import generics
class DirectView(generics.CreateAPIView):
serializer_class = DirectSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Reference
1. What is a mixin, and why are they useful?
2. Python class inherits object
In the DRF source code, there's a get_serializer method. It wasn't inherited from object and it's not a method in the CreateModelMixin class. Where does this method come from?
serializer = self.get_serializer(data=request.data)
Here's the larger chunk of code for context.
from __future__ import unicode_literals
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}
There are a few SO posts that that also use this method. Like this, this, and this. But I still can't figure out where the implementation is.
CreateModelMixin along with all other mixin classes (Eg. ListModelMixin, UpdateModelMixin etc) are defined in rest_framework/mixins.py file.
These mixin classes provide all the basic CRUD operations on a model. You just need to define a serializer_class and queryset in your generic view to perform all these operations. DRF has separated out these common functionality in separate mixin classes so that they can be injected/mixed-in in a view and used as and when required.
In the DRF source code, there's a get_serializer method. It wasn't
inherited from object and it's not a method in the CreateModelMixin
class. Where does this method come from?
In the GenericAPIView, get_serializer method is defined. The combination of different mixin classes along with GenericAPIView class provide us different generic views for different use cases.
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Other generic views then inherit the relevant mixin along with GenericAPIView.
Eg. CreateAPIView inherit the CreateModelMixin along with GenericAPIView to provide create-only endpoints.
# rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
...
It helps if you understand class inheritance (not suggesting you don't, though).
CreateModelMixin will be used by a class based view, for example:
class YourViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
get_serializer is a method available through GenericViewSet (although there are probably other View classes that it's available on, too).
So, basically if you would use CreateModelMixin only get_serializer would not be available. (You probably wouldn't do that anyway). It's only because you inherit from another class besides CreateModelMixin that the get_serializer method is available at the moment that the create method is executed.
I could give a simple example of what's happening, while I am at it. It's just to give a more simple overview of what's happening.
disclaimer: you might want to keep in mind that I am a junior developer, so this may not be the most Pythonic code out there :P.
class MyMixin:
def create(self):
serializer = self.get_serializer()
return serializer
class FakeView:
def get_serializer(self):
return "Sadly I am just a string, but I could've been a serializer."
class MyFakeView(MyMixin, FakeView):
pass
view = MyFakeView()
serializer = view.create()
print(serializer)
executing this would show you:
Sadly I am just a string, but I could've been a serializer.
Where if you would define MyFakeView like below,
class MyFakeView(MyMixin):
pass
you would receive the following error:
AttributeError: 'MyFakeView' object has no attribute 'get_serializer'
You can see the __file__ or __module__ member of the method (if it has them) to learn that. inspect also has getsourcefile and getsourcelines that use data from the function's code object, specifically, <function>.f_code.co_filename and .co_firstline.
For example, this retrieves source information for a method inherited from DictMixin:
>>> c=ConfigParser._Chainmap()
>>> f=c.__len__
>>> dump(f.__code__) # my custom function that dumps attributes,
# see https://github.com/native-api/dump
<...>
co_filename : /usr/lib/python2.7/UserDict.py
co_firstlineno : 179
<...>
# same with inspect
>>> inspect.getsourcefile(f)
'/usr/lib/python2.7/UserDict.py'
>>> inspect.getsourcelines(f)
([' def __len__(self):\n', ' return len(self.keys())\n'], 179)
>>> inspect.getsource(f)
' def __len__(self):\n return len(self.keys())\n'
# same with __file__
>>> sys.modules[f.__module__].__file__
'/usr/lib/python2.7/UserDict.pyc'
I am using class based view and applied permission classes only on POST method using method decorator. Till yesterday it was working but all of sudden it stopped. I am unable to find issue.
class OfferCreateListView(ListCreateAPIView):
serializer_class = OfferSerializer
queryset = Offers.objects.filter(user__isnull=True)
#method_decorator(permission_classes((IsAuthenticated,)))
#method_decorator(authentication_classes((BasicAuthentication, SessionAuthentication, TokenAuthentication,)))
def post(self, request, *args, **kwargs):
return super(OfferCreateListView, self).post(request, *args, **kwargs)
Where i am doing wrong. Is there any setting for this to work??
The permission_classes and authentication_classes decorators are designed for function based views. I haven't followed the rest framework code all the way through, but I'm surprised that it worked until yesterday -- I don't the decorators are intended to be used with class based views.
Instead, set attributes on the class. Since you only want the permission class to be applied for post requests, it sounds like you want IsAuthenticatedOrReadOnly.
class OfferCreateListView(ListCreateAPIView):
permission_classes = (IsAuthenticatedOrReadOnly,)
authentication_classes = (BasicAuthentication, SessionAuthentication, TokenAuthentication,)
serializer_class = OfferSerializer
queryset = Offers.objects.filter(user__isnull=True)
I have a model, Parent which contains a Django ContentType GenericForeignKey relationship to various child models (ChildA, ChildB) in Parent.child
I'm trying to get ListCreateAPIView and other listing views working with this setup. Originally I handled the serialization of the child instance using a SerializerMethodField which looked something like:
class ParentSerializer(serializers.ModelSerializer):
child = serializers.SerializerMethodField('get_child')
(other fields)
def get_child(self, obj):
if obj.content_type == "child_a":
return ChildASerializer(obj.child).data
...
Now I want to take advantage of Django Rest Framework to its full (including deserialization/creation/validation) so I want to avoid my current approach and increase DRYness by doing:
class ParentSerializer(serializers.ModelSerializer):
(code for serializing parent fields except 'child' attribute)
class ChildASerializer(serializers.ModelSerializer):
(code for ChildA fields)
class ParentTypeASerializer(ParentSerializer):
child = ChildASerializer()
If i'm reading the docs right, this means POST/PUT will go through the serializer's process without me having to override the post methods in views and other uglyness. This is important as ChildA,ChildB,ChildC come from plugins and the core Parent/ParentSerializer should be as unaware of them as possible.
My thinking was to override get_serializer() in the view, but when listing many objects, I don't see how I can provide ParentTypeASerializer, ParentTypeBSerializer etc in the view.
def get_serializer(self, instance=None, data=None, files=None, many=False, partial=True):
serializer_class = None
if instance and instance.content_type == "child_a":
serializer_class = ParentTypeASerializer
if instance and instance.content_type == "child_b":
serializer_class = ParentTypeBSerializer
...
# What about many=True ?!
return serializer_class(instance,data=data,files=files,many-many,partial=partial,context=context)
Another idea I had was to write a PolymorphicField class extending WritableField that does the decision. Unsure if this is the simplest approach:
class ParentSerializer(serializers.ModelSerializer):
child = PolymorphicChildSerializerProxy() # Passes through/wraps the right serializer
question: is there any dynamic/runtime/per-object way to provide the right serializer for an Generic/polymorphic nested object in such a way either at the view level or the parent serializer? Ideally something like the second example and an override in the view that works for List/Create/Destroy generics or like the first example except I return a serializer class rather than serialized data?