I am trying to perform a POST request to create an article and I am getting this error Request failed with status code 400 Bad Request: /api/articles/create/.
An article needs 3 attributes to be created:
(1) title
(2) body
(3) author (the current user)
The router works fine since the POST request goes into the post method of the ArticleCreateView class. But I'm guessing that serializer.is_valid() is returning False for some reason.
Also print(request.data) returns {'title': 'hello', 'body': 'stuff'}
Another mystery about Django is, how does the serializer know if I want to get, create or update something? In all the examples I've seen, the serializer magically seems to know this.
class ArticleCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
author = self.context['request'].user
title = validated_data.get('title')
body = validated_data.get('body')
return Article.objects.create(author=author, title=title, body=body)
class Article(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
body = models.TextField(max_length=100)
date_updated = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True)
According to your serializer, the validation process needs author details, which is not passing through POST payload.
So, make the author field in the serializer a not required field by adding required=False or use a ReadOnlyField() or specify read_only_fields in Meta class. Also pass context data as #nishant mentioned
# views.py
class ArticleCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ArticleSerializer(data=request.data, context={"request": request}) # change here
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
# serializer.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('author',)
def create(self, validated_data):
author = self.context['request'].user
title = validated_data.get('title')
body = validated_data.get('body')
return Article.objects.create(author=author, title=title, body=body)
add this
serializer = ArticleSerializer(data=request.data, context={'request':request})
Related
I want to save a simple model with Django REST Framework. The only requirement is that UserVote.created_by is set automatically within the perform_create() method. This fails with this exception:
{
"created_by": [
"This field is required."
]
}
I guess it is because of the unique_together index.
models.py:
class UserVote(models.Model):
created_by = models.ForeignKey(User, related_name='uservotes')
rating = models.ForeignKey(Rating)
class Meta:
unique_together = ('created_by', 'rating')
serializers.py
class UserVoteSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
created_by = UserSerializer(read_only=True)
class Meta:
model = UserVote
fields = ('id', 'rating', 'created_by')
views.py
class UserVoteViewSet(viewsets.ModelViewSet):
queryset = UserVote.objects.all()
serializer_class = UserVoteSerializer
permission_classes = (IsCreatedByOrReadOnly, )
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
How can I save my model in DRF without having the user to supply created_by and instead set this field automatically in code?
Thanks in advance!
I had a similar problem and I solved it by explicitly creating and passing a new instance to the serializer. In the UserVoteViewSet you have to substitute perform_create with create:
def create(self, request, *args, **kwargs):
uv = UserVote(created_by=self.request.user)
serializer = self.serializer_class(uv, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I was able to solve this with one-liner in views.py
def create(self, request, *args, **kwargs):
request.data.update({'created_by': request.user.id})
return super(UserVoteViewSet, self).create(request, *args, **kwargs)
Since this view expects user to be authenticated, don't forget to extend permission_classes for rest_framework.permissions.IsAuthenticated
The other weird way you can do is use signals like this
#receiver(pre_save, sender=UserVote)
def intercept_UserVote(sender, instance, *args, **kwargs):
import inspect
for frame_record in inspect.stack():
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
break
else:
request = None
instance.pre_save(request)
Then basically you can define pre_save in your model
def pre_save(self, request):
# do some other stuff
# Although it shouldn't happen but handle the case if request is None
self.created_by = request.user
The advantage of this system is you can use same bit of code for every model. If you need to change anything just change in pre_save(). You can add more stuff as well
Add the following to the ViewSet:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
And the following on the Serializer:
class Meta:
extra_kwargs = {
'user': {
'required': False,
},
}
Below code worked for me.
Even I was facing same error after many experiments found something, so added all fields in serializer.py in class meta, as shown below -
class Emp_UniSerializer( serializers.ModelSerializer ):
class Meta:
model = table
fields = '__all__' # To fetch For All Fields
extra_kwargs = {'std_code': {'required': False},'uni_code': {'required': False},'last_name': {'required': False},'first_name': {'required': False}}
Here, we can update any field which are in "extra_kwargs", it wont show error ["This field is required."]
I am checking in serializer if product exists in cart or not and I am using this
class ProductSerializer(serializers.ModelSerializer):
in_cart = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ['id', 'in_cart']
def get_in_cart(self, obj):
user = self.context['request'].user
if user.is_authenticated:
added_to_cart = Cart.objects.filter(user=user, product_id=obj.id).exists()
return added_to_cart
else:
return False
It works fine but I cannot add product to the cart because of that request
my cart model like this
class Cart(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True)
quantity = models.PositiveIntegerField()
def __str__(self):
return f'{self.user} cart item'
class ItemsListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
When I post product id to add cart it throws this error
user = self.context['request'].user KeyError: 'request'
I need to make both work but adding item to cart is being problem.
How can I solve this? Thank you beforehand!
You need to pass the request to the context before usage. So the calling of serializer should look like this:
ProductSerializer(product, context={'request': request})
With ListAPIView class you don't even need this, because by default it would be available in the serializer due to the default implementation of get_serializer_context method:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
Although you could override it if needed. Also, take a note that serializer_class accepts a callable object, it should be serializer_class = ProductSerializer.
I have had the same problem while using nested serializers. As mentioned above, you just can pass self.context['request'] to the context of nested serializer:
'author': GETUserSerializer(
recipe.author,
context={'request': self.context['request']}
).data,
models.py
class UserContentItem(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
views.py
class UserContentItemView(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
def post(self, request, format=None):
data = request.data
data['owner'] = request.user.id
serializer = UserContentItemSerializer(data=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)
serializers.py
class UserContentItemSerializer(serializers.ModelSerializer):
class Meta:
model = UserContentItem
fields = ('id', 'owner', 'title', 'created_date')
I am building an API with Django Rest Framework and simple jwt. I want to allow authenticated users to POST a new UserContentItem that has a FK dependency on the User but the User is not part of the POST payload. The only way I've been able to figure out how to do this is as above, adding the request.user.id to the request data before passing it to the serializer. Is there a better way to serialize the UserContentItem and achieve the same goal?
I think you can try like this using CurrentUserDefault:
class UserContentItemSerializer(serializers.ModelSerializer):
owner = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
class Meta:
model = UserContentItem
fields = ('id', 'owner', 'title', 'created_date')
And in view, pass request as context to serializer:
serializer = UserContentItemSerializer(data=data,context={'request':request})
Also you don't need to pass user id with data.
Your approach is good even you can follow these
serializer.save(owner=self.request.user)
I'm currently writing an API using the django rest framework for the first time. However, I've run in to a problem when trying to update an object using only the request.user field
from what I understand, when updating an object with request.data fields, all you have to do is:
def put(self, request, pk, format=None):
chore = self.get_object(pk)
serializer = ChoreSerializer(chore, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
however, if I try to leave the data=request.data out, I'll get errors when trying to call .is_valid(). and in turn .save(). I managed to get around this error by serializing the object I want to change based on a primary key, and then de-serializing it again, using the serialized data like this:
seri = ChoreSerializer(chore)
serializer = ChoreSerializer(chore, data=seri.data)
if serializer.is_valid():
serializer.save(assigned_to=self.request.user)
return Response(serializer.data)
My question is whether this is actually the correct way to do this, or whether there's a better method
here's my Model:
class Chore(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, blank=True, default='')
owner = models.ForeignKey('auth.User', related_name='chores')
assigned_to = models.ForeignKey('auth.User',related_name='assigned_to', blank=True, null=True)
and here's my Serializer:
class ChoreSerializer(serializers.ModelSerializer):
class Meta:
model = Chore
fields = ('id', 'name', 'owner', 'assigned_to')
owner = serializers.ReadOnlyField(source='owner.username')
assigned_to = serializers.ReadOnlyField(source='assigned_to.username')
def create(self, validated_data):
return Chore.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.assigned_to = validated_data.get('assigned_to', instance.assigned_to)
instance.save()
return instance
I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.