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
I want to create an API that allows to send JSON through a POST request.
The JSON should then get passed on to a serializer which takes care of creating a new object and populate it with the existing data.
It works fine for the 'simple' cases such as basic character-only inputs like usernames and alike, but I am seriously stuck when it comes to creating a OneToOne relation. Here's the sample code.
Function called employee_list in views.py - data['account'] is a valid username, a User instance is successfully being selected!
data = JSONParser().parse(request)
user_object = User.objects.get(username=data['account'])
data['account'] = user_object # this is now a valid User object
serializer = EmployeeSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
The model
class Employee(models.Model)
id = models.AutoField(primary_key=True)
name = models.CharField(...)
surname = models.CharField...
account = models.OneToOneField(User)
role = ...
salary = ...
picture = ...
And the serializer
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = (...) # all fields of the `Employee` model
So far so good, however, the serializer never validates! When I remove the need for a OneToOne relation, it works..
How can I create a new Employee objects with a working OneToOne relationship to a User object?
Thanks in advance.
You need to pass the pk of the User object, rather than the User object.
Solution:
data['account'] = user_object.pk
The issue is because a user object is being passed to the serializer, when it can be just passed to the serializer, while saving the object. Try something like this:
data = JSONParser().parse(request)
user_object = User.objects.get(username=data['account'])
serializer = EmployeeSerializer(data=data)
if serializer.is_valid():
# pass object to save, instead of saving in the data dictionary
serializer.save(account=user_object)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
My Category model is related to User, but I can't find a way to store the logged in user's id into user_id field in Category.
Category models.py:
class Category(models.Model):
user = models.ForeignKey(User, default=None)
name = models.CharField(max_length=32, unique=True)
views.py:
class CategoryList(APIView):
...
def post(self, request):
"""
Create a new Category
:param request:
:return: Category
"""
user_id = request.user.pk
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
I can access request.user.pk and see it's correctly shown, but I can't figure out how I can store this value when creating a new category.
To add current user id to new record you can pass current user as save argument:
if serializer.is_valid():
serializer.save(user=request.user)
Or you can change serializer and use CurrentUserDefault:
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
from docs:
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.
So to use CurrentUserDefault you need to pass request to serializer in view:
serializer = CategorySerializer(data=request.data, context={'request': request})
I've a model, and one of it's field refers to an overrided User instance (changed in Django settings).
When I'm performing a POST from my client, the route ends up here at the create method:
class CatView(ModelViewSet):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
serializer_class = CatListSerializer
def get_queryset(self):
return Cat.objects.filter(owner=self.request.user).order_by('id')
'''
def list(self, request, format=None):
serializer = CatGetSerializer(Cat.objects.filter(owner=request.user), context={'request': request}, many=True)
return Response(serializer.data)
'''
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def create(self, request, *args, **kwargs):
serializer = CatPutSerializer(data=request.data)
if serializer.is_valid():
serializer.create(serializer.data)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
When using a PUT to do a partial update on my model, it works fine. But creating one just doesn't work. I manually inject the user instance into the serializer and asks it to create the object. Then... nothing. No exception raises, it returns the proper data, but the object is not in my database, not being saved.
What's the issue here?
EDIT:
When I'm adding the owner field to the CatPutSerializer, it opens security issues since I don't know how to prevent this to be changed as I don't want the client to send me which user to assign. And when I'm duplicating the serializer to be used on POST only requests, it says it misses the owner field...
Here's the CatPutSerializer:
class CatPutSerializer(serializers.ModelSerializer):
class Meta:
model = Cat
fields = ('name', 'weight', 'sterilized', 'image', 'tag', 'dob', 'race', 'gender')
UPDATE:
As suggested, I'm now doing as follows :
def create(self, request, *args, **kwargs):
pdb.set_trace()
serializer = CatPutSerializer(data=request.data)
if serializer.is_valid():
serializer.save(owner=self.request.user)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
Though removed the perform_create overriding.
SOLUTION:
After further investigation, it doesn't seem related to drf but to Django / PostgreSQL itself, so I checked to Django model save method, and it seems that my custom image processing prevented from new objects to be created... Changed it and now works.
You seem to be overriding both create and perform_create. If you look at the code for CreateModelMixin which the ModelViewSet inherits from you will notice that create calls perform_create, which calls serializer.save(). You don't call perform_create in your create method; you seem to be calling serializer.create(...). If you are overriding create, simply do this:
def create(self, request, *args, **kwargs):
serializer = CatPutSerializer(data=request.data)
if serializer.is_valid():
serializer.save(owner=self.request.user)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
How do add one extra field (non-model field) to serializer.data dict ? I would like to add ("status" : "available") before sending the response. I tried this -
if serializer.is_valid():
serializer.save()
## This one doesn't work as serializer.data is of the type 'ReturnDict'
serializer.data.append(("status","available"))
## OR
serializer.data["status"] = "available"
return Response(serializer.data, status=status.HTTP_201_CREATED)
I also want to modify serializer.data's field names before sending the response. Is there any way to do this?
You can add an extra field to the serializer like this:
class MySerializer(serializers.ModelSerializer)
status = serializers.SerializerMethodField('get_status')
class Meta:
model = MyModel
read_only_fields = ('status',)
def get_status(self, obj):
""" Get the current objects status """
if obj.available == 1:
return 'available'
else:
return 'not available'
One way to change the field names would be to use a middleware class to format the response. I would do this if I would have to format the response for the whole API.