multiple deletion in django rest framework - django

I am working upon User model. How to delete multiple users by getting id of users in views?
I have tried with this in views but does'nt works:
class UserDeleteView(APIView):
permission_classes = (IsAdminUser,)
def get_object(self,id):
try:
return User.objects.get(id=id)
except User.DoesNotExist:
raise Http404
def delete(self,request,id,format=None):
userData=self.get_object(id=id)
if userData:
for usr in userData:
usr.delete()

Try Passing the list of IDs to the view and filtering the objects having those IDs and then deleting them.
Say you want to delete 3 users, having IDs 1,2,3
This will work like this
class UserDeleteView(APIView):
permission_classes = (permissions.IsAdminUser,)
def post(self, request):
user_ids = request.data.get('user_ids') # [1,2,3] List of ID's
users = User.objects.filter(id__in=user_ids)
users.delete()
return Response({'message': 'Users deleted successfully'})

Related

Django Rest Framework custom permission not working

I want users to have access only to the records that belong to them, not to any other users' records so
I've created the following view:
class AddressViewSet(viewsets.ModelViewSet):
authentication_classes = (TokenAuthentication,)
permission_classes = [IsAuthenticated, IsOwner]
queryset = Address.objects.all()
def retrieve(self, request, pk):
address = self.address_service.get_by_id(pk)
serializer = AddressSerializer(address)
return Response(serializer.data, status=status.HTTP_200_OK)
I want only the owner of the records to have access to all the methods in this view ie retrieve, list, etc (I'll implement the remaining methods later) so I created the following permissions.py file in my core app:
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print('here in has_object_permission...')
return obj.user == request.user
this wasn't working, so after going through stackoverflow answers I found this one Django Rest Framework owner permissions where it indicates that has_permission method must be implemented. But as you can see in that answer, it's trying to get the id from the view.kwargs but my view.kwargs contains only the pk and not the user. How can I fix this? Do I need to implicitly pass the user id in the request url? that doesn't sound right.
Here's the test I'm using to verify a user cannot access other user's records:
def test_when_a_user_tries_to_access_another_users_address_then_an_error_is_returned(self):
user2 = UserFactory.create()
addresses = AddressFactory.create_batch(3, user=user2)
address_ids = [address.id for address in addresses]
random_address_id = random.choice(address_ids)
url = reverse(self.ADDRESSES_DETAIL_URL, args=(random_address_id,))
res = self.client.get(url, format='json')
print(res.data)
Currently just using the test to check the data returned, will implement the assertions later on.
Edit
So I added has_permission method to IsOwner:
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
if I put a print statement here it gets printed, but doesn't seem to be hitting the has_object_permission method, none of the prints I added there are being displayed
This answer was the right one for me.
It says:
The has_object_permission is not called for list views. The
documentation says the following:
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you
require object-level filtering of list views, you'll need to filter
the queryset separately. See the filtering documentation for more
details.
Link to documentation
Note: The instance-level has_object_permission method will only be called if the view-level has_permission checks have already passed.
You need to write the has_permission too in order to make your custom permission works.
Here is the official docs and mentioned it. It should works after you add in has_permission.
As mentioned in the docs, permissions are checked on self.get_object method call.
def get_object(self):
obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
self.check_object_permissions(self.request, obj)
return obj
Which basically is all retrieve method does in ModelViewSet
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
Whatever it is you do in self.address_service.get_by_id(pk) should either be moved to self.get_object or call self.check_object_permissions(self.request, obj) in retrieve method.
In the basic scenario this is all you need. There's no need to overwrite retrieve method.
class AddressViewSet(viewsets.ModelViewSet):
serializer_class = AddressSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = [IsAuthenticated, IsOwner]
queryset = Address.objects.all()

Allow GET request for only certain Group of Users Django Rest Framework

I am just getting started with Django Rest framework and want to create a feature such that it allows superuser to create a message in admin.py and allow it only to be seen by a certain group(s) ie. "HR","Managers","Interns" etc.
in other words, only a user belonging to "HR" group will be allowed to get data from view assigned to "HR" group by admin. I would like to have only one view that appropriately gives permission.
Something like
#views.py
class message_view(APIView):
def get(request):
user = request.user
group = get_user_group(user) #fetches user's respective group
try:
#if message assigned to 'group' then return API response
except:
#otherwise 401 Error
I need some guidance with this.
I propose you to define a permission in your app (<your-app>/permissions.py) as below:
class HasGroupMemberPermission(permissions.BasePermission):
message = 'Your custom message...'
methods_list = ['GET', ]
def has_permission(self, request, view):
if request.method not in methods_list:
return True
group_name ='put_your_desired_group_name_here'
if request.user.groups.filter(name__exact=group_name).exists():
return False
return True
Then, import the above permission in your views module (<your-app>/views.py) as well as the serializer of the Message model (let's assume MessageSerializer).
from rest_framework.generics import RetrieveAPIView
from .permissions import HasGroupMemberPermission
from .serializers import MessageSerializer
class MessageView(RetrieveAPIView):
# use indicative authentication class
authentication_classes = (BasicAuthentication, )
permission_classes = (HasGroupMemberPermission, )
serializer_class = MessageSerializer
lookup_url_kwarg = 'message_id'
def get_object(self):
""" Do your query in the db """
qs = Message.objects.get(pk=self.kwargs['message_id'])
return qs
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
401 will be returned if user is not authenticated. 403 will be returned if the logged in user is not member of the desired group. 200 will be returned if logged in user is member of the group and the message_id exists.

In Django REST Framework, how to filter list based on user?

I have two models, User and Book. Users own books and can only be seen by their owners.
NOTE: the book model is handled in a separate database, so I can't use a foreign key on Book pointing to User. Not sure if this matters.
If I'm authenticated, and send a GET /books request, I want only the books owned by the user to be shown. If I'm not authenticated, I should get a 403 error.
Where should I implement this logic?
I could do it in the View, with something like this:
class BookView(APIView):
"""
Get books
"""
permission_classes = (IsAuthenticated, IsBookOwner,)
queryset = Book.objects.all()
serializer_class = BookSerializer
def post(self, request):
# create a book
def get(self, request):
books = Book.objects.filter(owner_id=request.user.owner_id)
serializer = self.serializer_class(books, many=True)
return Response(serializer.data)
class IsBookOwner(permissions.BasePermission):
"""
Object-level permission to only allow seeing his own books
"""
def has_object_permission(self, request, view, obj):
# obj here is a Book instance
return obj.owner_id == request.user.owner_id
Is this the correct way to do it? Also, is the IsBookOwner permission doing anything here?
User model dont have owner_id field. You should change request.user.owner_id to request.user.id
For get request you dont need IsBookOwner Permission. You already check owner in your queryset. if you need to check book owner entire view, it is okay.
books = Book.objects.filter(owner_id=request.user.owner_id)

has_object_permission not called

I looked through similar questions on the same topic and I think I am following all the rules specified for has_object_permission.
This is what I have in my settings.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
'users.permissions.CanAccessData', # this is my custom class
],
...
}
This is my permission class
class CanAccessData(permissions.BasePermission):
message = 'You do not have permission to perform this action.'
def has_permission(self, request, view):
print "has_permission`"
return True
def has_object_permission(self, request, view, obj):
print "has_object_permission"
return False
Here is my view structure:
class CompleteList(generics.ListCreateAPIView):
permission_classes = (CanAccessData,)
serializer_class = SomeSerializer
model = Some
filter_backends = (filters.OrderingFilter, filters.SearchFilter)
ordering_fields = (tuple of Some fields)
search_fields = ordering_fields
ordering = ('-create_date')
Still, has_object_permission is not getting called, has_permission gets called though.
The has_object_permission is not called for list views. The documentation says the following:
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.
I ran into the same problem. The has_object_permission function is never called when listing objects.
Even if the following may not be the most efficient solution, you can override the list method in your view as follows, which is how I solved it for me:
from typing import List
import rest_framework.permissions as drf_permissions
def list(self, request, *args, **kwargs):
# base query set
queryset: QuerySet = self.filter_queryset(self.get_queryset())
# check object permissions for each object individually
valid_pks: List[int] = [] # # storage for keys of valid objects
permissions: List[drf_permissions.BasePermission] = self.get_permissions()
for obj in queryset:
for permission in permissions:
if permission.has_object_permission(request, self, obj):
valid_pks.append(obj.pk)
# remove not valid objects from the queryset
queryset = queryset.filter(pk__in=valid_pks)
# ... business as usual (original code)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This basically is the original implementation except that it prefetches the relevant objects and checks the permission individually.
However, this may be DRY in the sense that you don't have to override the get_queryset() method to somehow reinvent your has_object_permission logic. But also it is slow, since it fetches objects twice. You could improve that situation by working with the already prefetched objects instead of filtering the queryset, though.
has_object_permission method will only be called if the view-level has_permission checks have already passed.
look this real-world example
olympia project on github (https://github.com/mozilla/addons-server/blob/master/src/olympia/ratings/permissions.py)
class CanDeleteRatingPermission(BasePermission):
"""A DRF permission class wrapping user_can_delete_rating()."""
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return user_can_delete_rating(request, obj)
when use this permission class, if has_permission is passed then has_object_permission ( user_can_delete_rating() function ) is called.

Django class based views, passing parameters

I've the following class based view in django rest,
class UserRoom(views.APIView):
def add_user_to_persistent_room(self, request):
try:
user = User.objects.get(id=int(request.data['user_id']))
club = Club.objects.get(id=int(request.data['club_id']))
location = Location.objects.get(id=int(request.data['location_id']))
name = location.city + '-' + club.name
room, created = PersistentRoom.objects.get_or_create(name=name,
defaults={'club': club, 'location': location})
room.users.add(user)
room.save()
return Response(PersistentRoomSerializer(room).data, status=status.HTTP_201_CREATED)
except User.DoesNotExist:
return Response("{Error: Either User or Club does not exist}", status=status.HTTP_404_NOT_FOUND)
def find_all_rooms_for_user(self, request, **kwargs):
try:
user = User.objects.get(id=int(kwargs.get('user_id')))
persistent_rooms = user.persistentroom_set.all()
floating_rooms = user.floatingroom_set.all()
rooms = [PersistentRoomSerializer(persistent_room).data for persistent_room in persistent_rooms]
for floating_room in floating_rooms:
rooms.append(FloatingRoomSerializer(floating_room).data)
return Response(rooms, status=status.HTTP_200_OK)
except User.DoesNotExist:
return Response("{Error: User does not exist}", status=status.HTTP_404_NOT_FOUND)
This is my urls.py
urlpatterns = [
url(r'^rooms/persistent/(?P<user_id>[\w.-]+)/(?P<club_id>[\w.-]+)/(?P<location_id>[\w.-]+)/$',
UserRoom.add_user_to_persistent_room(),
name='add_user_to_persistent_room'),
url(r'^rooms/all/(?P<user_id>[\w.-]+)/$', UserRoom.find_all_rooms_for_user(), name='find_all_rooms')
]
When I run this I get the following error,
TypeError: add_user_to_persistent_room() missing 2 required positional arguments: 'self' and 'request'
I understand the reason for this error clearly, my question is how do I pass the request object in the urls.py?
I think you used Class Based Views the wrong way. Your method must be named get or post. For example:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User
class ListUsers(APIView):
"""
View to list all users in the system.
* Requires token authentication.
* Only admin users are able to access this view.
"""
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
def get(self, request, format=None):
"""
Return a list of all users.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
More info here: http://www.django-rest-framework.org/api-guide/views/#class-based-views