django-rest-framework multiple serializer for 1 model? - django

Suppose you want to give out
{field1, field2, field3} on detail request.
{field1, field2} on list request.
{field1} on some other simpler list request.
I have seen examples using get_serializer_class with self.action which can handle detail vs list scenario. (https://stackoverflow.com/a/22755648/433570)
Should I define two viewsets and two url endpoints?
Or is there a better approach here?
I guess this could be applied to tastypie as well. (two resources?)

I haven't tested it myself but I think you can do it overriding the methods you need.
According to the documentation (Marking extra actions for routing) you could do:
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
Or if you need custom methods:
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from myapp.serializers import UserSerializer, PasswordSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset that provides the standard actions
"""
queryset = User.objects.all()
serializer_class = UserSerializer
#detail_route(methods=['post'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.DATA)
if serializer.is_valid():
user.set_password(serializer.data['password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
#list_route()
def recent_users(self, request):
recent_users = User.objects.all().order('-last_login')
page = self.paginate_queryset(recent_users)
serializer = self.get_pagination_serializer(page)
return Response(serializer.data)

Related

How to remove put method in RetrieveUpdateAPIView from drf-spectacular API documentation?

I have the following view:
class PersonalInfos(generics.RetrieveUpdateAPIView):
serializer_class = ClientSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
:return: A QuerySet Object
"""
return Client.objects.get(user=self.request.user)
def get(self, *args):
"""
:param args: Handled by rest_framework views.dispatch
:return: JSON object containing User Personal Data
"""
queryset = self.get_queryset()
serializer = ClientSerializer(queryset)
return Response(data=serializer.data)
def patch(self, request):
"""
:param request: request object is sent by the client
:return: Json response with the data sent of the body
"""
client = self.get_queryset()
serializer = ClientSerializer(client, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=200)
return Response(data="Unexpected Parameters", status=400)
Everything works fine in the view, but the problem is that I am using drf-spectacular and it is showing me a PUT method in the documentation that we won't be needing in the API. My questions is, how can I customize drf-spectacular to not include a PUT method in the documentation?
You may use the #extend_schema decorator to exclude one or more methods from the schema generated, as shown below.
#extend_schema(methods=['PUT'], exclude=True)
I solved this using RetrieveAPIView instead of UpdateRetrieveAPIView and I have extended it with to include a PATCH method. RetrieveAPIView will handle a PATCH method perfectly and would not show automatically an UPDATE request in the API documentation. Here is the new code:
class PersonalInfos(generics.RetrieveAPIView):
serializer_class = ClientSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
:return: A QuerySet Object
"""
return Client.objects.get(user=self.request.user)
def get(self, *args):
"""
:param args: Handled by rest_framework views.dispatch
:return: JSON object containing User Personal Data
"""
queryset = self.get_queryset()
serializer = ClientSerializer(queryset)
return Response(data=serializer.data)
def patch(self, request):
"""
:param request: request object is sent by the client
:return: Json response with the data sent of the body
"""
client = self.get_queryset()
serializer = ClientSerializer(client, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=200)
return Response(data="Unexpected Parameters", status=400)
this should exclude the put method in drf-yasg
class MyView(generics.RetrieveUpdateAPIView):
#swagger_auto_schema(auto_schema=None)
def put(self, request, *args, **kwargs):
return
source: https://drf-yasg.readthedocs.io/en/stable/custom_spec.html#excluding-endpoints

Facing Issue when giving restricted permissions to particular group in Django

I am trying to make a REST API using Django REST Framework. In the section of Authentication and Authorization I created 2 groups namely Manager (which has permissions to perform CRUD on database) and Staff (which has only view permission). Using serializers I am displaying the data in json format. But I am still able to perform CRUD operation using Staff account. How can I fix that?
This is my models.py
from django.db import models
class Users(models.Model):
Aadhar_Number = models.IntegerField(unique=True, primary_key=True, default=0)
Is_Active = models.BooleanField()
street = models.CharField(max_length=100,null=True)
city = models.CharField(max_length=10,null=True)
state = models.CharField(max_length=10,null=True)
Postal_Code = models.IntegerField(null=True)
def __str__(self):
return f"{self.Aadhar_Number}-{self.Full_Name}"
This is my api.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import *
class UserList(APIView):
def get(self, request):
model = Users.objects.all()
serializer = UsersSerializer(model, many=True)
return Response(serializer.data)
def post(self, request):
serializer = UsersSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class UserDetail(APIView):
def get_user(self, employee_id):
try:
model = Users.objects.get(id=employee_id)
return model
except Users.DoesNotExist:
return
def get(self, request, employee_id):
if not self.get_user(employee_id):
return Response(f'User with {employee_id} is Not Found in database', status=status.HTTP_404_NOT_FOUND)
serializer = UsersSerializer(self.get_user(employee_id))
return Response(serializer.data)
def put(self, request, employee_id):
serializer = UsersSerializer(self.get_user(employee_id), data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, employee_id):
if not self.get_user(employee_id):
return Response(f'User with {employee_id} is Not Found in database', status=status.HTTP_404_NOT_FOUND)
model = self.get_user(employee_id)
model.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
I have added 2 groups using a Superuser and assigned them different permissions.
Permissions using Superuser
REST API View
When using DRF, you should go for its own permissions system.
Create a permissions.py inside your app.
# permissions.py
from rest_framework import permissions
class IsManagerOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
# GET, OPTIONS, HEAD are considered safe methods.
return True
# for other methods such as POST/PUT/DELETE,
# check if the user has access with group name
return request.user.groups.filter(name='manager').exists()
And in your views, since you're using APIView it's pretty straightforward.
# views.py
from .permissions import IsManagerOrReadOnly
class NameOfYourView(APIView):
permission_classes = [IsManagerOrReadOnly]
# rest of your code...

DRF Custom Permission is not firing

I wrote a custom permission class for a drf project to protect my view:
views.py
class Employee(APIView):
permission_classes = [BelongsToClient]
serializer_class = EmployeeSerializer
def get(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
serializer = EmployeeSerializer(employee, many=False)
return Response(serializer.data)
def delete(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
employee.Employees_deleted = True
employee.save()
return Response(status=status.HTTP_200_OK)
My permission class:
permission.py
from rest_framework import permissions
class BelongsToClient(permissions.BasePermission):
message= "You are only authorized to view objects of your client"
"""
Object-level permission to only see objects of the authenticated users client
"""
def has_object_permission(self, request, view, obj):
if obj.Mandant == request.user.Mandant:
return True
else:
return False
Unfortunatly this permission class isn't blocking my view even when it should. I dont know why. Did I miss something?
You need to call check_object_permissions method before response for APIView
class Employee(APIView):
permission_classes = [BelongsToClient]
serializer_class = EmployeeSerializer
def get(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
serializer = EmployeeSerializer(employee, many=False)
self.check_object_permissions(request, employee)
return Response(serializer.data)
has_object_permission only called when you use the DestroyAPIView or RetrieveAPIView or ViewSet.
Try to use a viewset just like below
from rest_framework import viewsets
class Employee(viewsets.ViewSet):
permission_classes = [BelongsToClient]
serializer_class = EmployeeSerializer
def delete(self, request, pk, format=None):
employee = EmployeeModel.objects.get(pk=pk)
self.check_object_permissions(request, employee)
employee.Employees_deleted = True
employee.save()
return Response(status=status.HTTP_200_OK)
Note: I didn't test it but it should work.

How can I fix django rest framework metaclass conflict

I am a beginner learning django rest framework and I just encounter this error and I can't seem to find a way around it. Here is the permissions.py sample code
from rest_framework import permissions
class UpdateOwnProfile(permissions, BaseException):
"""Allow user to edit their own profile"""
def has_object_permission(self, request, view, obj):
"""Check if user is trying to update their own profile"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.id == request.user.id
Here is also a sample of the views.py sample codes
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from profiles_api import serializers
from profiles_api import models from profiles_api import permissions
class HelloApiView(APIView): """Test Api view""" serializer_class = serializers.HelloSerializer
def get(self, request, format=None):
"""Returns a list of Api features"""
an_apiview = [
'Uses HTTP methods as function (get, post, patch, put, delete)',
'Is similar to a traditional Django view',
'Gives you the most control over your application logic',
'Is mapped manually to URLs',
]
return Response({'message': 'Hello', 'an_apiview': an_apiview})
def post(self, request):
"""Create a hello message with our name"""
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
name = serializer.validated_data.get('name')
message = f'Hello {name}'
return Response({'message': message})
else:
return Response(
serializer.errors,
status = status.HTTP_400_BAD_REQUEST
)
def put(self, request, pk=None):
"""Handling updates of objects"""
return Response({'method': 'PUT'})
def patch(self, request, pk=None):
"""Handle a partial update of an object"""
return Response({'method': 'PATCH'})
def delete(self, request, pk=None):
"""Delete an object"""
return Response({'method': 'DELETE'})
class HelloViewset(viewsets.ViewSet): """Test API Viewset""" serializer_class = serializers.HelloSerializer
def list(self, request):
"""Return a hello message"""
a_viewset = [
'Uses actions (list, create, retrieve, update, partial update'
'Automatically maps to URLs using router'
'provides more functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
def create(self, request):
"""Create a new hello message"""
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
name = serializer.validated_data.get('name')
message = f'Hello {name}!'
return Response({'message': message})
else:
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
def retrieve(self, request, pk=None):
"""Handle getting an object by its ID"""
return Response({'http_method': 'GET'})
def update(self, request, pk=None):
"""Handle updating an object"""
return Response({'http_method': 'PUT'})
def partial_update(self, request, pk=None):
"""Handle updating of an object"""
return Response({'http_method': 'PATCH'})
def destroy(self, request, pk=None):
"""Handle removing an object"""
return Response({'http_method': 'DELETE'})
class UserProfileViewSet(viewsets.ModelViewSet): """Handle creating and updating profiles""" serializer_class = serializers.UserProfileSerializer queryset = models.UserProfile.objects.all() authentication_classes = (TokenAuthentication,) permission_classes = (permissions.UpdateOwnProfile,)
And while running the development server I get this error:
class UpdateOwnProfile(permissions, BaseException): TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
permissions (rest_framework.permissions) is of type module (whose type is type FWIW), but the type of BaseException is type (like all regular classes). So you have a metaclass conflict as expected.
Presumably, you meant to use permissions.BasePermission class from the module:
class UpdateOwnProfile(permissions.BasePermission, BaseException):
...
...
You can also import and refer the class directly:
from rest_framework.permissions import BasePermission
class UpdateOwnProfile(BasePermission, BaseException):
...
...

AllowAny for get method in djangorestframework

I use django rest framework,
example code
class TestApiView(APIView):
def get(self, request):
return Response({'text': 'allow any'})
def post(self, request):
return Response({'text': 'IsAuthenticated'})
how to make method GET accessible to all, and method POST only authorized
thank you in advance
You can use IsAuthenticatedOrReadOnly permission class:
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class TestApiView(APIView):
permission_classes = (IsAuthenticatedOrReadOnly,)
def get(self, request):
return Response({'text': 'allow any'})
def post(self, request):
return Response({'text': 'IsAuthenticated'})