Retrieving current user inside Serializer Method in Django Rest API - python

I have a serializer for user profiles in Django Rest:
class UserProfileSerializer(serializers.ModelSerializer):
......................
......................
status = serializers.SerializerMethodField()
def get_status(self, obj):
user = self.context['request'].user
if obj.user.userprofile in user.followed_userprofiles_set.all():
return "following"
else:
return "notfollowing"
class Meta:
model = UserProfile
fields = (...., 'status',...)
And I have two views that use this serializer:
class Followers(APIView):
def get(self, request, format=None):
#user who follow current user
users = request.user.userprofile.followers.all()
userprofiles= UserProfile.objects.filter(user__in=users)
serializer = UserProfileSerializer(userprofiles, many=True)
return Response(serializer.data)
and
class Friends(mixins.ListModelMixin, generics.GenericAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def get_queryset(self):
.................
.................
return queryset
One view is using APIView and other is using genericAPIView. When i request from genericAPIView, its working properly. But when i request from APIView, its giving me key error. How to retrieve the current user inside serializer method when APIView is used?

Since you are manually instantiating the UserProfileSerializer in your APIView class without passing the context, KeyError exception gets raised.
You should pass the request in context parameter when instantiating the UserProfileSerializer in your APIView.
class Followers(APIView):
def get(self, request, format=None):
#user who follow current user
users = request.user.userprofile.followers.all()
userprofiles= UserProfile.objects.filter(user__in=users)
context = {'request':request} # prepare serializer context
serializer = UserProfileSerializer(userprofiles, many=True, context=context) # pass context
return Response(serializer.data)

Related

maximum recursion depth is exceeded error while calling a Python object in django while making a post request in Django?

I am trying to list all the doubtclasses model using doubtclass view but there is some recursion error in the post request, which i am not able to understand , i search across for same error and i have tried if i made a similar mistake to the other developers that have asked the same question but as far as i searched mine one is different
My doubtclass view
class DoubtClass(LoginRequiredMixin, mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = serializers.DoubtClass_serializer
queryset = models.DoubtClasses.objects.filter(is_draft=False)
def get(self, request):
print("error in doubtclass get")
return self.list(request)
def post(self, request):
if request.user.is_superuser:
return self.post(request)
else:
return Response(status=status.HTTP_403_FORBIDDEN)
my doubtclass model
class DoubtClasses(models.Model):
doubtClass_details = models.TextField()
class_time = models.DateTimeField()
end_time = models.DateTimeField()
doubtsAddressed = models.IntegerField(default=0)
no_of_students_registered = models.IntegerField(default=0)
no_of_students_attended = models.IntegerField(default=0)
mentor_id = models.ForeignKey(Mentor, on_delete=models.CASCADE, null=True)
is_draft = models.BooleanField(default=True)
class Meta:
verbose_name_plural = 'DoubtClasses'
def __str__(self):
return self.doubtClass_details
I am new to django
From your code it seems you want to limit post requests to superusers. The problem with your implementation is that you are just calling post again recursively. Seeing as you inherit from CreateModelMixin you likely want to call create instead:
class DoubtClass(LoginRequiredMixin, mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = serializers.DoubtClass_serializer
queryset = models.DoubtClasses.objects.filter(is_draft=False)
def get(self, request, *args, **kwargs):
print("error in doubtclass get")
return self.list(request)
def post(self, request, *args, **kwargs):
if request.user.is_superuser:
return self.create(request, *args, **kwargs) # call `create` instead
else:
return Response(status=status.HTTP_403_FORBIDDEN)
But this can be improved. Firstly instead of inheriting from ListModelMixin, CreateModelMixin and GenericAPIView you can simply reduce that to inheriting from generics.ListCreateAPIView. Next instead of using LoginRequiredMixin it is better to use the IsAuthenticated permission. Also for your limitation of POST requests being limited to superusers that can also be added to a custom permission:
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
class IsSuperuserOrReadOnly(BasePermission):
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_superuser
)
class DoubtClass(generics.ListCreateAPIView):
serializer_class = serializers.DoubtClass_serializer
queryset = models.DoubtClasses.objects.filter(is_draft=False)
permission_classes = [IsAuthenticated, IsSuperuserOrReadOnly]

django post method create record using ListApiView

I am a beginner to django rest-framework and trying to create new record using POST method in ListAPIView.
Here's my serializer:
from scheme.models import ProjectScheme, ProjectSchemeMaster
from rest_framework import serializers
class SchemeDetailSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectScheme
fields = ('id', 'name', 'parent_scheme_id', 'rule', 'created_on', 'created_by', 'updated_on','updated_by')
depth=1
And view:
class ProjectSchemeList(ListAPIView):
"""
List all Schemes
"""
serializer_class = SchemeDetailSerializer
# pagination_class = ProjectLimitOffsetPagination
def get_queryset(self, *args, **kwargs):
comp_logger.info('invoked scheme list all')
schemes = ProjectScheme.objects.all().order_by('-id')
return schemes
def post(self, request, *args, **kwargs):
if serializer_class.is_valid():
serializer_class.save()
return Response(serializer_class.data, status=status.HTTP_201_CREATED)
return Response(serializer_class.errors, status=status.HTTP_400_BAD_REQUEST)
I get this error:
NameError at /scheme/schemes/
name 'serializer_class' is not defined
How do I pass request data to serializer_class?
Created functioanlity is included by default in CreateAPIView generic view, or if you want to provide list and create functionality, you can use ListCreateAPIView which provides both. More details on DRF's generic views here.
class ProjectSchemeList(ListCreateAPIView):
serializer_class = SchemeDetailSerializer
def get_queryset(self, *args, **kwargs):
comp_logger.info('invoked scheme list all')
schemes = ProjectScheme.objects.all().order_by('-id')
return schemes
With this definition, you won't need to manually write a post method.
If you want to manually define a post methdod, you can investiage how it is written in generic CreateAPIView and copy it, it's slighly different from how you want to write it. Finally, following is your version of the post method with errors fixed:
class ProjectSchemeList(ListAPIView):
serializer_class = SchemeDetailSerializer
def get_queryset(self, *args, **kwargs):
comp_logger.info('invoked scheme list all')
schemes = ProjectScheme.objects.all().order_by('-id')
return schemes
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Notice how we use self.serializer_class(data=request.data) instead of just serializer_class

Modifying a Django Rest Framework Request

I have a Django rest framework api set up, and I'm trying to insert the current time into incoming PUT requests. I currently have:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.filter(done = False).order_by('-time')
serializer_class = ItemSerializer
paginate_by = None
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(self.object_list, many=True)
return Response({'results': serializer.data})
This handles partial updates, but I would like to be able to send a request setting an Item to done = True and have the api also insert a unix timestamp into the data sent to the serializer. Could I alter the request object like this, or is there a better way?
def put(self, request, *args, **kwargs):
request.data['time'] = time.time()
return self.partial_update(request, *args, **kwargs)
Instead of modifying request, override serializer's method update.
Class ItemlSerializer(serializers.ModelSerializer):
class Meta:
model = ItemModel
fields = '__all__'
read_only_fields = ('time',)
def update(self, instance, validated_data):
instance.time = time.time()
return super().update(instance, validated_data)
You make a Parent serializer mixin with a serializer method field. Then all your serializers can inherit this serializer mixin.
class TimeStampSerializerMixin(object):
timestamp = serializers.SerializerMethodField()
def get_timestamp((self, obj):
return str(timezone.now())

How can I get the user data in serializer `create()` method? [duplicate]

I've tried something like this, it does not work.
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = self.context['request.user']
title = self.validated_data['title']
article = self.validated_data['article']
I need a way of being able to access request.user from my Serializer class.
You cannot access the request.user directly. You need to access the request object, and then fetch the user attribute.
Like this:
user = self.context['request'].user
Or to be more safe,
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
More on extra context can be read here
Actually, you don't have to bother with context. There is a much better way to do it:
from rest_framework.fields import CurrentUserDefault
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = CurrentUserDefault() # <= magic!
title = self.validated_data['title']
article = self.validated_data['article']
As Igor mentioned in other answer, you can use CurrentUserDefault. If you do not want to override save method just for this, then use doc:
from rest_framework import serializers
class PostSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Post
CurrentUserDefault
A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer.
in views.py
serializer = UploadFilesSerializer(data=request.data, context={'request': request})
This is example to pass request
in serializers.py
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
Source From Rest Framework
Use this code in view:
serializer = UploadFilesSerializer(data=request.data, context={'request': request})
then access it with this in serializer:
user = self.context.get("request").user
For those who used Django's ORM and added the user as a foreign key, they will need to include the user's entire object, and I was only able to do this in the create method and removing the mandatory field:
class PostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
request = self.context.get("request")
post = Post()
post.title = validated_data['title']
post.article = validated_data['article']
post.user = request.user
post.save()
return post
class Meta:
model = Post
fields = '__all__'
extra_kwargs = {'user': {'required': False}}
You can pass request.user when calling .save(...) inside a view:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
exclude = ['user']
class EventView(APIView):
def post(self, request):
es = EventSerializer(data=request.data)
if es.is_valid():
es.save(user=self.request.user)
return Response(status=status.HTTP_201_CREATED)
return Response(data=es.errors, status=status.HTTP_400_BAD_REQUEST)
This is the model:
class Event(models.Model):
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date = models.DateTimeField(default=timezone.now)
place = models.CharField(max_length=255)
You can not access self.context.user directly. First you have to pass the context inside you serializer. For this follow steps bellow:
Some where inside your api view:
class ApiView(views.APIView):
def get(self, request):
items = Item.object.all()
return Response(
ItemSerializer(
items,
many=True,
context=request # <- this line (pass the request as context)
).data
)
Then inside your serializer:
class ItemSerializer(serializers.ModelSerializer):
current_user = serializers.SerializerMethodField('get_user')
class Meta:
model = Item
fields = (
'id',
'name',
'current_user',
)
def get_user(self, obj):
request = self.context
return request.user # <- here is current your user
In GET method:
Add context={'user': request.user} in the View class:
class ContentView(generics.ListAPIView):
def get(self, request, format=None):
content_list = <Respective-Model>.objects.all()
serializer = ContentSerializer(content_list, many=True,
context={'user': request.user})
Get it in the Serializer class method:
class ContentSerializer(serializers.ModelSerializer):
rate = serializers.SerializerMethodField()
def get_rate(self, instance):
user = self.context.get("user")
...
...
In POST method:
Follow other answers (e.g. Max's answer).
You need a small edit in your serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = self.context['request'].user
title = self.validated_data['title']
article = self.validated_data['article']
Here is an example, using Model mixing viewsets. In create method you can find the proper way of calling the serializer. get_serializer method fills the context dictionary properly. If you need to use a different serializer then defined on the viewset, see the update method on how to initiate the serializer with context dictionary, which also passes the request object to serializer.
class SignupViewSet(mixins.UpdateModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
http_method_names = ["put", "post"]
serializer_class = PostSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
kwargs['context'] = self.get_serializer_context()
serializer = PostSerializer(instance, data=request.data, partial=partial, **kwargs)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
The solution can be simple for this however I tried accessing using self.contenxt['request'].user but not working in the serializer.
If you're using DRF obviously login via token is the only source or maybe others that's debatable.
Moving toward a solution.
Pass the request.user instance while creating serializer.create
views.py
if serializer.is_valid():
watch = serializer.create(serializer.data, request.user)
serializer.py
def create(self, validated_data, usr):
return Watch.objects.create(user=usr, movie=movie_obj, action=validated_data['action'])
If you are using generic views and you want to inject current user at the point of saving the instance then you can override perform_create or perform_update:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
user will be added as an attribute to kwargs and you can access it through validated_data in serializer
user = validated_data['user']
drf srz page
in my project it worked my user field was read only so i needed to get
user id in the create method
class CommentSerializer(serializers.ModelSerializer):
comment_replis = RecursiveField(many=True, read_only=True)
user = UserSerializer(read_only=True)
class Meta:
model = PostComment
fields = ('_all_')
def create(self, validated_data):
post = PostComment.objects.create(**validated_data)
print(self._dict_['_kwargs']['data']["user"]) # geting #request.data["user"] # <- mian code
post.user=User.objects.get(id=self._dict_['_kwargs']['data']["user"])
return post
in my project i tried this way and it work
The best way to get current user inside serializer is like this.
AnySerializer(data={
'example_id': id
}, context={'request': request})
This has to be written in views.py
And now in Serializer.py part
user = serializers.CharField(default=serializers.CurrentUserDefault())
This "user" must be your field in Model as any relation like foreign key

Django Rest Framework: Change Serializer or Add Field Based on Authentication

I have a viewset as follows:
class CardViewSet(viewsets.ReadOnlyModelViewSet):
"""
Standard Viewset for listing cards
"""
pagination_class = StandardCellSetPagination
permission_classes = [AllowAny, IsAuthenticated]
def list(self, request):
queryset = Card.objects.exclude(reply_to__isnull=False).order_by('-created')
cards = self.paginate_queryset(queryset)
serializer = CardCellSerializer(cards, many=True)
return self.get_paginated_response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Card.objects.all()
card = get_object_or_404(queryset, pk=pk)
serializer = CardSerializer(card)
return Response(serializer.data)
My serializer for the CardSerializer is:
class CardSerializer(serializers.ModelSerializer):
class Meta:
model = Card
How do I either
Change the serializer for the retrieve method if the viewset has the permission IsAuthenticated?
OR
Add a field to the CardSerializer if viewset has IsAuthenticated?
Such that I can return True / False if a user has favorited the card via a SerializerMethodField
You can do this:
def retrieve(self, request, pk=None):
queryset = Card.objects.all()
card = get_object_or_404(queryset, pk=pk)
# Same for list method
if request.user and request.user.is_authenticated:
serializer = AuthenticatedCardSerializer(card)
else:
serializer = CardSerializer(card)
return Response(serializer.data)
AuthenticatedCardSerializer could then extend CardSerializer to include any fields visible to authenticated users.
Also if you decide to use same serialization behavior for list and retrieve, you could override get_serializer_class in your viewset instead:
def get_serializer_class(self):
if self.request.user and self.request.user.is_authenticated:
return AuthenticatedCardSerializer
else:
return CardSerializer
and leave everything else to the default list/retrieve implementations.
As an alternative, you could add the field in serializer's __init__. You can get the request from the context kwarg, do the same check and add any fields you need. I think though that it is needlessly more complicated than just having two serializers.

Categories

Resources