Rest Framework cant save serializer with foreign key - python

I have next serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Post
fields = ['id', 'title', 'text', 'date', 'category']
And here is my view:
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors)
return Response(serializer.data)
I want to create new Post object, but when I pass an category id at form it does not work, it is not saving my object. I tried to replace create method at my PostSerializer, to this:
def create(self, validated_data):
category_id = validated_data.pop('category')
post = Post.objects.create(**validated_data, category=category_id)
return post
but this dont work. Using postman formdata it is saying, that category field is required despite I filled it.
Here is my models:
class Category(models.Model):
name = models.CharField(max_length=512)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=512)
text = models.TextField()
date = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category')

You need a category object not just an id, so try this
#api_view(['POST'])
def create_post(request):
category_id = request.data['category'] # or however you are sending the id
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
category = Category.objects.get(id=category_id)
serializer.save(category=category)
else:
return Response(serializer.errors)
return Response(serializer.data)
or you can do something similar in the create method of the serializer

Related

How to send several fields in the response with a PUT request?

I would like when my PUT request is successful it returns me a response with all the fields in my PlantSerializer because currently the response returns me this:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
7
]
}
But, I would like the response to return this instead:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
"id":7,
"name":"Artichoke",
"image":null
]
}
How can I achieve this result?
Here is my serializers and my model class :
class Plot(models.Model):
name = models.CharField(max_length=50)
garden = models.ForeignKey('perma_gardens.Garden', on_delete=models.CASCADE)
width = models.CharField(max_length=50, blank=True, null=True)
height = models.CharField(max_length=50, blank=True, null=True)
plant = models.ManyToManyField('perma_plants.Plant', related_name='%(class)s_plant', blank=True)
# Here is my serializers :
class GardenSerializer(serializers.ModelSerializer):
class Meta:
model = Garden
fields = ('id', 'name',)
class PlantSerializer(serializers.ModelSerializer):
class Meta:
model = Plant
fields = ('id', 'name', 'image')
class ReadPlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True)
plant = PlantSerializer(many=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'
read_only_fields = [fields]
class WritePlotSerializer(serializers.ModelSerializer):
class Meta:
model = Plot
fields = '__all__'
And here is my views :
class PlotViewSet(viewsets.ModelViewSet):
queryset = Plot.objects.all()
def create(self, request, *args, **kwargs):
serializer = WritePlotSerializer(data=request.data, many=isinstance(request.data, list))
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)
def delete(self, request, pk):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def partial_update(self, request, *args, **kwargs):
instance = self.queryset.get(pk=kwargs.get('pk'))
serializer = WritePlotSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def get_serializer_class(self):
if self.action in ("list", "retrieve"):
return ReadPlotSerializer
return WritePlotSerializer
In the fuction partial_update you are utilizing the WritePlotSerializer, which only has the plant field implicitly through the fields=__all__ value. This is probably causing drf to use a PrimaryKeyRelatedField, and as such you don't get all the extra fields you defined in your PlantSerializer.
If I understand correctyl you want to use the WritePlotSerializer in the update but use the ReadPlotSerializer when returning the object. You probably should combine both of them in a single serializer by overriding the update method in order to support updating the nested Plant objects. Here is the related documentation.
Alternatively, if you don't want to update the Plants values you can use a slightly modified version of the ReadPlotSerializer in all calls:
class PlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True, read_only=True)
plant = PlantSerializer(many=True, read_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'

drf serializer data not showing all fields data properly

id field and name field not showing in result.
in models.py:
class Group(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)
admin = models.ForeignKey(User, on_delete=models.CASCADE)
member = models.ManyToManyField(User, related_name='groups_user')
def __str__(self):
return self.name
in serializers.py:
class SimpleUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','first_name', 'last_name')
class GroupSerializer(serializers.Serializer):
admin = SimpleUserSerializer()
class Meta:
model = Group
fields = ('id','name','admin')
views.py:
#api_view(['GET'])
#permission_classes((IsAuthenticated,))
def getSomeGroup(request):
allGroup = Group.objects.all().count()
randomGroupId = random.sample(range(allGroup), 3)
randomGroup = Group.objects.filter(id__in=randomGroupId)
serializer = GroupSerializer(randomGroup, many=True)
#print(serializer)
return Response(serializer.data)
the result comes like this:
[{"admin":{"id":1,"first_name":"asif","last_name":""}},{"admin":{"id":3,"first_name":"Test2","last_name":"lastname"}},{"admin":{"id":3,"first_name":"Test2","last_name":"lastname"}}]
why id and name field not showing?
class SimpleUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
First try to access all admin
#api_view(['GET'])
#permission_classes(IsAuthenticated)
def getSomeGroup(request):
randomGroup = Group.objects.all()
serializer = GroupSerializer(randomGroup, many=True)
return Response(serializer.data)
If that works there may be issue in your these two line
The Issue may be in these two lines
allGroup = Group.objects.all().count()
randomGroupId = random.sample(range(allGroup), 3)
Modify serializers.py:
class GroupSerializer(serializers.ModelSerializer):

DRF: how to change the value of the model fields before saving to the database

If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.

Django Rest Framework I can't use Serializer save model of have foreign key

I'm a Django Rest Framework and Django newbie
i can use random data to make stages but i can't use serializer to add new stages.
My model and serializer
class Stage(models.Model):
class Meta:
db_table = 'stage'
stage_id = models.AutoField(primary_key=True)
stage_name = models.CharField(max_length=64, null=False)
company = models.ForeignKey(
Company,
db_column='id',
on_delete=models.CASCADE,
)
class StageSerializer(ModelSerializer):
stage_id = IntegerField(read_only=True)
class Meta:
model = Stage
fields = [
'stage_id',
'stage_name',
'company',
]
def update(self, instance, validated_data):
pass
def create(self, validated_data):
# create stages
stage = create_stage(**validated_data)
return stage
view.py
class StageListAPIView(APIView):
def post(self, request, company_id):
data = request.data.copy()
company = get_company_by_id(company_id)
data['company'] = company.pk
serializer = StageSerializer(data=data)
if not serializer.is_valid(raise_exception=True):
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
new_data = serializer.validated_data
serializer.save(company=company)
return Response(new_data, status=HTTP_200_OK)
request.data
<QueryDict: {'stage_name': ['kAkSdKq9Gt'], 'company': [6]}>
i will receive error:
TypeError: Object of type Company is not JSON serializable
i can't understand it and i don't know how to use serializer to save foreign key.
You need to serialize the Company instance before you can include it in your StageSerializer.
A simple example would be something like
class CompanySerializer(ModelSerializer):
class Meta:
model = Company
fields = '__all__'
And then to include that in your StageSerializer:
class StageSerializer(ModelSerializer):
stage_id = IntegerField(read_only=True)
company = CompanySerializer(source='company', read_only=True)
class Meta:
model = Stage
fields = [
'stage_id',
'stage_name',
'company',
]

Related field id in POST request in Django Rest Framework

I am creating the object with just only related field id. I have searched a lot but couldn't get the answer. Here is my code
models.py:
class Resturant(models.Model):
name = models.CharField(_('name'), max_length=100)
class Menu(models.Model):
resturant_id = models.OneToOneField(Resturant, related_name='resturant', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
serializers.py:
class MenuSerializer(serializers.ModelSerializer):
resturant_id = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Menu
fields = ['id', 'created_at', 'resturant_id']
views.py:
class CreateMenuAPIView(APIView):
def post(self, request, *args, **kwargs):
serializer = MenuSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
I am getting this error while sending { "resturant_id": 2 } in POST request.
DETAIL: Failing row contains (14, 2018-04-02 09:36:43.261849+00, null).
The above exception (null value in column "resturant_id" violates not-null constraint
Any help would be appreciated !
you can override method create for find Restaurant object or create if not exist. and only edit serializer.
serializer.py
class MenuSerializer(serializers.ModelSerializer):
resturant_id = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Menu
fields = ['id', 'created_at', 'resturant_id']
def create(self, validated_data):
id_param = validated_data.pop('resturant_id')
resturant = Resturant.objects.get_or_create(id=id_param)[0]
menu = Menu.objtects.create(resturant_id=resturant.id)
return menu
if not work you can delete this line:
resturant_id = serializers.PrimaryKeyRelatedField(read_only=True)
returant_id = serializers.PrimaryKeyRelatedField(read_only=True)
Could you try giving read_only=False
Could you check the spelling,
returant_id is used in serializers field,'s' is missing.
'resturant_id' is used in fields list
You are using a model serializer and have overridden 'returant_id'.
class MenuSerializer(serializers.ModelSerializer):
returant_id = serializers.PrimaryKeyRelatedField(queryset=Resturant.objects.all())
try to change the serializer to
class MenuSerializer(serializers.ModelSerializer):
resturant_id = serializers.PrimaryKeyRelatedField()
class Meta:
model = Menu
fields = ['id', 'created_at', 'resturant_id']
if read_only=True then it will not write into database.

Categories

Resources