How to create values to foreign key model in DRF serializer? - python

When sending json to the server, it shows the following error:
Direct assignment to the reverse side of a related set is prohibited. Use items.set() instead.
Help me please. I recently started to study DRF, and I don't understand how to correctly write def create in django to write data to a foreign key model?
Here is my code
serializer.py
class ConsignmentNoteSerializer(serializers.ModelSerializer):
create_user = serializers.HiddenField(default=serializers.CurrentUserDefault())
create_user = UserSerializer(source='creator', read_only=True)
contragent_detail = ContragentSerializer(source='contragent', read_only=True)
items = ConsignmentItemSerializer(many=True)
class Meta:
model = ConsignmentNote
fields = ['id', 'doc_type', 'date', 'number', 'contragent_detail', 'comment', 'create_user', 'items', 'created']
**def create(self, validated_data):
items_data = self.validated_data.pop('items')
return ConsignmentNote.objects.create(**validated_data)**
Here is the json I am trying to send
{
"id": 9,
"doc_type": "capitalize",
"date": "2022-06-04",
"number": 98,
"contragent_id": 4,
"comment": "",
"items": [
{
"id": 18,
"product": 10,
"buy_price": "200.00",
"sell_price": "500.00",
"quantity": 5
},
],
}

I think You need to upload the item_ids data.
class ConsignmentNoteSerializer(serializers.ModelSerializer):
...
item_ids = serializers.ListField(
child = serializers.IntegerField,
write_only = True
)
class Meta:
model = ConsignmentNote
fields = ['id', 'doc_type', 'date', 'number', 'contragent_detail', 'comment', 'create_user', 'items', 'created', 'item_ids']
def create(self, validated_data):
item_ids = self.validated_data.pop('item_ids')
new_obj = ConsignmentNote.objects.create(**validated_data)
new_obj.set(item_ids)
return new_obj
And in this case, the payload should be
{
"doc_type": "capitalize",
"date": "2022-06-04",
"number": 98,
"contragent_id": 4,
"comment": "",
"item_ids": [18],
}

This solved my problem
def create(self, validated_data):
items = validated_data.pop('items')
note = ConsignmentNote.objects.create(**validated_data)
for item in items:
product = item.pop('product')
item = ConsignmentItem.objects.create(consignmentnote=note, product=product ,**item)
return note

Related

django rest framework update nested serializer by id

How to update a list in a nested serializer by id
I have a user who has multiple contacts
Example:
contact serializer
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = [
'id',
'name',
'last_name'
]
user serializer
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
contacts = ContactSerializer(many=True)
class Meta:
model = User
fields = [
"email",
"contacts"
]
def update(self, instance, validated_data):
contacts_data = validated_data.pop('contacts')
contacts = (instance.contacts).all()
contacts = list(contacts)
instance.name = validated_data.get('name', instance.name)
instance.save()
# many contacts
for contact_data in contacts_data:
contact = contacts.pop(0)
contact.name = contact_data.get('name', contact.name)
contact.last_name = contact_data.get('last_name', contact.last_name)
contact.save()
return instance
I want to update contacts by ID, for now it works, but it only updates the first contact in the list
Example I want to update the contact with ID 4 and 6
Payload request
{
"name": "string",
"contacts": [
{
"id": 4,
"name": "string",
"last_name": "string"
},
{
"id": 6,
"name": "string",
"last_name": "string"
}
]
}
Any ideas or recommendations?
Try it like this
def update(self, instance, validated_data):
contacts_data = validated_data.pop('contacts')
instance.name = validated_data.get('name', instance.name)
instance.save()
# many contacts
for contact_data in contacts_data:
contact = Contact.objects.get(pk=contact_data['id']) # this will crash if the id is invalid though
contact.name = contact_data.get('name', contact.name)
contact.last_name = contact_data.get('last_name', contact.last_name)
contact.save()
return instance

How to create post request in DRF

I m working on this POST request in drf and I lost it somewhere please help.
my models.py
class TargetDefination(models.Model):
targetName=models.CharField(max_length=50)
displayName=models.CharField(max_length=100)
def __str__(self):
return self.targetName
class Target(models.Model):
targetDefn=models.ForeignKey(TargetDefination,on_delete=models.CASCADE)
roleId=models.ForeignKey(Role,on_delete=models.CASCADE)
empId=models.ForeignKey(Employee,on_delete=models.CASCADE)
startDate= models.DateField(default=datetime.date.today)
endDate= models.DateField(null=True,blank=True)
value=models.PositiveIntegerField(default=0)
def __str__(self):
return str(self.empId) + ' ' +str(self.targetDefn)
serializer.py
class TargetSerializers(serializers.ModelSerializer):
targetDefn=TargetDefinationSerializers()
roleId=RoleSerializers()
empId=OnlyEmployeeSerializers()
class Meta:
model = Target
fields = (
'id',
'targetDefn',
'roleId',
'empId',
'startDate',
'endDate',
'value'
)
and this is what I have tried:
views.py
#api_view(['GET','POST'])
def setTarget(request, *args, **kwargs):
if request.method=='GET':
setTrgt=Target.objects.all()
serial=TargetSerializers(setTrgt,many=True)
return JsonResponse(serial.data,safe=False)
elif request.method == 'POST':
data=JSONParser().parse(request)
serial=TargetSerializers(data=data)
if serial.is_valid():
print("working")
target = serial.save()
serializer = TargetSerializers(target)
return JsonResponse(serializer.data,status=status.HTTP_200_OK,safe=False)
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I need to create POST request and the format will be:
{
"id": 6,
"targetDefn": {
"id": 1,
"targetName": "MIN SALES",
"displayName": "MIN SALES"
},
"roleId": {
"id": 1,
"roleName": "CEO",
"description": "Chief executive officer",
"roleReportsTo": null,
"roleReportsToName": null
},
"empId": {
"id": 5,
"empName": "Emp05",
"startDate": "2021-05-04",
"termDate": null
},
"startDate": "2021-05-05",
"endDate": null,
"value": 123
}
this is an example with just values in it.
errors I m getting:
The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `employee.serializers.TargetSerializers`, or set `read_only=True` on nested serializer fields.
You will have to implement a create() method in your serializer. The implementation of this method should break the request data and then write it into its respective models.
Here is how you can implement the method -
def create(self, validated_data):
#split the objects into multiple objects.
targetDef = validated_data.pop(targetDefn)
#save the objects into its respective models.
targetDefId = TargetDefination.objects.create(**targetDef)
#get the objects of roleId and empID
role = list(validated_data['roleId'].items())
role_id = Role.objects.get(roleName =role[0][1])
emp_id = Employee.objects.get(pk=validated_data['empId']['id'])
target_obj = Target.object.create(targetDef=targetDefId, roleId=role_id, empID=emp_id, startDate=validated_data['startDate'], endDate=validated_data['endDate'], value=validated_data['value'])
return target_obj

Django : This field is required on POST request

I'm trying to create a serializer which outputs the Report and also the User information.
My task is accomplished by this serializer:
class ReportSerializer(serializers.ModelSerializer):
latitude = serializers.CharField()
longitude = serializers.CharField()
city = serializers.IntegerField()
type = serializers.IntegerField()
# We have created a field which returns a value from get_marker_icon_url
marker_icon = serializers.SerializerMethodField('get_marker_icon_url')
status_str = serializers.SerializerMethodField('convert_status_toStr')
type_str = serializers.SerializerMethodField('convert_type_toStr')
status_color = serializers.SerializerMethodField('get_status_color')
likes = serializers.SerializerMethodField('random_likes')
user = ReportUserSerializer()
class Meta:
model = Reports
fields = [
'user',
'id',
'type',
'city',
'latitude',
'longitude',
'likes',
'type_str',
'status_str',
'status_color',
'attached_message',
'marker_icon',
'attached_photo',
'date_created'
]
...
With this code my serializer returns a response like this:
[
{
"user": {
"id": 1,
"username": "3nematix",
"profile_pic": "http://192.168.0.29:8000/frontend/static/frontend/images/reports/user_profile_pic.jpg",
"verified": false
},
"id": 1,
"type": 9,
"city": 0,
"latitude": "6.5123333",
"longitude": "51.512586",
"likes": 27,
"type_str": "OTHER",
"status_str": "PENDING",
"status_color": "orange",
"attached_message": "test",
"marker_icon": "OTHER",
"attached_photo": "http://192.168.0.29:8000/frontend/static/frontend/images/reports/user_profile_pic_VRjIYTs.jpg",
"date_created": "2020-10-21T23:19:06.899302Z"
},
......
]
And this is exactly what I need, but the problem is that when I'm trying to create a new object by a POST request, I get this response:
{
"user": [
"This field is required."
]
}
If I would remove 'user' from Meta and user = ReportUserSerializer() from the ReportSerializer class, then I can create a new object, but when I wish to get the Reports I with the Users information I need to add these two again, how can I fix it?
You need to fill the user field yourself before calling save.
Here's the easy way:
class YourView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Or slightly differently:
class YourView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.validated_data['user'] = self.request.user
return super().perform_create(serializer)

How to create multiple model instances without duplicated writable nested serializer in Django REST Framework?

I have two related models: Product and ProductDescription. In 1 submit action user able to insert a new Product with multiple descriptions depend on the available languages. I use writable nested serializer to insert into Product and ProductDescription simultaneously. I do it by overriding create function in ProductDescriptionSerializer class, it works. However, I can only insert 1 ProductDescription at a time.
Then I tried to use this answer to create multiple model instances at once. The problem is it also creates the same Product twice instead of using the newly created Product Id to insert the next ProductDescription.
My models.py:
class Product(models.Model, ProductStatus):
product_code = models.CharField(max_length=6)
color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
video = models.URLField(verbose_name='Video URL', max_length=250, null=True, blank=True)
status = models.CharField(max_length=20, choices=ProductStatus.status, default=ProductStatus.active)
class ProductDescription(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
description = models.TextField(max_length=500, null=True, blank=True)
def __str__(self):
return '%s - %s' % (self.product, self.language)
My serializers.py:
class CustomRelatedField(serializers.RelatedField):
def display_value(self, instance):
return instance
def to_representation(self, value):
return str(value)
def to_internal_value(self, data):
model = self.queryset.model
return model.objects.get(id=data)
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
class ProductDescriptionSerializer(serializers.ModelSerializer):
product = ProductSerializer()
language = CustomRelatedField(many=False, queryset=Language.objects.all())
class Meta:
model = ProductDescription
fields = ['id', 'product', 'language', 'description']
def to_representation(self, instance):
data = super().to_representation(instance)
if self.context['request'].method == 'GET':
data['product'] = instance.product.product_code
return data
return Serializer.to_representation(self, instance)
# The `.create()` method does not support writable nested fields by default.
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product = Product.objects.create(**product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
My views.py:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
class ProductDescriptionView(CreateListModelMixin, viewsets.ModelViewSet):
permission_classes = [permissions.DjangoModelPermissions]
queryset = ProductDescription.objects.all()
serializer_class = ProductDescriptionSerializer
http_method_names = ['get', 'head', 'post', 'put', 'patch', 'delete']
The JSON format I use to POST data:
[
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 1,
"description": "English description."
},
{
"product": {
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
"language": 2,
"description": "Vietnamese description."
}
]
It creates a duplicate Product in Product List:
[
{
"id": 26,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
},
{
"id": 27,
"product_code": "BQ1080",
"collection": 5,
"color": 7,
"video": "https://www.youtube.com/watch?v=",
"status": "Continue"
}
]
The ProductDescription datas are correct though:
[
{
"id": 5,
"product": "BQ1080",
"language": "English",
"description": "English description."
},
{
"id": 6,
"product": "BQ1080",
"language": "Vietnam",
"description": "Vietnamese description."
}
]
To avoid duplicate product you can use get_or_create() method:
class ProductDescriptionSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
# create product data for Product model.
product_data = validated_data.pop('product')
product_code = product_data.pop("product_code")
product, _ = Product.objects.get_or_create(product_code=product_code, defaults=product_data)
# create ProductDescription and set product FK.
product_description = ProductDescription.objects.create(product=product, **validated_data)
# return ProductDescription instance.
return product_description
Note that get_or_create is prone to race condition. So if two same requests came to you service at the same time you may still have duplicate products.
I think you need to override your ProductSerializer's create method. Maybe you can try like this:
class ProductSerializer(serializers.ModelSerializer):
collection = CustomRelatedField(queryset=ProductCollection.objects.all(), many=False)
color = CustomRelatedField(queryset=ColorParent.objects.all(), many=False)
def create(self, validated_data):
instance, _ = Product.objects.get_or_create(**validated_data)
return instance
class Meta:
model = Product
fields = ['id', 'product_code', 'collection', 'color', 'video', 'status']
So that, first it will try to get if the Product exists, else create the instance(hence reducing duplicate entry).
ForeignKey not for this job you should use ManyToManyField

Why i can't to update nested serializer by id?

When i send data throw PUT request this id can't get this id in validated data.
For example body of PUT request:
{
"slug": "string",
"company_id": 1,
"api_secret": "string",
"merchant_url_list": [
{
"id": 1
"merchant_url_type_id": 1,
"url": "string"
}
]
}
But in validated_data {'slug': 'string', 'company': <Company: Company object (1)>, 'api_secret': 'string', 'merchanturl_set': [OrderedDict([('merchant_url_type', <MerchantUrlType: Бот (None)>), ('url', 'string')])]}
There is not id. How i can get id?
class MerchantUrlSerializer(IdModelSerializer):
merchant = serializers.PrimaryKeyRelatedField(required=False, read_only=True)
class Meta:
model = MerchantUrl
fields = ('id', 'merchant_url_type', 'merchant', 'url')
class MerchantSerializer(NestedSerializerMixin, IdModelSerializer):
merchant_url_list = MerchantUrlSerializer(source='merchanturl_set', many=True)
class Meta:
model = Merchant
fields = ('id', 'slug', 'company', 'api_secret', 'merchant_url_list')
{
"slug": "string",
"company_id": 0,
"api_secret": "string",
"merchant_url_list": [
{
"merchant_url_type_id": 0,
"url": "string"
}
]
}
The pk field - id in your case - is read_only by default since it represents the PK.
If you want to alter that behaviour, you'll need to set it read/write:
class MerchantUrlSerializer(IdModelSerializer):
merchant = serializers.PrimaryKeyRelatedField(required=False, read_only=True)
class Meta:
model = MerchantUrl
fields = ('id', 'merchant_url_type', 'merchant', 'url')
extra_kwargs = {'id': {'read_only': False}}

Categories