Override permission and authentication classes in ViewSet list method - django-rest-framework

I am trying to set specific authentication and permission classes on the ListModelMixin of a viewset. I tried the following but it is not working:
def list(self, request):
self.authentication_classes = (apiauth.SessionAuthentication, )
self.permission_classes = (permissions.IsAuthenticated, )
return super(TestViewSet, self).list(request)
Am i doing something wrong ?

#henriquesalvaro's answer does not work for me.
For some reason action is not part of self in get_authenticators(self)
So if self.action == 'list' does not work for me.
I had to write the following code to get the action.
def get_authenticators(self):
authentication_classes = [TokenAuthentication]
action_map = {key.lower(): value for key,
value in self.action_map.items()}
action_name = action_map.get(self.request.method.lower())
if action_name =="list":
return [YourPermission]
return authentication_classes

When your request reaches the list function, it means it has already passed both the authentication and the permission phases.
If you're using the same classes for permission and authentication for all actions on your ViewSet you should define them on your class declaration:
class MyViewSet(viewsets.ViewSet):
authentication_classes = (apiauth.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
If you're trying to use different authentication/permission classes for the list action, you can override the get_authenticators and get_permissions methods:
class MyViewSet(viewsets.ViewSet):
...
def get_authenticators(self):
if self.action == 'list':
# Set up here
return YourAuthenticators
return super().get_authenticators()
def get_permissions(self):
if self.action == 'list':
# Set up here
return YourPermissions
return super().get_permissions()

Related

django rest permission called many times

So I'm building a simple blog app and made custom permission so only owner of post can update or delete the post, so I added print statment to check if it's working but found out it's called 6 times every time calling the view!
here's the code
class PostUpdateDeletePermission(IsAuthenticated):
def has_object_permission(self, request, view, obj):
print("called")
if request.user.is_superuser:
return True
if (request.method == 'PUT' or request.method == 'DELETE') and request.user != obj.user:
return False
return True
class PostView(ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = (PostUpdateDeletePermission,)
def create(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj = serializer.save(user=request.user)
if request.data.get('files_length'):
PostFile.objects.bulk_create([PostFile(img = request.data.get(f"file-{x}"), post=obj) for x in range(request.data.get('files_length'))])
return Response({'posts' : serializer.data}, status=201)
that's how many times called printed when refreshing the drf browsable api
called
called
called
called
called
called

DRF how to use DjangoModelPermissions on function-based views?

I separated by view functions instead of using viewset/queryset. Question is, how can I restrict permissions to my view functions based on user-group permissions?
Sample code:
#api_view(['GET', 'POST'])
#permission_classes([DjangoModelPermissions])
def some_list(request):
"""
List all something, or create a new something.
"""
{...code here...}
Error:
Cannot apply DjangoModelPermissions on a view that does not set .queryset or have a .get_queryset() method.
Ended up making my own custom DjangoModelPermissions without checking queryset to get model but rather creating multiple child class for each specific models.
from rest_framework import permissions
from django.apps import apps
class BaseCustomModelPermissions(permissions.BasePermission):
model_cls = None
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': [],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
authenticated_users_only = True
def get_required_permissions(self, method):
"""
Given a model and an HTTP method, return the list of permission
codes that the user is required to have.
"""
kwargs = {
'app_label': self.model_cls._meta.app_label,
'model_name': self.model_cls._meta.model_name
}
if method not in self.perms_map:
raise exceptions.MethodNotAllowed(method)
return [perm % kwargs for perm in self.perms_map[method]]
def has_permission(self, request, view):
# Workaround to ensure DjangoModelPermissions are not applied
# to the root view when using DefaultRouter.
if getattr(view, '_ignore_model_permissions', False):
return True
if not request.user or (
not request.user.is_authenticated and self.authenticated_users_only):
return False
perms = self.get_required_permissions(request.method)
return request.user.has_perms(perms)
Sample Child class:
class SomeModelPermissions(BaseCustomModelPermissions):
model_cls = apps.get_model('my_app', 'its_Model')

Getting has_object_permission() missing 4 required positional arguments: 'self', 'request', 'view', and 'obj'

I am trying to create a has_object_permission function which is in a permissions.py file in a custom class:
class IsOwnerOrAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print("has object permissions statement")
return obj.owner == request.user
def has_permission(self, request, view):
print("has permissions statement")
return request.user and request.user.is_authenticated
My view set looks like this:
class SchoolViewSet(viewsets.ModelViewSet):
queryset = School.objects.all()
serializer_class = SchoolSerializer
permission_classes = [IsOwnerOrAdmin,
IsOwnerOrAdmin.has_object_permission()]
...
I get the following error when I call the /schools/ endpoint:
TypeError: has_object_permission() missing 4 required positional
arguments: 'self', 'request', 'view', and 'obj'
However, has_permissions works fine and I don't have to explicitly call it in my permission_classes array. It just gets called. How do I pass these arguments? Am I doing this correctly? I tried a few variations of passing self, passing SchoolViewSet, passing obj=queryset etc. Was not sure if that was correct. Could not find exactly what I needed to fix this on the internets.
Update
How do I change the code in order to call has_object_permission just like has_permission gets called automatically?
permission_classes is not defined correctly. It should only include the customised class.
class SchoolViewSet(viewsets.ModelViewSet):
queryset = School.objects.all()
serializer_class = SchoolSerializer
permission_classes = [IsOwnerOrAdmin]

How get request user data in django rest to representation function

I need to access request.user in to_representation function, I tried self.context['request'] but self.context is empty dictionary. Is there anyway to access request.user or any way that I can push this data to this function?
def to_representation(self, obj):
print(self.context)
#output is an empty dictionary {}
UPDATE
class RetrieveView(generics.RetrieveAPIView):
def get(self, request, *args, **kwargs):
uid = kwargs.get('uid')
try:
item = self.model.nodes.get(uid=uid)
except Exception as e:
# error response
serializer = self.serializer_class(item)
return HttpSuccessResponse(SuccessResponse(serializer.data).to_json(), status=status.HTTP_200_OK).send()
class TopicRetrieveView(single_result.RetrieveView):
model = Topic
serializer_class = topic.TopicSerializer
ALL CODES are from django rest framwork Generic views generic.py
serializer_class is attribute we set in class definition or we need to override get_serializer_class function. It will handle in this function:
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
get_serializer_class will used in get_serializer function:
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)
and context will fill by get_serializer_context function.
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
So correct usage is serializer = self.get_serializer(item) because it will use serializer_class for serializing item and fill context with extra information that may be helpful. serializer = self.serializer_class(item) can be used for just serializing item with no more extra information.

'type' object is not iterable with Django Rest Framework and django oauth2-toolkit

Well I am trying to create new access token for the login user on creation with custom authentication class in views.
Serializer.py
class UserCreateSerializer(ModelSerializer):
def create(self, validated_data):
user = User.objects.create_user(validated_data['username'],
validated_data['email'],
validated_data['password'])
return user
class Meta:
model = User
fields = ('username', 'email' ,'password')
views.py
class User_Create_view(CreateAPIView):
serializer_class = UserCreateSerializer
queryset = User.objects.all()
permission_classes = [AllowAny]
authentication_classes = Has_Access_Token
def create(self, request):
serializers =self.serializer_class(data=request.data)
if serializers.is_valid():
# pdb.set_trace()
serializers.save()
# Has_Access_Token.access_token(Has_Access_Token())
return Response(serializers.data)
return Response(status=status.HTTP_202_ACCEPTED))
permission.py
class Has_Access_Token(BaseAuthentication):
def access_token(self):
app = Application.objects.get(name="testing")
tok = generate_token()
pdb.set_trace()
acce_token=AccessToken.objects.get_or_create(
user=User.objects.all().last(),
application=app,
expires=datetime.datetime.now() + datetime.timedelta(days=365),
token=tok)
return acce_token
#method_decorator(access_token)
def authenticate(self):
return request
If I use the Decorator
File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'tuple' object has no attribute 'module'
If I am Not using the Decorator
File "/home/allwin/Desktop/response/env/local/lib/python2.7/site-packages/rest_framework/views.py", line 262, in get_authenticators
return [auth() for auth in self.authentication_classes]
TypeError: 'type' object is not iterable
The Problem I am facing is that when i use the Has_Access_Token fuction implicitly after serializer.save() Access token is generated in admin with respect to user but that's not effective method, so I need to override the custom authentication_class in views.
Could somebody please suggest some ways to tackle this issue or perhaps let me know the decorator correction with the above code.
Thanks in advance.
While setting REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES in settings.py file, the customAuthencationClass must like below:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [ # need to be list not tuple
'CustomAuthentication',
],
}

Resources