This very simple django restframework code.
models.py
class User(models.Model)
Email = models.CharField(max_length=100)
Username = models.CharField(max_length=100)
State = models.CharField(max_length=100)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('Email','Username','State')
views.py
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
If use this I am getting error out put like this
{
"Email": [
"This field may not be blank."
],
"Username": [
"This field may not be blank."
],
"Country": [
"This field may not be blank."
],
}
But I need to change the error out like this.How I can i archive this and any suggestion greatly appreciated.
{"error":
[
"Email is required",
"Username is required",
"County is required"
]
}
You can always overwrite the create/update methods from the generic views. It would be something like:
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=False):
# TODO: add here your custom error dict using serializer.errors
return Response({"error":...}, status=...)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Or... you could try to overwrite the serializers... if you don't want to overwrite the view.
(However, there must be a good reason for a JS developer not being able to parse a simple json error object :P)
Hope this helps
You can define your own error messages for any error case:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('Email','Username','State')
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
for field in self.Meta.fields:
self.fields[field].error_messages['required'] = "%s is required" % field
Related
So, I'm trying to make an endpoint where I insert a list of objects.
My issue is the behavior and response when inserting duplicates.
What I want to accomplish is to:
Send the duplicate lead external_id(s) in the error response
Insert any other non duplicated object
I'm pretty sure this logic (for the response and behavior) could be accomplished in the modifying the serializer.is_valid() method... but before doing that, I wanted to know if anyone had experience with this kind of request.. Maybe there is a "clean" way to do this while keeping the unique validation in the model.
Data on OK response:
[
{
"external_id": "1",
"phone_number": "1234567890"
}
]
Data for a FAIL request (1 is dup, but 2 should be inserted. Expecting a response like "external_id" : "id 1 is duplicated"):
[
{
"external_id": "1",
"phone_number": "1234567890"
},
{
"external_id": "2",
"phone_number": "2234567890"
}
]
models.py
class Lead(models.Model):
external_id = models.CharField(max_length=20, unique=True)
phone_number = models.CharField(max_length=50)
serializers.py
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = '__all__'
def create(self, validated_data):
lead = Lead.objects.create(**validated_data)
log.info(f"Lead created: {lead.import_queue_id}")
return lead
views.py
class LeadView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
#extend_schema(description="Insert campaign data", request=LeadSerializer(many=True), responses=None, tags=["Leads"])
def post(self, request):
serializer = LeadSerializer(data=request.data, many=True)
valid = serializer.is_valid()
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
You can customize the behaviour of list of object through list_serializer_class option in meta class. Like this:
class LeadListSerializer(serializers.ListSerializer):
def validate(self, data):
items = list(map(lambda x: x['phone_number'], data))
if len(set(items)) == len(items)):
return super().validate(data)
raise ValidationError()
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = '__all__'
list_serializer_class = LeadListSerializer
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."]
How do I throw the serializer.errors if I have a wrong input in some field? Do I have to code some "logic", or I just have to write some "configurations".
#models.py
class Product(models.Model):
name = models.CharField()
amount = models.IntegerField()
description = models.TextField()
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
#views.py
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
def partial_update(self, request, pk=None):
...
def get_queryset(self):
...
For example I want to POST:
{
"name": "Banana",
"amount": "ABCD",
"description": ""
}
Instead of getting an error in the server:
ValueError: invalid literal for int() with base 10: 'ABCD'
I want a response like this:
{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
As far the model is designed, the modelserializer will take care. In case you want more control of validation in your hand then you can do it in your serializers. For your problem, the following snippets would work.
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
def validate(self, attrs):
# you can add your validation rule here
if not isinstance(attrs.get('amount'), int):
serializers.ValidationError({
"amount": ["A valid integer is required."],
"description": ["This field may not be blank."]
})
In this way, you can use validate() to validate any field of your serializer as you wish. Official documentation on validator in serializer, Also you can use validate_<field_name>(self, <field_name>) function to validate a specific field of a serializer. Like mentioned in this answer.
i want to implement a feature in my rest api that users can add specific keywords for a news feed.
so if the users make a post request with a keyword within, the user object will be added on the predefined keyword (predefined in the database).
I have tried it with this code, but always if i try to simulate the post request with postman and i have this problem:
the keyword will be added but not the provided json data, its just a empty string and the post request returns also an empty keyword...
I hope you are able to help me and maybe you could give me an advice how to just allow the static keywords which are already defined and allow user only have a keyword once (no double keywords with same value)
Made with this headers:
[{"key":"Content-Type","value":"application/json","description":""}]
[{"key":"Authorization","value":"Token xxxxxxx","description":""}]
Body:
{
"name": "keyword1"
}
Authorization works, so the user added to the empty keyword
I am very new to django and i am doing this project to improve my skills, so please be lenient to me :) So it could be that its completly wrong, please give me some advices to solve my problem
These are the snippets for the implementation:
models.py
class Keywords(models.Model):
name = models.CharField(max_length=200)
user = models.ManyToManyField(User)
def __str__(self):
return self.name
serializers.py
class KeywordSerializer(serializers.Serializer):
class Meta:
model = Keywords
fields = ('id', 'name', 'user')
def create(self, validated_data):
keyword = Keywords(**validated_data)
keyword.save()
keyword.user.add(self.context['request'].user)
return keyword
views.py
class KeywordAPI(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
keyword = serializer.save()
return Response({
"name": KeywordSerializer(keyword, context=self.get_serializer_context()).data,
})
Try this snippet:
class KeywordSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Keywords
fields = '__all__'
def create(self, validated_data):
user = validated_data.pop('user')
kw = Keywords.objects.create(**validated_data)
kw.user.add(user)
kw.save()
return kw
and views:
from rest_framework import viewsets, permissions
class KeywordAPI(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
queryset = Keywords.objects.all()
The input payload be as
{
"name":"kw1"
}
NOTE
Here I used ModelSerializer class, because it's very handy for CURD applications and HiddenField is something like write_only=True parameter for fields.
References:
DRF - Modelviewset
HiddenField
CurrentUserDefault
There are few things you are doing wrong
First
Your model's name is Keywords it shouldn't be plural use Keyword and user field is ManyToMany so you should pluralise it
class Keyword(models.Model):
name = models.CharField(max_length=200)
users = models.ManyToManyField(User)
def __str__(self):
return self.name
Second
You are using Serializer instead of ModelSerializer
class KeywordSerializer(serializers.ModelSerializer):
class Meta:
model = Keywords
fields = ('id', 'name', 'users')
read_only_fields = ('users',)
def create(self, validated_data):
keyword = super().create(validated_data)
keyword.users.add(self.context['request'].user)
return keyword
Third
You don't have to write creation logic yourself use existing mixins
from rest_framework import mixins
class KeywordAPI(mixins.CreateModelMixin, generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
For those who maybe have the same problem, here is the whole solution:
Thanks to Sardorbek Imomaliev and Jerin Peter George for helping me
serializer:
class KeywordSerializer(serializers.ModelSerializer):
users = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Keyword
fields = '__all__'
def create(self, validated_data):
users = validated_data.pop('users')
if Keyword.objects.filter(**validated_data).exists():
kw = Keyword.objects.filter(**validated_data).get()
kw.users.add(self.context['request'].user)
return kw
else:
raise serializers.ValidationError("This Keyword has not been set yet.")
model:
class Keyword(models.Model):
name = models.CharField(max_length=200)
users = models.ManyToManyField(User)
def __str__(self):
return self.name
view:
class KeywordAPI(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
queryset = Keyword.objects.all()
I have the following json I'm trying to post -
{
"title": "test",
"description": "testing desc",
"username": "admin",
"password": "Welcome1",
"owner": 4
}
The ModelViewSet is below -
class PasswordViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = Password.objects.all()
serializer_class = PasswordSerializer
filter_class = PasswordFilter
def list(self, request):
my_passwords = Password.objects.filter(owner=request.user)
page = self.paginate_queryset(my_passwords)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = PasswordSerializer(my_passwords, many=True)
return Response(serializer.data)
If I remove the def list I can do a post just fine. The problem is trying to post with that def in there. it returns that all my fields are required. Why is it not passing the data?
This is my serializer for reference -
class PasswordSerializer(serializers.ModelSerializer):
class Meta:
model=Password
edit - no idea what's going wrong testing some more here and now it's not even working with the def list removed.
model -
class Password(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=1024)
username = models.CharField(max_length=50)
password = models.CharField(max_length=200)
owner = models.ForeignKey('MyUser', related_name='MyUser_owner')
The response is telling me all the fields in the model are required so to me on post the request.data is empty even though the json should be it.