Set 2 permmissions for 2 different actions in ListCreateAPIView in django-rest-framework - 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

Related

Problom in Django REST Framework IsAdminUser permission class

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):
...

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

Record exists or not in DB? Django Api generic Views

I want to write a class based API view to check if the record exists in DB or not then return True else False by using rest_framework. How could I create CBV to check it? Please help me with this context.
here is my serializer class
class EmployeeSerializer(ModelSerializer):
class Meta:
model = Employee
fields = '__all__'
here is my url
path('employee/<name>/<code>/',views.EmployeeExist.as_view(),name = 'employee_exits')
Here is how you can create simple view:
from rest_framework import status, response
from rest_framework import generics
class EmployeeExistView(generics.GenericAPIView):
serializer_class = None
def get(self, request, *args, **kwargs):
employee = Employee.objects.filter(id=kwargs.get('id'))
if employee.exists():
return response.Response(status=status.HTTP_200_OK)
return response.Response(status=status.HTTP_404_NOT_FOUND)

Django rest framework generic view for multiple models

Is there a way of creating a generic view to be used by several different models? I have many models in my project and do not want to have to create a view and serializer for each of them.
Assume this is a simple solution but I have spent hours googling with no results.
Within the Django REST framework API guide Generic views Examples the following code snippet is shown:
url(r'^/users/', ListCreateAPIView.as_view(model=User), name='user-list')
Which suggests this is possible but the example does not seem to be complete (or my understanding is not complete).
This is my attempt:
url.py
url(r'^foomodel/', views.GenericViewSet.as_view(model = Foomodel) ),
views.py
class GenericViewSet(generics.ListAPIView):
model = User # this is over ridden by the url
queryset = model.objects.all()
serializer_class = BaseSerializer
ordering_fields = '__all__'
serializers.py
class BaseSerializer(serializers.ModelSerializer):
class Meta:
model = None
And of cause this fails as the serializer does not like model = None or any variation that I can think of.
Any suggestions as to how this should be addressed?
A solution would be overriding the get_serializer_class method of the ViewSet.
The model field in the view will be fine as you override it in the url, as you said, what I would do is build the serializer class on the fly.
class GenericViewSet(generics.ListAPIView):
queryset = model.objects.all()
def get_serializer_class(self):
class BaseSerializer(serializer.ModelSerializer):
class Meta:
model = self.model
return BaseSerializer
Hope this helps!
To recap the intention is to create a reusable view that can be used for many models without having to repeat a heap of code. This leaves very little code in Django to create a backed API for many Django models. I am using this for ember.js
urls.py
from myapp import models as mymodels
url(r'^cs/(?P<strmodel>[A-Za-z]+)/(?P<id>[0-9]+)/', views.GenericViewSet.as_view({'get': 'retrieve', 'post':'update', 'delete':'destroy' }, application = mymodels ) ),
url(r'^cs/(?P<strmodel>[A-Za-z]+)/', views.GenericViewSet.as_view({'get': 'list','post': 'create' }, application = mymodels ) ),
Worth noting that I have several apps and there is a different set of URL's to access them. The model being accessed is passed as strmodel
views.py
class GenericViewSet(viewsets.ModelViewSet):
def __init__(self, application):
self.application = application
def initialize_request(self, request, *args, **kwargs):
self.model = getattr(self.application, self.kwargs['strmodel'] )
request = super(viewsets.ModelViewSet, self).initialize_request(request, *args, **kwargs)
return request
strmodel = None
application = None
model = User
lookup_field = 'id'
def get_queryset(self):
return self.model.objects.all()
def get_serializer_class(self):
class BaseSerializer(serializers.ModelSerializer):
class Meta:
model = self.model
return BaseSerializer
Hope this helps :)

Resources