How to write permissions in a viewset with conditional statements in DRF? - python

I have a viewset written in DRF:
class MyViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = models.MyClass.objects.all()
def get_serializer_class(self):
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
return self.serializer_class
else:
return OtherSerializer
def perform_create(self, serializer):
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
serializer.save()
else:
employee = models.Employee.objects.get(user=self.request.user)
serializer.save(employee=employee)
Here, I am using the following statements in both get_serializer_class and perform_create which looks like a repetitive code:
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
Is there any way to write it once and then use it as a permission_class somehow?

Create a Custom Permission class
https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions
from rest_framework.permissions import BasePermission, SAFE_METHODS
class CustomPermission(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
return True
return False
in Views.py
class MyViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = models.MyClass.objects.all()
permission_classes = (CustomPermission,)

Related

Django REST how to not apply default permission for get request

I don't want to apply my permission_classes for get request. I tried #permission_classes([AllowAny]) but didn't work.
Here is my my code:
class BlogViewSet(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
pagination_class = BlogPagination
lookup_field = 'blog_slug'
permission_classes = [IsOwnerOrReadOnly & IsAuthorGroup]
#permission_classes([AllowAny])
def list(self, request):
if request.method == "GET":
blog = Blog.objects.all().order_by("id")
serializer = BlogSerializer(blog, many=True)
return Response(serializer.data)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
I also tried this and getting local variable 'permission_classes' referenced before assignment for post put patch and delete request
class BlogViewSet(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
pagination_class = BlogPagination
lookup_field = 'blog_slug'
permission_classes = [IsOwnerOrReadOnly & IsAuthorGroup]
def get_permissions(self):
if self.action == "list":
permission_classes = [
AllowAny,
]
return [permission() for permission in permission_classes]
you can simply pass a permission class like this.
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly, AllowAny, IsAdminUser
class BlogViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly,IsAuthenticated ]
queryset = Blog.objects.all()
serializer_class = BlogSerializer
pagination_class = BlogPagination
lookup_field = 'blog_slug'
And you can write your own permission classes to pass it. and user will access this view only if they will have all permissions.
Hope this will help you :)

How can I access the serializer in extra actions?

I have the following two classes and I want two merge EventInvitationCreateView into EventInvitationViewSet.
However, I am struggling to bring perform_create into create_invitation as I still need to access serializer. Do you have any input on how to achieve that?
class EventInvitationCreateView(CreateAPIView):
serializer_class = InvitationSerializer
permission_classes = [RetoolPermission]
queryset = Invitation.objects.none()
def perform_create(self, serializer):
email = serializer.validated_data["email"]
event = self.request.event
invitation_exists = Invitation.objects.filter(
email__iexact=email, event=event
).exists()
if invitation_exists:
raise ValidationError("Email already exists")
serializer.save(event=event)
class EventInvitationViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet
):
permission_classes = [RetoolPermission]
serializer_class = InvitationSerializer
filter_backends = [filters.SearchFilter]
search_fields = ["email"]
queryset = Invitation.objects.none()
def get_queryset(self) -> QuerySet:
return self.request.event.invitations.all()
#action(detail=True, methods=["post"])
def create_invitation(self, request, pk=None):
[...perform_create]
the action is still a standard view's method, so you can use self.get_serializer()
for instance
#action(detail=True, methods=["post"])
def create_invitation(self, request, pk=None):
s = self.get_serializer(data=request.data)
....
If you need to use a "action specific serializer" you can specify the serializer class in the decorator
#action(detail=True, methods=["post"], serializer_class=MyOtherSerializer)
def create_invitation(self, request, pk=None):
s = self.get_serializer(data=request.data) # <-- instance of MyOtherSerializer
....

How to get url to action method from ModelViewSet(Django Rest Framework) with reverse function?

I have this ModelViewSet class:
class DriveInvoiceViewSet(viewsets.ModelViewSet):
filter_fields = ('location_id', 'logical_deleted')
permission_classes = (UserCanManageFacilityPermission,)
pagination_class = None
def get_serializer_class(self):
...
def get_queryset(self):
...
#action(detail=False, methods=['GET'])
def get_subtotals_by_unit(self, request):
invoices_list = self.filter_queryset(self.get_queryset())
grouped_invoices = get_subtotals_by_unit(invoices_list)
return Response(grouped_invoices)
How can I get the URL from reverse function to test the get_subtotals_by_unit action?
The ViewSet registred by the router router.register('drive_invoices', DriveInvoiceViewSet, base_name='drive_invoices')
Change the action decorator slightly as below,
class DriveInvoiceViewSet(viewsets.ModelViewSet):
# other code
#action(detail=False, methods=['GET'], url_path="/some/path/", url_name="some-view-name")
def get_subtotals_by_unit(self, request):
invoices_list = self.filter_queryset(self.get_queryset())
grouped_invoices = get_subtotals_by_unit(invoices_list)
return Response(grouped_invoices)
Thus, DRF will create a url pattern with a view name with a syntax as <router_base_name>-<action_view_name>
Thus, the view name in your case will be, drive_invoices-some-view-name

How to create permission for specific rules in Django Rest Framework?

I want to arrange permission like that each user can edit his own profile. Just super user can edit all profile. What I need to add permissions.py ? Thank you.
views.py
class UserViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
authentication_classes = (JSONWebTokenAuthentication, )
permissions.py
class IsOwnerOrReadOnly(BasePermission):
message = '!!'
my_safe_method = ['GET', 'PUT']
def has_permission(self, request, view):
if request.method in self.my_safe_method:
return True
return False
def has_object_permission(self, request, view, obj):
# member .0 Membership.objects.get(user=request.user)
# member.is_active
if request.method in SAFE_METHODS:
return True
return obj.user == request.user
Write your own permission
class IsObjectOwner(BasePermission):
message = "You must be the owner of this object."
my_safe_methods = ['GET', 'PUT', 'PATCH', 'DELETE']
def has_permission(self, request, view):
if request.method in self.my_safe_methods:
return True
return False
def has_object_permission(self, request, view, obj):
if request.user.is_superuser:
return obj
else:
return obj == request.user
and then in the view add it in permission_classes
class UserDetailView(RetrieveUpdateDestroyAPIView):
permission_classes = [IsObjectOwner, permissions.IsAuthenticated]

How to return custom JSON in Django REST Framework

I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret

Categories