I have a nested serializer, the create method does not work. I have searched over the internet and no method worked for me. The most popular one is the one listed below.
models.py
class Displays(models.Model):
id = models.CharField(primary_key=True, max_length=32,
default=generate_uuid)
name = models.CharField(max_length=45, blank=True, null=True)
class OrdersDj(models.Model):
id = models.CharField(primary_key=True, max_length=32,
default=generate_uuid)
class AdsDj(models.Model):
id = models.CharField(primary_key=True, max_length=32,
default=generate_uuid)
order = models.ForeignKey(
OrdersDj, on_delete=models.CASCADE, blank=False, null=True)
display = models.ForeignKey(
Displays, on_delete=models.CASCADE, blank=True, null=True)
serializers.py
class DisplaySerializer(serializers.ModelSerializer):
class Meta:
model = Displays
fields = "__all__"
class AdsSerializer(serializers.ModelSerializer):
display = DisplaySerializer()
def create(self, validated_data):
print("validated_data", validated_data)
display_id = validated_data.pop('display')
display = Displays.objects.get(id=display_id)
ad = Displays.objects.create(display=display, **validated_data)
return ad
class Meta:
model = Ads
fields = "__all__"
class OrderSerializer(serializers.ModelSerializer):
ads = AdsSerializer(source="adsdj_set", many=True)
def create(self, validated_data):
validated_data.pop('adsdj_set')
order = Orders.objects.create(**validated_data)
return order
class Meta:
model = Orders
fields = "__all__"
views.py
class AdsCreate(APIView):
def put(self, request):
print('request.data', request.data)
serializer = serializers.AdsSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print('serializer.data > serializer.valid', serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
print('serializer.errors > serializer.invalid', serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
print outs
request.data <QueryDict: {'order': ['18bb2225cf6e407a943f2941072d06de'], 'display': ['91'], 'firstday': ['2021-12-4'], 'lastday': ['2021-12-21'], 'duration': ['17'], 'filetype': ['image/png'], 'originalname': ['Picture1.png'], 'price': ['2550'], 'status': ['0'], 'filehash': ['not available yet'], 'orderByNum': ['-1'], 'imgWidth': ['1061'], 'imgHeight': ['708'], 'image': [<InMemoryUploadedFile: Picture1.png (image/png)>]}>
serializer.errors > serializer.invalid {'display': [ErrorDetail(string='This field is required.', code='required')]}
I am giving the display field to the serializer that is the id of the display. In the create method I find that display and reference it in the new ad object. The display field is present in the request.data and passed to the serializer, the serializer complains that the display field is not present.
One version of sending json data
{"order":"18bb2225cf6e407a943f2941072d06de","display":{"id":91},"firstday":"2021-12-5","lastday":"2021-12-15","duration":11,"image":{},"filetype":"image/png","originalname":"Picture1.png","price":1650,"status":0,"filehash":"not available yet","orderByNum":-1,"imgWidth":1061,"imgHeight":708}
Try using different serializers for creation and representation.
class AdsCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Ads
fields = "__all__"
class AdsSerializer(serializers.ModelSerializer):
display = DisplaySerializer()
class Meta:
model = Ads
fields = "__all__"
For display field, AdsSerializer expects the following structure;
"display": {"id": 91}
whereas you send this;
"display": 91
Related
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__'
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
In my case,I use JWT authentication, and when I create new "Post"(my model), I want automatically set author to user that request it.But when I do it, I got an error
{
"author": [
"This field is required."
]
}
I know,I'm not passing user, but I want to set it automatically, and I dont know how.
I just want to know how to avoid error, because when I pass value, that allows me to go ahead, the user is set automatically from context.
Serializer
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = ('title','content',author','category','likedBy')
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}
def create(self, validated_data):
user = self.context['request'].user
post = Post.objects.create(
author=user, title=validated_data['title'], content=validated_data['content'])
post.category.set(validated_data['category'])
return post
My create view
class PostCreate(generics.CreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
Model
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
and when I create
You can make author a read-only field, or if you're just using this serializer to create users and not retreive them. You can just remove 'author' from fields in the serializer meta.
Read-only field
from rest_framework import serializers
class PostSerializer(FlexFieldsModelSerializer):
author = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Post
fields = ('title','content','author','category','likedBy')
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',
]
I'm a little new to Django and Django-REST so please bear with me. Perhaps the answer is in the documentation, so if I missed it, apologies in advance.
Goal: I would like to create an EquipmentInfo object whose attributes include pre-existing foreign keys (EquipmentType and EquipmentManufacturer).
models.py
class EquipmentType(models.Model):
equipment_type = models.CharField(verbose_name="Equipment Type", max_length=50, unique=True)
def __unicode__(self):
return self.equipment_type
class EquipmentManufacturer(models.Model):
manufacturer_name = models.CharField(verbose_name="Manufacturer Name", max_length=50, unique=True)
def __unicode__(self):
return self.manufacturer_name
class EquipmentInfo(models.Model):
equipment_type = models.ForeignKey(EquipmentType, verbose_name="Equipment Type")
part_identifier = models.CharField(verbose_name="Machine ID (alias)", max_length=25)
manufacturer_name = models.ForeignKey(EquipmentManufacturer, verbose_name="Manufacturer Name")
serial_number = models.CharField(verbose_name="Serial Number", max_length=25)
date_of_manufacture = models.DateField(verbose_name="Date of Manufacture", default=date.today)
is_active = models.BooleanField(verbose_name="Is Active", default=True)
def __unicode__(self):
return self.part_identifier
serializers.py
class EquipmentTypeSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentType
fields = ('id', 'equipment_type',)
class EquipmentManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentManufacturer
fields = ('id', 'manufacturer_name',)
class EquipmentInfoSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentInfo
fields = ('id', 'equipment_type', 'part_identifier', 'manufacturer_name','serial_number', 'date_of_manufacture', 'is_active')
equipment_type = EquipmentTypeSerializer(many=False)
manufacturer_name = EquipmentManufacturerSerializer(many=False)
def create(self, validated_data):
equipment_type = validated_data.pop('equipment_type')
manufacturer_name = validated_data.pop('manufacturer_name')
equipment_info = EquipmentInfo.objects.create(**validated_data)
return equipment_info
Assuming I already have relevant EquipmentType and EquipmentManufacturer objects created, I would like to add another EquipmentInfo object. What is the appropriate way to set up my EquipmentInfo serializer so that I can pass in information such as
{
"equipment_type":{
"equipment_type":"already_created",
},
"part_identifier":"something_new",
"manufacturer_name":{
"manufacturer_name":"already_created"
},
"serial_number":"WBA1",
"date_of_manufacture": "1900-01-01",
"is_active":true
}
or even better:
{
"equipment_type":"already_created",
"part_identifier":"something_new",
"manufacturer_name":"already_created",
"serial_number":"WBA1",
"date_of_manufacture": "1900-01-01",
"is_active":true
}
Any help is appreciated.
I have also faced the problem ,and have solved it ,the following is my step ,hope it will be helpful
1.company Model and contact model as follows:
class Company(models.Model):
Company_Name = models.CharField(u'Company Name',max_length=255, default="")
Modified_By = models.CharField(u'Modified By',max_length=255, default="")
class Company_Contact(models.Model):
Company = models.ForeignKey(Company)
Last_Name = models.CharField(u'Last Name',max_length=255, default="")
First_Name = models.CharField(u'First Name',max_length=255, default="")
2.I create A New Serializer Named CompanyReferenceSerializer,and company_contact
class CompanyReferenceSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ['id', 'Company_Name', 'Company_Name_SC']
class CompanyContactSerializer(serializers.ModelSerializer):
Company = CompanyReferenceSerializer()
class Meta:
model = Company_Contact
fields = ['Company', 'Last_Name','First_Name']
extra_kwargs = {
'Company': {'allow_null': True, 'required': False},
'Last_Name': {'allow_null': True, 'allow_blank': True, 'required': False},
'First_Name': {'allow_null': True, 'required': False, 'allow_blank': True},
}
3.Viewset as follows,in the backend,I get the object Namedcompany_instanceaccording to the 'company_id'
class CompanyContactViewSet(viewsets.ModelViewSet):
serializer_class = CompanyContactSerializer
def create(self, validated_data):
serializer = self.get_serializer(data=self.request.data)
company_id_for_contact = self.request.data.pop('Company_id')
company_instance = Company.objects.filter(id=company_id_for_contact).first()
if not serializer.is_valid():
print serializer.errors
data = serializer.validated_data
serializer.save(Company=company_instance)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
and I success insert one record in the company_contact ,Hope it can help you !
Using nested serializers makes it really hard for posts (if it even works, as it didn't used to work), and given your simple models, I would recommend just removing them.
I will recommend you add APIs for
/api/v1/type
/api/v1/manufacturer
/api/v1/info
(or whatever names you want to use). The type and manufacturer ones should be vanilla views and using your existing serializers.
For info, remove the two nested serializers:
class EquipmentInfoSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentInfo
fields = ('id', 'equipment_type', 'part_identifier', 'manufacturer_name','serial_number', 'date_of_manufacture', 'is_active')
After that, you should be able to do post using:
data = {
"equipment_type": 5, # The ID of the equipment type record
"part_identifier":"something_new",
"manufacturer_name": 10 # The ID of the manufacturer record
"serial_number":"WBA1",
"date_of_manufacture": "1900-01-01",
"is_active":true
}
In my case, I do like making it the GETs more convenient so I add read-only fields to return a name (or even the whole serialized record):
class EquipmentInfoSerializer(serializers.ModelSerializer):
type_name = serializers.SerializerMethodField(read_only=True)
class Meta:
model = EquipmentInfo
fields = ('id', 'equipment_type', 'part_identifier', 'manufacturer_name','serial_number', 'date_of_manufacture', 'is_active')
def get_type_name(self, obj):
return obj.equipment_type.equipment_type
Hope this helps.