DRF: Creating mixin using different serializers & objects - django

I have two APIView classes with almost similar behaviour (and code). Different are only serializers and models object (Favorite & FavoriteSerializer for first class and PurchaseList and PurchaseListSerializer for second). As far as I understand it is a perfect usecase for mixin, according to DRF docs ([DRF: Creating custom mixins] 1https://www.django-rest-framework.org/api-guide/generic-views/).
Unfortunately, I'm struggling with how to define mixin without the definition of serializer & obj in it.
class FavoriteViewSet(APIView):
def get(self, request, recipe_id):
user = request.user.id
data = {"user": user, "recipe": recipe_id}
serializer = FavoriteSerializer(
data=data,
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
def delete(self, request, recipe_id):
user = request.user
favorite_recipe = get_object_or_404(
Favorite, user=user, recipe__id=recipe_id
)
favorite_recipe.delete()
return Response(
"Рецепт удален из избранного", status.HTTP_204_NO_CONTENT
)
class PurchaseListView(APIView):
def get(self, request, recipe_id):
user = request.user.id
data = {"user": user, "recipe": recipe_id}
serializer = PurchaseListSerializer(
data=data, context={"request": request}
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
def delete(self, request, recipe_id):
user = request.user
purchase_list_recipe = get_object_or_404(
PurchaseList, user=user, recipe__id=recipe_id
)
purchase_list_recipe.delete()
return Response(
"Рецепт удален из списка покупок", status.HTTP_204_NO_CONTENT
)

You can create your own mixin like this:
class CustomMixin:
serializer_class = None
model_class = None
def get(self, request, recipe_id):
user = request.user.id
data = {"user": user, "recipe": recipe_id}
serializer = self.serializer_class(
data=data,
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
def delete(self, request, recipe_id):
user = request.user
obj = get_object_or_404(
self.model_class, user=user, recipe__id=recipe_id
)
obj.delete()
return Response(
"Рецепт удален из избранного", status.HTTP_204_NO_CONTENT
)
class FavoriteViewSet(CustomMixin, APIView):
serializer_class = FavoriteSerializer
model_class = Favorite
class PurchaseListView(CustomMixin, APIView):
serializer_class = PurchaseListSerializer
model_class = PurchaseList
Or use the GenericApiView with the bulti-in mixins
https://www.django-rest-framework.org/api-guide/generic-views/#genericapiview

Related

How to Convert Api view class to CreateModelMixin

class Add_Product(APIView):
def post(self,request,*args, **kwargs):
user=request.user
if user.is_authenticated:
data=request.data
date=datetime.now().date()
slug=user.username+"-"f'{int(time())}'
print(data)
serializer=ProductSerializer(data=data,many=True)
if serializer.is_valid():
print(serializer.data)
serializer.save(user=request.user,slug=slug)
return Response("Your product is added")
return Response(serializer.errors)
return Response("Login First")
I want to convert this to CreateModelMixin But i don't know how to pass values like request.user and slug in create method.
class Product_List(GenericAPIView,CreateModelMixin):
queryset = Product.objects.all()
serializer_class = ProductSerializer
def post(self,request,*args, **kwargs):
return self.create(request,*args,**kwargs)
You can pass the user to the serializer through its context then override its create method:
# View
class Product_List(GenericAPIView,CreateModelMixin):
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = (IsAuthenticated,)
def get_serializer_context(self):
return {'user': self.request.user}
# Serializer
class ProductSerializer(serializers.ModelSerializer):
[...]
def create(self, validated_data):
user = self.context['user']
slug = f'{user.username}-{int(time())}'
return Product.objects.create(
user=user,
slug=slug,
**validated_data
)

Django rest framework custom permission for ViewSet

this is my modelViewSet
class UserViewSet(viewsets.ModelViewSet):
def list(self, request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
pass
def retrieve(self, request, pk):
user = get_object_or_404(User, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
def get_permissions(self):
if self.action == "list":
permission_classes = [
IsAdminUser,
]
elif self.action == "create":
permission_classes = [AllowAny]
else:
permission_classes = [AccountOwnerPermission]
return [permission() for permission in permission_classes]
and this is the custom permission class
class AccountOwnerPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print(object)
print(request.user)
return obj == request.user
i access this view from another user and it show me user retrieve, and that 2 prints on
AccountOwnerPermission won't run. can someone tell me what is wrong with what i did and why has_object_permission wont run.
i change has_object_permission to has_permission and it works, but i dont have access to obj on the other hand
From the docs:
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
So you'll need to call check_object_permissions in your retrieve to be able to trigger has_object_permission:
def retrieve(self, request, pk):
user = get_object_or_404(User, pk=pk)
self.check_object_permissions(request, user) # Add this line
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)

Return response statement is not returning any response

I am creating a logout view to logout from my django restframework using simplejwt. There is no direct way to logout so only blacklisting the refresh token is the workarround.
Those print statements works expectedly so it does blacklist the tokens but the return statement doesn't return anything, why is that and how may I return a Response?I am guessing the save function doesnt return anything, is it ture?
class LogoutSerializer(serializers.Serializer):
refresh = serializers.CharField()
def validate(self, attrs):
self.token = attrs['refresh']
return attrs
def save(self, **kwargs):
try:
RefreshToken(self.token).blacklist()
print('done')
return Response({'msg':'token has been blacklisted'})
except TokenError:
print('not done')
return Response({'msg':'token is expired or blacklisted'})
views.py
class LogoutAPIView(APIView):
serializer_class = LogoutSerializer
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = self.serializer_class(data = request.data)
serializer.is_valid(raise_exception = True)
serializer.save()
return Response(status = status.HTTP_204_NO_CONTENT)
def post(self, request):
serializer = self.serializer_class(data = request.data)
serializer.is_valid(raise_exception = True)
serializer.save()
return Response(status = status.HTTP_204_NO_CONTENT) # this is the response that you're actually returning to the client.
If you want to return the responses in the serializer you can do:
class LogoutSerializer(serializers.Serializer):
refresh = serializers.CharField()
def validate(self, attrs):
self.token = attrs['refresh']
return attrs
def save(self, **kwargs):
RefreshToken(self.token).blacklist()
then:
def post(self, request):
serializer = self.serializer_class(data = request.data)
serializer.is_valid(raise_exception = True)
try:
serializer.save()
return Response({'msg':'token has been blacklisted'})
except TokenError:
return Response({'msg':'token is expired or blacklisted'})

I want to force change password on first login Django Rest

I have a custom User model with field is_resetpwd as a boolean field with default value True
I want that to change to False when the password is changed, following is my password change view
class ChangePasswordView(APIView):
"""
An endpoint for changing password.
"""
serializer_class = ChangePasswordSerializer
model = User
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
obj = self.request.user
# print("obj", obj)
return obj
def post(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# Check old password
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
response = {
'message': 'Password updated successfully',
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
what should I add in this to change my boolean value
Just before save() add this
self.object.is_resetpwd = False
and then save the objects just like before
The updated code will look like this
"""
An endpoint for changing passwords.
"""
serializer_class = ChangePasswordSerializer
model = User
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
obj = self.request.user
# print("obj", obj)
return obj
def post(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# Check old password
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.is_resetpwd = False
self.object.save()
response = {
'message': 'Password updated successfully',
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Django REST framework: restricting user access for objects

I'm trying to build a REST API for books:
/api/book
/api/book/{book_id}
A user should have access to his books only. The way I'm doing this now is by filtering the result using username i.e Book.objects.all().filter(owner=request.user)
views.py
class Book_List(APIView):
permission_classes=(permissions.IsAuthenticated)
def get(self, request, format=None):
**books= Book.objects.all().filter(owner=request.user)**
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = BookSerializer(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 perform_create(self, serializer):
serializer.save(owner=self.request.user)
#/book/{pk}
class Book_Detail(APIView):
permission_classes = (permissions.IsAuthenticated)
def get_object(self, pk, request):
try:
return Book.objects.get(pk=pk, owner=request.user)
except Playlist.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
book = self.get_object(pk, request)
serializer = BookSerializer(playlist, context={'request': request})
return Response(serializer.data)
def put(self, request, pk, format=None):
book= self.get_object(pk)
serializer = BookSerializer(playlist, data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
book= self.get_object(pk)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
serializers.py
class BookSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Book
fields = ('url', 'owner','title', 'created_date', 'shared_with', 'tracks')
class UserSerializer(serializers.HyperlinkedModelSerializer):
books = serializers.HyperlinkedRelatedField(many=True,view_name='playlist-detail', read_only=True)
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = User
fields = ('url', 'username', 'owner', 'books')
But is this the correct way?
Does Django Rest Framework provide any in-built solution for this?
Does the solution lie in permissions? If yes, then how do we set it for all objects created by a user (I understand the for getting a particular object we can put a permission check like obj.user==request.user). Am I right?
You could use the ModelViewset, which contains all the logic for the typical CRUD:
class BooksViewSet(ModelViewset):
serializer_class = BookSerializer
permission_classes=[permissions.IsAuthenticated, ]
def get_queryset(self):
return Books.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.data.owner = self.request.user
super(BooksViewSet, self).perform_create(serializer)