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

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

Related

Create model instance only if its user id field is equal to the logged-in user's id

In a django rest framework app, there's a TextViewSet. The Text object structure is as follows:
{
text: text_value,
author: author_id
}
When creating a new Text instance, I want to check if the supplied author_id equals the currently logged-in user's id.
I've read this question: When to use Serializer's create() and ModelViewset's perform_create(), but still can't decide whether to override Serializer's create(), ModelViewset's create() or perform_create() methods. What's the right method to override?
UPD:
models.py:
class Text(models.Model):
text = models.TextField()
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
serializers.py:
class TextSerializer(serializers.ModelSerializer):
class Meta:
model = Text
fields = ['author', 'text']
The question is in which of these methods should one perform this check if self.request.user.id != self.request.data['author']:?
You can override create() the method of your TextViewSet
views.py
from rest_framework.response import Response
class TextViewSet(ModelViewSet):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if request.user.id == request.data['author']:
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
else:
return Response("Unauthorized", status=status.HTTP_401_UNAUTHORIZED
The real question is why not just set author to the logged in user and not let the client send in any ID at all?
The normal way of doing this is by:
class MyViewSet(ModelViewSet):
...
def perform_create(self, serializer):
'''The logged in user is always the author'''
return serializer.save(author=self.request.user)
def get_queryset(self):
'''Limit the queryset to the author, i.e the logged in user, for fetching/updating data'''
return self.queryset.filter(author=self.request.user)
But if you really want to send in author-id then you can also use a custom Permission for it to have it re-usable.
from rest_framework.permissions import BasePermission
class UserIsAuthor(BasePermission):
default_author_field = "author"
def has_permission(self, request, view):
author_field = getattr(
view,
"permission_author_field",
self.default_author_field
)
return request.user.is_authenticated and (
request.user.pk == request.data.get(author_field)
)
used as:
class ExampleViewSet(ModelViewSet):
permission_classes = [UserIsAuthor]
# optional if the default "author" isnt what you want.
permission_author_field = "some_field_in_request_data"

How to use create function instead of perform_create in ListCreateApiView in django rest framework

I am trying to create a booking api for a website.
For this, I used perform_create function in ListCreateApiView. But, it was someone else who helped me and told me to use perform_create function.
But, I was thinking, it should be possible using create function and is a right approach rather than perform_create function.
Also, I don't really know the difference between these two functions and don't really know when to use which
Here is my code:
class BookingCreateAPIView(ListCreateAPIView):
permission_classes= [IsAuthenticated]
queryset = Booking.objects.all()
serializer_class = BookingSerializer
def perform_create(self, serializer):
# user = self.request.user
package = get_object_or_404(Package, pk= self.kwargs['pk'])
serializer.save(user=self.request.user,package=package)
Here is my serializer:
class BookingSerializer(serializers.ModelSerializer):
# blog = serializers.StringRelatedField()
class Meta:
model = Booking
fields = ['name', 'email', 'phone', 'bookedfor']
Here is my model:
class Booking(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
package = models.ForeignKey(Package, on_delete=models.CASCADE, related_name='package')
name = models.CharField(max_length=255)
email = models.EmailField()
phone = models.CharField(max_length=255)
bookedfor = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created_at',)
if you looking in source code:
ListCreateApiView call
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
then inside create call
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)
That mean perform_create called after serializer validated.
And for what you want, you can override different method.
In my opinion:
If you want custom Response return from api, you override create
If you want doing anything special after serializer validated, but before object created in database, you override perform_create. Like your example, it want checking Package exists and save request.user in field user

How to associate user to a post request in Django drf

I have the following :
I am working with DRF, based JWT token.
I want to associate an experiment with a USER, i.e when a post request is arriving I want to be able to save that post request with the Foreginkey it needed for the author by the user whom sent the request.
The POST request is always authenticated and never anonymous, i.e request.user is always exist ( I can see it when debugging)
I tried to add the following
def create(self, request, **kwargs):
request.data["author"] = request.user
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return....
But is_valid return always False ( the only time ts was true, was when I took out the author from the ExperimentsSerializers fields....
will be happy for any leads....
my code attached below
Model.py:
class User(AbstractUser):
pass
def __str__(self):
return self.username
class Experiments(models.Model):
name = models.CharField(max_length=40)
time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
View.py:
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
serializer_class = ExperimentsSerializers
queryset = Experiments.objects.all()
filterset_fields = '__all__'
permission_classes = (permissions.IsAuthenticated,)
serializers.py
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
You can just pass additional data with save arguments:
def create(self, request, **kwargs):
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
Note that you may need to specify author field as read_only so it would not be required in request body:
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
read_only_fields = ['author']
One more approach can be to use
HiddenField with default value set to CurrentUserDefault
This way that field will not be exposed at the same time current user will be accessible and other operations can be done on that user context.
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
Something like this:
class ExperimentsSerializers(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.Experiments
fields = '__all__'
Reference :
HiddenField - https://www.django-rest-framework.org/api-guide/fields/#hiddenfield
CurrentUserDefault - https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault

Django Rest Framework: User Update

I know this has been discussed and it's basic but I can't find what is wrong with it. I've pulled up my old projects (which works!) and corresponded to what I did. It never reaches to update in serializer, and I'm at lost why.
I dont know what else I'm missing.
Error:
{"last_name":["This field may not be null."],"pass…
null."],"email":["This field may not be null."]}, status: 400
frontend patch('api/getprofile')
django/DRF serializer:
class UserSerializer(serializers.ModelSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
email = serializers.EmailField()
password = serializers.CharField(style={'input_type': 'password'})
class Meta:
model = User
fields = '__all__'
def create(self, validated_data):
user = User.objects.create(
username=validated_data.get('username'),
email=validated_data.get('email'),
password=validated_data.get('password')
)
user.set_password(validated_data.get('password'))
user.save()
return user
def update(self, instance, validated_data):
#print instance <-- if never gets here... is update not update
for key, value in validated_data.items():
if value:
print value
setattr(instance, key, value)
instance.save()
return instance
views.py
class UserRetrieveUpdateAPIView(generics.RetrieveUpdateAPIView):
serializer_class = UserSerializer
permission_classes = (IsAuthenticated, )
queryset = User.objects.all()
def get_object(self):
return self.request.user
def update(self, request, *args, **kwargs):
serializer = UserSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
instance = serializer.instance
return Response(UserSerializer(instance=instance).data, status=status.HTTP_200_OK)
The only implementation that you may need to provide for your APIView is the get_object method.
From the source for mixins.UpdateMixins, update (for HTTP PUT requests) and partial_update are implemented as you have it.
The override for the mixins.UpdateMixins.update you provide allows partial updates on HTTP PUT requests and misses passing the model instance to the serializer for the update. i.e.
serializer = UserSerializer(self.get_object(), data=request.data, partial=True)
I however suggest to not perform the override for mixins.UpdateMixins.update in the current manner.
Use the standard handling of the HTTP requests implemented in mixins.UpdateMixins and only provide your implementation for .get_object().
You do this already with UserRetrieveUpdateAPIView.get_object().

Django Rest Framework ModelSerializer Set attribute on create

When creating an object initially I use the currently logged-in user to assign the model field 'owner'.
The model:
class Account(models.Model):
id = models.AutoField(primary_key=True)
owner = models.ForeignKey(User)
name = models.CharField(max_length=32, unique=True)
description = models.CharField(max_length=250, blank=True)
Serializer to set owner:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = models.Account
fields = ('name', 'description')
def restore_object(self, attrs, instance=None):
instance = super().restore_object(attrs, instance)
request = self.context.get('request', None)
setattr(instance, 'owner', request.user)
return instance
It is possible for a different user in my system to update another's Account object, but the ownership should remain with the original user. Obviously the above breaks this as the ownership would get overwritten upon update with the currently logged in user.
So I've updated it like this:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = models.Account
fields = ('name', 'description')
def restore_object(self, attrs, instance=None):
new_instance = False
if not instance:
new_instance = True
instance = super().restore_object(attrs, instance)
# Only set the owner if this is a new instance
if new_instance:
request = self.context.get('request', None)
setattr(instance, 'owner', request.user)
return instance
Is this the recommended way to do something like this? I can't see any other way, but I have very limited experience so far.
Thanks
From reviewing #zaphod100.10's answer. Alternatively, in the view code (with custom restore_object method in above serializer removed):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.object.owner = request.user
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Basically you want the owner to be set on creation and not on subsequent updates. For this I think you should set the owner in the POST view. I think it is more logical and robust that way. Update is done via PUT view so your data should always be correct since no way on updation the owner can be changed if the owner is not editable on PUT.
For making the views you can use DRF's generic class based views. Use the RetrieveUpdateDeleteView as it is. For ListCreateView override the post method. Use a django model form for validating the data and creating an account instance.
You will have to copy the request.DATA dict and insert 'owner' as the current user.
The code for the POST method can be:
def post(self, request, *args, **kwargs):
data = deepcopy(request.DATA)
data['owner'] = request.user
form = AccountForm(data=data)
if form.is_valid():
instance = form.save(commit=false)
instance.save()
return Response(dict(id=instance.pk), status=status.HTTP_201_CREATED)
return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)
Potential other option using pre_save which I think seems to be intended for just this kind of thing.
class AccountList(generics.ListCreateAPIView):
serializer_class = serializers.AccountSerializer
permission_classes = (permissions.IsAuthenticated)
def get_queryset(self):
"""
This view should return a list of all the accounts
for the currently authenticated user.
"""
user = self.request.user
return models.Account.objects.filter(owner=user)
def pre_save(self, obj):
"""
Set the owner of the object to the currently logged in user as this
field is not populated by the serializer as the user can not set it
"""
# Throw a 404 error if there is no authenticated user to use although
# in my case this is assured more properly by the permission_class
# specified above, but this could be any criteria.
if not self.request.user.is_authenticated():
raise Http404()
# In the case of ListCreateAPIView this is not necessary, but
# if doing this on RetrieveUpdateDestroyAPIView then this may
# be an update, but if it doesn't exist will be a create. In the
# case of the update, we don't wish to overwrite the owner.
# obj.owner will not exist so the way to test if the owner is
# already assigned for a ForeignKey relation is to check for
# the owner_id attribute
if not obj.owner_id:
setattr(obj, 'owner', self.request.user)
I think this is the purpose of pre_save and it is quite concise.
Responsibilities should be split here, as the serializer/view only receives/clean the data and make sure all the needed data is provided, then it should be the model responsibility to set the owner field accordingly. It's important to separate these two goals as the model might be updated from elsewhere (like from an admin form).
views.py
class AccountCreateView(generics.CreateAPIView):
serializer_class = serializers.AccountSerializer
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, *args, **kwargs):
# only need this
request.data['owner'] = request.user.id
return super(AccountCreateView, self).post(request, *args, **kwargs)
models.py
class Account(models.Model):
# The id field is provided by django models.
# id = models.AutoField(primary_key=True)
# you may want to name the reverse relation with 'related_name' param.
owner = models.ForeignKey(User, related_name='accounts')
name = models.CharField(max_length=32, unique=True)
description = models.CharField(max_length=250, blank=True)
def save(self, *args, **kwargs):
if not self.id:
# only triggers on creation
super(Account, self).save(*args, **kwargs)
# when updating, remove the "owner" field from the list
super(Account, self).save(update_fields=['name', 'description'], *args, **kwargs)

Categories

Resources