Django rest-framework; show different content to authetificated and anonymous users - django

I try to implement rest_framework for Django 2.
I have a URL that should show diffrent content for authentificated users.
Anonymous users just will get a limited view, authentificated will see everything.
In the documentation I can only methods that will deny everything, but not a if else clause.
Basicly I try something like this:
class StoryViewSet(viewsets.ModelViewSet):
if IsAuthenticated == True:
queryset = Story.objects.all()
else:
queryset = Story.objects.filter(story_is_save=True)
serializer_class = StorySerializer
Obviously IsAuthenticated is not a True/False statement i can query.
Any ideas how I could do this easily?
Thanks

You need to override get_queryset method:
class StoryViewSet(viewsets.ModelViewSet):
serializer_class = StorySerializer
def get_queryset(self):
if self.request.user.is_authenticated():
queryset = Story.objects.all()
else:
queryset = Story.objects.filter(story_is_save=True)
return queryset

Related

Different authentications and permissions in ModelViewSet - Django REST framework

This question is similar to this one: Using different authentication for different operations in ModelViewSet in Django REST framework, but it didn't work for me.
I've got the following viewset:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = UserProfile.objects.none()
permission_classes = [SpecialPermission]
SpecialPermission looks like this:
class SpecialPermission(IsAuthenticated):
def has_permission(self, request, view):
if request.method == 'POST':
return True
return super().has_permission(request, view)
REST framework settings:
"DEFAULT_AUTHENTICATION_CLASSES": ["backend.api.authentication.ExpiringTokenAuthentication"],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
I want to everybody to be able to post to UserViewSet but every other method should require Authentication. However, with the code above I get an Unauthorized Response on post.
What do I need to change?
Although it can be done, this requirement imo does not justify this ifology as auth/user related stuff should be clean and secure.
Instead extract POST method from this viewset to its own class.
class UserViewSet(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
serializer_class = UserSerializer
queryset = UserProfile.objects.none()
permission_classes = [SpecialPermission]
class CreateUserView(CreateAPIView):
serializer_class = UserSerializer
queryset = UserProfile.objects.none()
authentication_classes = []
if you really want to disable authentication in this viewset I'd rather recommend this
def get_authenticators(self):
if self.action == 'create':
return []
return super().get_authenticators()
That's more explicit than your solution.
I figured it out: Making perform_authentication lazy solved my problem. Now I can post but authentication still runs on all other methods where it is needed.
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.
Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
pass

Why is my Django Rest Framework Search filter not working?

Here is my code. I get no errors, and I can see the search button that was added to the browsable API. The problem though is the search does not work. No matter what I type into the search, it just returns every objects.
from rest_framework import status, filters
class JobView(GenericAPIView):
serializer_class = JobSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['name']
def get_queryset(self):
return Job.manager.all()
def get(self, request, format=None):
queryset = self.get_queryset()
if queryset.exists():
serializer = JobSerializer(queryset, many=True)
return Response(serializer.data)
else:
return Response({"Returned empty queryset"}, status=status.HTTP_404_NOT_FOUND)
endpoint
http://localhost:8000/jobs/?search=something
returns the same as
http://localhost:8000/jobs/
No matter what I put in the search string, it returns jobs.
This basically doesn't work because you're trying to do too much. You've written your own get method which bypasses all the magic of the DRF views. In particular, by not calling GenericAPIView.get_object, you avoid a line that looks like
queryset = self.filter_queryset(self.get_queryset())
which is where the QuerySet is filtered. This simpler version, practically identical to the one in the SearchFilter docs, should work
from rest_framework import status, filters, generics
class JobView(generics.LisaAPIView):
queryset = Job.manager.all()
serializer_class = JobSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['name']
NOTE based on your question, I am assuming:
that your Job model has a name field
that for some reason you've renamed the Job manager to manager via a call to models.Manager()
I think you should filter your queryset based on the parameter you're sending via GET, because it wont happen automatically. use request.query_params.get('search') to access your parameter.

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 Rest Framework: Update and delete current user without providing pk

I know a lot of people already asked about handling with current user but I couldn't find solution so I post this.
What I want to do is to get, put and delete current user without providing pk.
I want to set endpoint like users/my_account
My current code is here
class MyAccountDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = UserSerializer
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
And now I can get current user's info but when I try to update or delete the current user,
AssertionError: Expected view MyAccountDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.
How can I solve this?
Update
urlpatterns = [
path('users/my_account', views.MyAccountDetail.as_view()),
]
In this case, you will need to override get_object() method in your MyAccountDetail view. For example:
from rest_framework.permissions import IsAuthenticated
class MyAccountDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def get_object(self):
return self.request.user
You need to do that, because by default get_object method looks for lookup_url_kwarg or lookup_field in the URL, and from that it will try to fetch the object using pk or whatever you have configured in lookup_field or lookup_url_kwarg.
FYI, I have added a permission class as well, because without it, self.request.user will be an anonymous user, hence will throw error.

django rest permissions allow both IsAdmin and custom permission

I have a views.py as below,
from webapi.permissions import IsOwner
class MemberDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = members.objects.all()
serializer_class = MemberSerializer
permission_classes = (permissions.IsAdminUser,IsOwner)
And the following is custom permission to check if the user is ower of object in webapi.permissions,
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
Now the issue is it is check if he is a admin user and gives permissions to update / delete, But if the owner is the user it should actually give permission to edit he data but in this case it is failing.
On seeing the question "Django Rest Framework won't let me have more than one permission" I tried as below also still it did not work when I use Or,
class MemberDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = members.objects.all()
serializer_class = MemberSerializer
permission_classes = (Or(permissions.IsAdminUser,IsOwner))
If I use Or it is failing with error during run time as "'Condition' object is not iterable"
Since DRF 3.9, you can use the logical bitwise operators | and & (~ was added in 3.9.2).
As outlined in the docs you would just need
permission_classes = (permissions.IsAdminUser|IsOwner,)
If you need give edit permissions for admin and owner users only, you can implement custom permission class:
class IsOwnerOrAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user or request.user.is_admin
And use only this one in the view:
permission_classes = [IsOwnerOrAdmin]
This method is documented here.
you are using tuple syntax but you need comma to act as a tuple
replace
permission_classes = (Or(permissions.IsAdminUser,IsOwner))
with
permission_classes = (Or(permissions.IsAdminUser,IsOwner), )