How to edit user permission in Django Rest Framework - django

I am following the tutorial of django Rest Framework. I want to add user-based permission so that only authenticated user can view each user's detail information.
Objective : Anyone can view the UserList, but only owner can view its UserDetail.
models.py
class Meeting(models.Model):
created = models.DateTimeField(auto_now_add=True)
sinceWhen = models.DateTimeField(null=True)
tilWhen = models.DateTimeField(null=True)
owner = models.ForeignKey('auth.User', related_name='meetings', on_delete=models.CASCADE)
#highlighted = models.TextField()
def save(self, *args, **kwargs):
super(Meeting, self).save(*args, **kwargs)
class Meta:
ordering = ('created',)
views.py
from django.contrib.auth.models import User
# User is not created inside models.py
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserListSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
# I added IsOwnerOrReadOnly to make it work, but this is the part where it causes error!
serializers.py
class UserSerializer(serializers.ModelSerializer):
meetings = serializers.PrimaryKeyRelatedField(many=True, queryset=Meeting.objects.all())
#owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = User
fields = ('id', 'username', 'meetings',)
class UserListSerializer(serializers.ModelSerializer):
#meetings = serializers.PrimaryKeyRelatedField(many=True, queryset=Meeting.objects.all())
class Meta:
model = User
fields = ('username',)
permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Any permissions are only allowed to the owner of the meeting
return obj.owner == request.user
I overrode IsOwnerOrReadOnly so that only user can view the details of his/her user detail.
And add this to permission_class in views.py.
Then I got this error :
File "/home/tony/env/lib/python3.6/site-packages/rest_framework/views.py" in check_object_permissions
345. if not permission.has_object_permission(request, self, obj):
File "/home/tony/swpp_hw1/meetings/permissions.py" in has_object_permission
15. return obj.owner == request.user
Exception Type: AttributeError at /users/1/
Exception Value: 'User' object has no attribute 'owner'
I tried to add User class in models.py, but again it causes error...
How can solve this issue?

Try to change it as:
return obj == request.user
as object is user you are trying to access and request.user is current authenticated user.

Related

How to retrive data from OneToOne Relational Model in DRF using API view

I have imported User model and customized it a/c to my need and make OneToOne Relation with UserProfileModel Model. While retrieving data I got this error.
"The serializer field might be named incorrectly and not match any attribute or key on the AnonymousUser instance.
Original exception text was: 'AnonymousUser' object has no attribute 'gender'."
My Model is :
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='userprofilemodel')
gender = models.CharField(max_length=20)
locality = models.CharField(max_length=70)
city = models.CharField(max_length=70)
address = models.TextField(max_length=300)
pin = models.IntegerField()
state = models.CharField(max_length=70)
profile_image = models.FileField(upload_to='user_profile_image', blank=True)
My Serializer looks like:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = ['id', 'name' , 'email','mobile',]
class UserProfileModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model= UserProfileModel
fields = ['user','gender' , 'locality','city','address','pin','state','profile_image', ]
My view looks like:
class UserProfileDataView(APIView):
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
I want to retrieve profile data of the logged user using UserProfileModel Model
Your first issue in that you are passing a User instance to the UserProfileModelSerializer, which is expecting a UserProfileModel instance. To fix this you need to change:
serializer = UserProfileModelSerializer(request.user)
to
serializer = UserProfileModelSerializer(request.user.userprofilemodel)
where userprofilemodel is the related_name you have set on the user field in your UserProfileModel.
Second issue is, as Mohamed Beltagy said, you're allowing anyone to access the view, including unauthenticated users. Django rest framework has a built in mixin that you can use to restrict access to authenticated users only (https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated).
from rest_framework.permissions import IsAuthenticated
class UserProfileDataView(APIView):
permission_classes = [IsAuthenticated]
the problem here is you are passing an anonymous user which has no profile ( you permit non-authenticated users access this view)
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
if request.user.is_authenticated:
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)

Create URL for detailview using DRF Modelviewset

i'm using DRF modelviewset to create an api for users model. Can anyone explain or help how can I pass a url in react-native for the detailview of user. Right now i'm getting the detailview in this manner 127.0.0.1:8000/users/users/2/. But everytime i don't want to pass the id in the url.
models.py
class User(AbstractUser):
"""This Class is used to extend the in-build user model """
ROLE_CHOICES = (('CREATOR','CREATOR'),('MODERATOR','MODERATOR'),('USERS','USERS'))
GENDER_CHOICES = (('MALE','MALE'),('FEMALE',"FEMALE"),('OTHER','OTHER'))
date_of_birth = models.DateField(verbose_name='Date of Birth', null=True)
profile_image = models.ImageField(upload_to='media/profile_images', verbose_name='Profile Image', default='media/profile_images/default.webp', blank=True)
bio = models.TextField(verbose_name='Bio')
role = models.CharField(max_length=10, verbose_name='Role', choices=ROLE_CHOICES, default='USERS')
gender = models.CharField(max_length=6, verbose_name='Gender', choices=GENDER_CHOICES)
serializers.py
class UserSerializer(serializers.ModelSerializer):
following = serializers.SerializerMethodField(read_only=True)
followers = serializers.SerializerMethodField(read_only=True)
class Meta:
model = User
fields = ('first_name','last_name','username','password','email','date_of_birth',
'profile_image','bio','role','gender', 'following','followers')
extra_kwargs = {'is_active':{'write_only':True},
'password':{'write_only':True}}
def create(self, validated_data):
logger.info('Information Stored!')
return User.objects.create_user(**validated_data)
def update(self, *args, **kwargs):
user = super().update( *args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
views.py
class UserAPI(viewsets.ModelViewSet):
serializer_class = UserSerializer
# permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
def get_queryset(self):
users = User.objects.all()
return users
urls.py
router = DefaultRouter()
router.register('users', views.UserAPI, basename='users'),
router.register('following', views.FollowingAPI, basename='following'),
urlpatterns = router.urls
How can i solve this. Need your help please. Thank you
You can make use of #action decorator.
Give this a try:
class UserAPI(viewsets.ModelViewSet):
...
#action(detail=False)
def profile(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Now go to 127.0.0.1:8000/users/profile/, you should see the current authenticated user's data.
this may be helpful.
You can use user = request.user.id , by in this way you can get current login user.

I created an extra table extra table in one to one relation with User table. how to show phone field in User registration

I am trying to create a simple API to get a user register.
I am using the default User table for authentication purpose, created another table called "phone" with one to one relation with User.
I am trying to add "phone" field just above the password. (I hope the image attached is visible).
**
Serializer.py
class UserRegisterSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetailsModel
fields = ('phone', 'user')
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=68, min_length=6, write_only=True)
class Meta:
model = User
fields = ('username','first_name', 'last_name','email','password')
read_only_fields = ('id',)
**
models.py<<
**
class UserDetailsModel(models.Model):
phone = models.IntegerField()
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
user = models.OneToOneField(get_user_model(),primary_key='email' , on_delete=models.CASCADE)
def __str__(self):
return str(self.user)
**
views.py
**
class RegisterView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
class DetailsRegisterView(generics.GenericAPIView):
serializer_class = UserRegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
**
urls
**
urlpatterns = [
path('',RegisterView.as_view()),
path('details', DetailsRegisterView.as_view())
]
**
You probably can use source in a serializer with a FK
class RegisterSerializer(...)
...
phone = serializers.CharField(..., source='userdetails.phone')
see also : the doc
I have some doubt in create case, in a update case this code work fine.
see also : How to serialize a relation OneToOne in Django with Rest Framework?
and an other way to resolve your issue : nested serializer
Updated code:
serializers>
from django.contrib.auth.models import User
from django.http import JsonResponse
from rest_framework import serializers
from .models import UserDetailsModel
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username','first_name', 'last_name','email','password')
read_only_fields = ('id',)
class UserRegisterSerializer(serializers.ModelSerializer):
user = RegisterSerializer(required=True)
class Meta:
model = UserDetailsModel
fields = ('phone','user')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = RegisterSerializer.create(RegisterSerializer(), validated_data=user_data)
data, created = UserDetailsModel.objects.update_or_create(user=user,
phone=validated_data.pop('phone'))
return data
class DetailView(serializers.ModelSerializer):
user = RegisterSerializer(read_only=True)
class Meta:
model = UserDetailsModel
fields = ('user','phone')
Remaining code stays the same.

Django: How to add user(author of the POST request) before save in serializers?

I'm tried to add author of the request before saving it in serializers.py
And got error:
Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x0000029169C48040>": "Category_product.posted_user" must be a "User" instance.
models.py
class Category_product(models.Model):
category_name = models.CharField(max_length=200, unique=True)
posted_user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.category_name
views.py
class Category_productDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category_product.objects.all()
serializer_class = Category_productSerializer
serializers.py
class Category_productSerializer(serializers.ModelSerializer):
post_user = serializers.ReadOnlyField(
source='posted_user.username')
class Meta:
model = Category_product
fields = ['id', 'category_name','post_user']
def validate(self, data):
data['posted_user'] = self.context['request'].user
return data
Have you tried overriding the user in your view?
Assuming you have Authentacion set up, because if not everyone could get in and it won't be a user, it would be Anonymous User instance.
If that's not the case, you can try it out in the view with perform create:
class Category_productDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category_product.objects.all()
serializer_class = Category_productSerializer
def get_user(self):
user = self.request.user
return user
def perform_create(self, serializer):
"""
Set the sender to the logged in user.
"""
serializer.save(posted_user=self.get_user())

How to retrieve all items created by a user using the django rest framework

I am trying to get only courses belonging to a particular user below I have the model, serializer and view I am using to try and achieve this. If I delete the entire get_queryset function from the view the api returns the appropriate user and every course created by every user. If get_queryset remains, the api always returns user not found and still gives every course that exists. Can anyone point me to how I can achieve my desired result.
view:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
# queryset = User.objects.all()
serializer_class = UserSerializer
def get_queryset(self):
user = self.request.user
queryset = User.objects.all()
if user is not None:
queryset = queryset.filter(courses__owner_id=user.id)
return queryset
serializer
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, queryset=Course.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'courses']
Model
class Course (models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
pub_date = models.DateField(default=date.today)
owner = models.ForeignKey('auth.User', related_name='courses', on_delete=models.CASCADE)
def __str__(self):
return self.title
You need to filter objects by user
class CreatePostsView(viewsets.ModelViewSet):
model = Post
serializer_class = PostsSerializer
def get_queryset(self):
user = self.request.user
return Post.objects.filter(owner=user)
class CoursesByOwnerView(RetrieveModelMixin, GenericViewSet):
serializer_class = YourModelSerializer
authentication_classes =[TokenAuthentication,]
permission_classes = [IsAuthenticated,]
def list(self, request, *args, **kwargs):
course_taker = self.request.user
courses = YourModel.objects.filter(owner=course_taker).values('your_model_fields')
return Response(courses)
Given your answer in the comments:
Either you use self.request.user given by the authentication middleware. In this case, it will only work for authenticated users, and you can't see courses for another User.
Either you use the endpoint users/<int:pk>/ you mentioned. In this case, you can fetch the user with:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
serializer_class = UserSerializer
def get_queryset(self):
return UserDetail.objects.filter(pk=self.kwargs["pk"])
See this thread if you need another example: Django 2.0 url parameters in get_queryset
EDIT: In both cases, change your UserSerializer with:
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'courses']