SCENARIO
I have a many-to-many relationship between two models:
Supplier
class Supplier(models.Model):
class Meta:
unique_together = ['supplier_no', 'supplier_name']
ordering = ['supplier_name']
supplier_no = models.IntegerField(blank=False, null=False)
supplier_name = models.CharField(max_length=180)
...
updated = models.DateTimeField(auto_now=True, blank=True)
updated_by = models.ForeignKey(UsaUser, on_delete=models.CASCADE, blank=True, null=True,
related_name='supplierUpdatedByUser')
def __str__(self):
return self.supplier_name
Plant
class Plant(models.Model):
class Meta:
unique_together = ['plant_no', 'plant_name']
ordering = ['plant_no']
plant_no = models.IntegerField(unique=True)
plant_name = models.CharField(max_length=180, unique=True)
...
suppliers = models.ManyToManyField(Supplier, related_name='plants')
updated = models.DateTimeField(auto_now=True, blank=True)
updated_by = models.ForeignKey(UsaUser, on_delete=models.CASCADE, blank=True, null=True,
related_name='plantUpdatedByUser')
def __str__(self):
return self.plant_name
Simply put: a supplier can be active in any number of plants, and a plant can have any number of suppliers.
And here is the SupplierSerializer with the create method in question:
class SupplierSerializer(serializers.ModelSerializer):
plants = PlantSerializer(many=True) # serializes entire supplier objects instead of just returning the pk
class Meta:
model = Supplier
fields = ['id', 'supplier_no', 'supplier_name', ... 'plants']
def create(self, validated_data):
# Add the UsaUser as a blameable field, which will be passed in the 'context' object to the serializer.
validated_data.update({"updated_by": self.context['request'].user})
assoc_plants = validated_data.pop('plants') # remove the many-to-many association from the data before saving
supplier = Supplier.objects.create(**validated_data)
# Now add in the associated plants
for plant in assoc_plants:
supplier.plants.add(plant)
return supplier
...
PROBLEM:
When creating a Supplier, I get the following 400 response:
{"plants":[{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]},{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}]
If I remove the following line from my serializer:
plants = PlantSerializer(many=True)
the problem is resolved. However, I want full plant objects to be returned to my front end, not just the plant ids.
I thought maybe I needed to return the full Plant object since the error says it's looking for a dictionary, but then I get another error:
{"plants":[{"plant_no":["plant with this plant no already exists."],"plant_name":["plant with this plant name already exists."]}]}
Sample Request Payload
{"supplier_no":"5052","supplier_name":"MySupplier","is_active":true,"plants":[1,10]}
^^ 1 and 10 are the pks of the Plants
When passing a whole Plant object:
{"supplier_no":"54564","supplier_name":"MySuppleir","is_active":true,"plants":[{"id":1,"plant_no":1,"plant_name":"AutoPlant1","is_active":true...}]}
I think you want to use the plants that are already created. Then you can use some extra fields.
class SupplierSerializer(serializers.ModelSerializer):
plants = PlantSerializer(many=True, read_only = True) # set it as read_only
plant_ids = serializers.ListField(
child = serializers.IntegerField(),
write_only = True
)
class Meta:
model = Supplier
fields = [..., 'plant_ids'] # add plant_ids
def create(self, validated_data):
plant_ids = validated_data.pop('plant_ids')
supplier = Supplier.objects.create(**validated_data)
supplier.plants.set(plant_ids)
return supplier