I am trying to make sure the user has permission to view the object they are calling. Here is my permissions class:
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to do actions.
"""
message = 'You must be the owner of this object.'
def has_object_permission(self, request, view, obj):
print("CHECK THAT I GOT HERE")
return obj.user == request.user
And here is my ViewSet:
class TopLevelJobViewSet(ModelViewSet):
permission_classes = (IsOwner,)
serializer_class = TopLevelJobSerializer
queryset = TopLevelJob.objects.all()
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
filter_class = TopLevelJobFilter
ordering_fields = '__all__'
Thehas_object_permissions is not being called, anyone visiting the endpoint is able to access all the objects.
Why is this? How do I get has_object_permissions to get called?
This post: Django rest framework ignores has_object_permission talks about it being an issue with not having GenericAPIView. But ModelViewSet has GenericViewSet which has generics.GenericAPIView. Is something overriding this somewhere?
EDIT: My issue was that I was calling list instead of get. How can I only returns objects in list that belong to a user?
This link: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-current-user shows I could implement something like this:
def get_queryset(self):
username = self.kwargs['username']
return Purchase.objects.filter(purchaser__username=username)
This seems to violate DRY if I have to add this to every viewset. Is there a way to turn this into a permissions class that I could always call?
You can implement custom generic filtering [drf-doc]. For example:
class IsOwnerFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.objects.filter(user=request.user)
Then you can add this to your ModelViewSet:
class TopLevelJobViewSet(ModelViewSet):
permission_classes = (IsOwner,)
serializer_class = TopLevelJobSerializer
queryset = TopLevelJob.objects.all()
filter_backends = [IsOwnerFilter, DjangoFilterBackend, RelatedOrderingFilter]
filter_class = TopLevelJobFilter
ordering_fields = '__all__'
Related
I'm trying to filter lists according to:
the user can work with all of their lists
the user can use safe methods on public lists
I have this code:
In views.py:
class LinkListViewSet(viewsets.ModelViewSet,
generics.ListAPIView,
generics.RetrieveAPIView):
queryset = LinkList.objects.all()
serializer_class = LinkListSerializer
permission_classes = [IsOwnerOrPublic]
In permissions.py:
class IsOwnerOrPublic(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return obj.owner == request.user or (
obj.public and (request.method in SAFE_METHODS))
The problem is, I believe the view just skips checking the permission classes and returns all lists, and I am not sure why, or how to fix it.
It will only check the has_object_permission for requests that work with an object, so for example the RetrieveAPIView, not the ListAPIView.
You should filter for the latter, so we can make a custom IsOwnerOrPublicFilterBackend filter backend:
from django.db.models import Q
from rest_framework import filters
class IsOwnerOrPublicFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(Q(owner=request.user) | Q(public=True))
and then use that filter as filter_backend in the ModelViewSet:
class LinkListViewSet(viewsets.ModelViewSet):
queryset = LinkList.objects.all()
serializer_class = LinkListSerializer
filter_backends = [IsOwnerOrPublicFilterBackend]
permission_classes = [IsOwnerOrPublic]
i am learning drf and i little confused
in the urls.py i have
path('todos/<int:pk>', views.TodoRetrieveUpdateDestroy.as_view()),
on the views.py
class TodoRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
serializer_class = TodoSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
user = self.request.user
return Todo.objects.filter(user=user)
by logic i would add to filter like pk = self.kwargs[‘pk’] to send only one element Todo but it works and send only that ‘id=pk’ post without adding additional filter.Can u explain why please and how works RetrieveUpdateDestroy and get_queryset)
RetrieveUpdateDestroyAPIView doesnt execuate method def get_queryset(self): in the first place
It has def get_object(self): method which gets object by lookup_fields and query from query set method returned data again
So to get/update/delte single data,
You have to do like this:
class TodoRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = 'pk'
and your urls should be like this:
path('some-paht/<int:pk>/', TodoRetrieveUpdateDestroy.as_view(), name='some_name')
I have created generic views in django rest framework which allows listing and creation of objects to admin user. But, what I am really trying to achieve is any user staff status should be able to get the objects (use get method) but only super user should be able to create objects (use post method). Here are my generic views.
class StateList(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = [IsAdminUser]
you can use get_permission for that.
class StateList(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = [IsAdminUser]
def get_permissions(self):
if self.request.method == 'POST':
return [permission() for permission in self.permission_classes]
return [AllowAny()]
I have a django project I am working on that needs a users and account model. I am using and integrating Django Rest Framework. I was initially using individual API generic views from DRF. I am thinking of converting the individual generic views into a view set. I was able to do it for the user model. I wan to convert the Account model views to a view set.
My issue is that I have two versions of the same ListAPIView for the profile model. The top View lists all of the accounts in the database and the second one list all the accounts for an idividual user based on the User__Username foreignkey in the Account model.
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = 'username'
class AccountListView(ListAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
class AccountUserListView(ListAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('user', '_id', '_class')
def get_queryset(self):
return self.queryset.filter(user_username=self.kwargs.get('username'))
It says that I can specifically define the properties of a view within the viewset but I want to define two versions of the ListAPIView for the single model. Is there a way to double define the same view in a single viewset.
I basically want to define both of my Account ListAPIViews in the same viewset. How should i go about doing that if it is possible??
you can use the #action decorator to define.
class UserViewSet( mixins.ListModelMixin, viewsets.GenericViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
#acttion(method="get", details=False, serializer_class=AccountSerializer)
def account_list(self, request, *args, **kwargs):
queryset = User.objects.all()
data = self.get_serializer_class().(instance=queryset, many=True).data
return response.Response(data)
I built a Follow model to record the social networking behaviour and would like to simulate the following action. Every authenticated user can follow the others.
class Follow(models.Model):
user = models.ForeignKey(User)
follower = models.ForeignKey(User, related_name="followers")
follow_time = models.DateTimeField(auto_now_add=True, blank=True)
class Meta:
unique_together = ('user', 'follower')
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.follower)
And the FollowSerializer is:
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
field = ('user', 'follower', 'follow_time')
The view that I am using is:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
I am registering it in the urls as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
Every authenticated user can view the following relation, no restricts. But I would like to just allow the authenticated user to add the following relation by himself/herself, which means there should be request.user == follower. How can I do this?
I would like to add the FollowingDelete view to just allow the user to add a following relation by himself/herself.
So I updated the url.py as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
url(r'^api/users/(?P<pk>[0-9]+)/following/(?P<following_id>[0-9]+)/$', views.FollowingDelete.as_view()),
The permission that I am using is:
class IsFollowerOrReadOnly(permissions.BasePermission):
"""
View-level permission to allow the follower to edit the following relation
"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
#Reject any request for an invalid user
return False
return follower == request.user
And the views are:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_class = (IsFollowerOrReadOnly)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_class = (IsAuthenticated, IsFollowerOrReadOnly)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
Now the questions are:
The permission class doesn't work totally.
How to rewrite the DestroyAPIView, should I override the get_queryset function?
Django REST framework provides custom permission handling that allows you to handle complex permissions on the view and object level. In order to do what you are looking for, you are going to have to create a custom permission, but it's surprisingly easy.
Every authenticated user can follow the others.
DRF provides an IsAuthenticated permission that allows you to do this very easily. All you have to do is add it to the permission_classes on the view, or globally through the settings.
from rest_framework import permissions
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
There is another restriction, which is the one that requires the custom permission class.
But I would like to just allow the authenticated user to add the following relation by himself/herself
This requires checking the request method (which I'm assuming is POST) and also the user who is being followed.
Lets start off with the easy check, the request method. Django REST framework provides permission classes that check the request method, such as IsAuthenticatedOrReadOnly, so we can look at the code to see how it is being done. From there it's just a matter of having a check against the request type.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
return False
This code will reject all requests that come in using the POST method, while allowing all others. The second step in creating this permission is doing the user check, so only the follower can add new people that they are following. This requires getting the follower and checking that against request.user.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
# Reject any requests for an invalid user
return False
return follower == request.user
This builds upon the last permission class by getting the user from the url (not allowing it if the user doesn't exist) and checking if they are the current user.
After I modified the typos, the permission class works now:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (IsFollowerOrReadOnly,)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_classes = (IsAuthenticated, IsFollowerOrReadOnly,)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
And I make the FollowingDelete view work successfully by overriding the get_object() function.
def get_object(self, *args, **kwargs):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
try:
return Follow.objects.get(user=user_id, follower=follower_id)
except Follow.DoesNotExist:
raise Http404("No such following relation")