Django rest framework unauthenticated user get request error in testing - python

I'm trying to test the get request on the url which requires user to be authenticated. If user is unauthenticated it must return HTTP_401 UNAUTHORIZED error as response status code. For that purpose i've created a test case where i tried firing get request without authentication.
TestCase
class PublicTagsApiTests(TestCase):
"""Test the publicly available tags API"""
def setUp(self):
self.client = APIClient()
def test_login_required(self):
"""Test that login is required for retrieving tags"""
res = self.client.get(reverse('recipe:tag-list'))
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
But the test fails returning error:
TypeError: int() argument must be a string, a bytes-like object or a number, not 'AnonymousUser'
View
class TagViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""Manage tags in the database"""
authentication_classes = (TokenAuthentication,)
permission = (IsAuthenticated,)
queryset = Tag.objects.all()
serializer_class = serializers.TagSerializer
def get_queryset(self):
"""Return objects for the current authenticated user only"""
return self.queryset.filter(user=self.request.user).order_by('-name')
Serializer
class TagSerializer(serializers.ModelSerializer):
"""Serializer for tag objects"""
class Meta:
model = Tag
fields = ('id','name')
read_only_fields = ('id',)
How can i modify my view to pass the test?

In TagViewSet try to change:
permission = (IsAuthenticated,)
to:
permission_classes = [IsAuthenticated]

Related

Django Rest Framework Serializers: Validating object.user is request.user

I'm working on a REST API with Django Rest Framework and in my ModelViewSet I need to validate that the current request.user has the right to edit a particular object.
I have found the part of the documentation that specifies how permissions work– but this is on the ViewSet side, and not the serializer side:
class FooViewSet(viewsets.ModelViewSet):
model = Foo
serializer_class = FooSerializer
def get_queryset(self):
return self.request.user.foos.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_permissions(self):
if self.action == "list":
permission_classes = [permissions.IsAuthenticated]
else:
permission_classes = [IsObjectUser]
return [permission() for permission in permission_classes]
This will work fine for 403ing when it's not the appropriate user, but I believe I should also be doing serializer-level validation? How can I get the object in question in my validate method to check against?
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = [
"type",
"title",
"description",
"image",
]
def validate(self, attrs):
# <-- how can I get the object so I can check against the self.request.user?
My answer is that you shouldn't. Ideally your serializers don't know about the request. That's the views realm (with exceptions). Also, since user isn't specified in fields of FooSerializer it doesn't make sense to validate the user. If the user could be specified, then it should be validated.
def validate(self, attrs):
# <-- how can I get the object so I can check against the self.request.user?
userobj=self.context['request'].user.
print(attrs)
title=attrs.get("title")
enter code here
attrs.update({'title': title})
attrs = super().validate(attrs)
return attrs

Django - QuerySet is not iterable in permissions.py

New to Django and Django Rest here.
I have the following structure :
class MyUsers(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
class roles(models.Model):
id = models.AutoField(primary_key=True)
label = models.CharField(max_length=80)
class user_roles(models.Model):
id_user = models.ForeignKey(MyUsers,on_delete=models.CASCADE)
id_role = models.ForeignKey(roles,on_delete=models.CASCADE)
I'm trying to create a custom permission, to allow users with a specific role to access some endpoints.
class IsAuthenticatedAndLeader(BasePermission):
def has_permission(self, request, view):
id_role=models.roles_users.objects.filter(id_user=request.user.id).values_list('id_role',flat=True)
if "Leader" in models.roles.objects.filter(id=id_role).values_list('label',flat=True):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return True
When I try to access to the endpoint, I have the following error:
TypeError: argument of type 'QuerySet' is not iterable
However if I try in views.py something simple like the following it works :
if "Leader" in models.roles.objects.filter(id=3).values_list('label',flat=True):
print("Yes")
So I'm not sure why I'm getting this error when trying to apply it to permissions.py
I think your problem its
id_role=models.roles_users.objects.filter(id_user=request.user.id).values_list('id_role',flat=True)
if "Leader" in models.roles.objects.filter(id=id_role).values_list('label',flat=True):
id_role is a list and you can get roles where id=(ids....)
then it not works, try with:
if "Leader" in models.roles.objects.filter(id__in=id_role).values_list('label',flat=True):

Django REST Framework custom permission causes JSON Parse error

I have the following classes:
class SimpleArtistTrackSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ArtistTrack
fields = (...fields...)
class ArtistProfileSerializer(serializers.HyperlinkedModelSerializer):
tracks = SimpleArtistTrackSerializer(many=True, required=False)
class Meta:
model = ArtistProfile
fields = (..fields...)
def create(self, validated_data):
user = get_user_or_explode(self.context)
profile = ArtistProfile.objects.create(**validated_data)
profile.owners.add(user)
class IsAnOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return True
class ArtistProfileViewSet(viewsets.ModelViewSet):
queryset = ArtistProfile.objects.all()
serializer_class = ArtistProfileSerializer
permission_classes = [IsAnOwnerOrReadOnly]
Just by adding this permission class, all my requests now return a Parse error:
{"detail":"JSON parse error - Extra data: line 1 column 19 (char 18)"}
Debugging shows that the object permission method is running. Using a standard DRF IsAuthenticated permission does not cause this error, and removing the permission_classes gets rid of this error. For some reason, using the custom BasePermission seems to be causing.
Am I missing something? How can I debug this better to find the problem?

How to change permissions depend on different instance using Django Rest Framework?

I'm using Django rest framework, and my model is like this, Every Act can have more than one post.
class Act(models.Model):
user = models.ForeignKey("common.MyUser", related_name="act_user")
act_title = models.CharField(max_length=30)
act_content = models.CharField(max_length=1000)
act_type = models.IntField()
class Post(models.Model):
user = models.ForeignKey("common.MyUser", related_name="post_user")
act = models.ForeignKey("activities.Act", related_name="post_act")
post_title = models.CharField(max_length=30, blank=True)
post_content = models.CharField(max_length=140)
my view.py in DRF:
class PostList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostAllSerializer
def perform_create(self, serializer): #self is a instance of class or is a class here?
serializer.save(user=self.request.user)
This works fine, but what I want now is if act_type = 1 means this is a private Act and only the act author can create post under this act.I wonder how to use different permission_classes depend on different Act.Maybe looks like:
class PostList(generics.ListCreateAPIView):
if self.act_type == 1:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsActCreatorOrReadOnly)
else
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostAllSerializer
def perform_create(self, serializer): #self is a instance of class or is a class here?
serializer.save(user=self.request.user)
And I also want to know how to write this permissions.py:
class IsActCreatorOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.act.user == request.user
I don't know what obj really means here, and the error tell me obj.act doesn't exist.
EDIT
Here is my postSerializer.
class PostAllSerializer(serializers.ModelSerializer):
"""Posts api fields"""
post_user = UserSerializer(source="user", read_only=True)
post_author = serializers.ReadOnlyField(source='user.user_name')
class Meta:
model = Post
fields = ("id", "act", "post_author", "post_title", "post_content",)
I tried this, but not working, I still can create the post even I'm not the author of the Act(but the act_id is wrong):
def create(self, request, *args, **kwargs):
act_type = request.data.get("act_type")
if act_type == 0:
act_id = request.data.get("act")
act = Act.objects.get(pk=act_id)
if request.user != act.user:
return Response(status=403)
return super().create(request)
For using different permission classes, there is the get_permissions method that you can overwrite on your PostList view:
def get_permissions(self):
if self.request.method == 'POST':
return (OnePermission(),)
elif # other condition if you have:
return (AnotherPermission(),)
return (YetAnotherPermission(),)
However, in your case you can't use object level permissions, because you don't have an object instance yet. From the DRF docs (highlights by me):
REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance.
Object level permissions are run by REST framework's generic views when .get_object() is called.
When doing a POST request, you don't have any object yet, thus the object level permissions won't be invoked.
One way you could achieve what you want is by checking it in the create method of PostList view. Something like this (hypothetical code):
class PostList(generics.ListCreateAPIView):
...
def create(self, request):
act_id = request.data.get('act') # depending on your PostSerializer, the logic of getting act id can vary a little
act = Act.objects.get(pk=act_id) # assuming act always exists, otherwise account for in-existing act
if act.user != request.user:
return Response({details: "You shall not pass!!!", status=200) # change to a status and message you need here
# logic of Post creation here
Good luck!

Serializing NotificationQuerySet from django-notifications-hq not working

So, i'm trying to add to my API made with DRF (Django REST Framework) the notifications Model, but i'm getting this error:
AttributeError: 'NotificationQuerySet' object has no attribute 'recipient'
I'm trying to serialize a django app model, Notification. It's from this app:
https://github.com/django-notifications/django-notifications
My ViewSet class is this:
class NotificationsViewSet(viewsets.ViewSet):
serializer_class = NotificationsSerializer
def list(self, request):
queryset = Notification.objects.all()
return Response(NotificationsSerializer(queryset).data)
And here my serializer:
class NotificationsSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = ('recipient','description')
depth = 0
So, when data pass to serializer, it becomes "Void" or without any data.
Doing something like into the list method:
print queryset[0] returns a Notification object normaly. But when passing this queryset to the serializer, seems to be null, and the AttributeError comes.
Also, tried this with the console:
notifications = Notification.objects.all()
That returns a NotificationQuerySet object (iterable). Then I can:
for noti in notifications:
print noti
That would output all the unicode methods of every notification.
With every Notification instance, i can also access to Model propierties:
for noti in notifications:
print noti.recipient
And works very well.
Why is not working when passing this to the serializer? Its weird...
You need to pass many=True when initializing a serializer with a queryset. DRF will assume you are passing a single object and try to get the fields directly from it if you do not tell it that you are passing in multiple objects.
Heres a full implementation where the readme leaves off for drf
urls.py
...
import notifications.urls
urlpatterns = [
...
path("inbox/notifications/", views.NotificationViewSet.as_view({'get': 'list'}), name='notifications'),
]
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
class NotificationSerializer(serializers.Serializer):
recipient = UserSerializer(read_only=True)
unread = serializers.BooleanField(read_only=True)
target = GenericNotificationRelatedField(read_only=True)
verb = serializers.CharField()
views.py
from notifications.models import Notification
from .serializers import NotificationSerializer
NotificationViewSet(viewsets.ViewSet):
serializer_class = NotificationSerializer
def list(self, request):
queryset = Notification.objects.all()
return Response(NotificationSerializer(queryset, many=True).data)

Categories