I am using DRF & token authentication system to deal with the API. Whenever I send the data from the client it sends two of three required fields: title and name. But created_by is given by request.user in the backend.
So I need the following, that of course does not work as expected:
class DefaultViewSet(viewsets.ModelViewSet):
"""
API endpoint
"""
queryset = Default.objects.all().order_by('-updated_at')
serializer_class = DefaultSerializer
permission_classes = [permissions.IsAuthenticated]
filterset_fields = ['created_by', 'title', 'name']
def create(self, request, *args, **kwargs):
request.data['created_by'] = request.user.id
super().create(request, *args, **kwargs)
Since I can't change request.data object I should completely override create method. Therefore there is no easy way to do the previous stuff, right? or there is?
You should override perform_create method which calls the save method of the serializer and send additional keyword arguments as the docs says:
def perform_create(self, serializer):
request = serializer.context['request']
serializer.save(created_by=request.user.id)
The original method does:
def perform_create(self, serializer):
serializer.save()
I just managed to handle it in the create() function by using this example in the documentation:
def create(self, request, *args, **kwargs):
serializer = DefaultSerializer(data=request.data, context={'request': request})
if not serializer.is_valid():
return Response(serializer.errors, status=400)
serializer.save(created_by=request.user)
return Response({
'data': serializer.data
})
I think add it to your serializer class is better
created_by = serializers.HiddenField(default=serializers.CurrentUserDefault())
Related
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
In my Django project, I have a APIView:
class PhysicalServerManualGenerateOrderAPIView(CreateAPIView):
serializer_class = PhysicalServerManualGenerateOrderSerialzier
permission_classes = [IsFinanceAdmin, IsSuperAdmin]
queryset = Order.objects.all()
in the PhysicalServerManualGenerateOrderSerialzier:
class PhysicalServerManualGenerateOrderSerialzier(ModelSerializer):
...
def create(self, validated_data):
try:
order = getOrder(user=user, validated_data=validated_data) # there I create the order instance
except Exception as e:
order = None
return order
But I have a requirement, I want to return the created order's id (or other data) when I access the APIView success.
Change your api as below,
from rest_framework.response import Response
from rest_framework import status
class PhysicalServerManualGenerateOrderAPIView(CreateAPIView):
# your code
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
sufficent_data = serializer.data # you will get the serialized data here, which includes the "order_id" too
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
When you call the self.perform_create(serializer) the create() method of PhysicalServerManualGenerateOrderSerialzier class will get called and return the Order instance. serializer.data will serialize that instance and makes it available at API class
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())
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'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)