Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid data. Expected a dictionary, but got str error with serializer field in Django Rest Framework

I'm using Django 2.x and Django REST Framework.

I have two models like

class Contact(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, on_delete=models.PROTECT)
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100, blank=True, null=True)
    modified = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

class AmountGiven(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
    amount = models.FloatField(help_text='Amount given to the contact')
    given_date = models.DateField(default=timezone.now)
    created = models.DateTimeField(auto_now=True)

the serializer.py the file has serializers defined as

class ContactSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Contact
        fields = ('id', 'first_name', 'last_name', 'created', 'modified')

class AmountGivenSerializer(serializers.ModelSerializer):
    contact = ContactSerializer()

    class Meta:
        model = AmountGiven
        depth = 1
        fields = (
            'id', 'contact', 'amount', 'given_date', 'created'
        )

views.py

class AmountGivenViewSet(viewsets.ModelViewSet):
    serializer_class = AmountGivenSerializer

    def perform_create(self, serializer):
        save_data = {}
        contact_pk = self.request.data.get('contact', None)
        if not contact_pk:
            raise ValidationError({'contact': ['Contact is required']})
        contact = Contact.objects.filter(
            user=self.request.user,
            pk=contact_pk
        ).first()
        if not contact:
            raise ValidationError({'contact': ['Contact does not exists']})
        save_data['contact'] = contact
        serializer.save(**save_data)

But when I add a new record to AmountGiven model and passing contact id in contact field

enter image description here

it is giving error as

{"contact":{"non_field_errors":["Invalid data. Expected a dictionary, but got str."]}}

When I remove contact = ContactSerializer() from AmountGivenSerializer, it works fine as expected but then in response as depth is set to 1, the contact data contains only model fields and not other property fields defined.

like image 512
Anuj TBE Avatar asked Oct 18 '18 06:10

Anuj TBE


2 Answers

I'm not a big fan of this request parsing pattern. From what I understand, you want to be able to see all the contact's details when you retrieve an AmountGiven object and at the same time be able to create and update AmountGiven by just providing the contact id.

So you can change your AmountGiven serializer to have 2 fields for the contact model field. Like this:

class AmountGivenSerializer(serializers.ModelSerializer):
    contact_detail = ContactSerializer(source='contact', read_only=True)

    class Meta:
        model = AmountGiven
        depth = 1
        fields = (
            'id', 'contact', 'contact_detail', 'amount', 'given_date', 'created'
        )

Note that the contact_detail field has a source attribute.

Now the default functionality for create and update should work out of the box (validation and everything).

And when you retrieve an AmountGiven object, you should get all the details for the contact in the contact_detail field.

Update

I missed that you need to check whether the Contact belongs to a user (however, I don't see a user field on your Contact model, maybe you missed posting it). You can simplify that check:

class AmountGivenViewSet(viewsets.ModelViewSet):
    serializer_class = AmountGivenSerializer

    def perform_create(self, serializer):
        contact = serializer.validated_data.get('contact')
        if contact.user != self.request.user:
            raise ValidationError({'contact': ['Not a valid contact']})
        serializer.save()
like image 120
slider Avatar answered Nov 13 '22 15:11

slider


Override the __init__() method of AmountGivenSerializer as

class AmountGivenSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        super(AmountGivenSerializer, self).__init__(*args, **kwargs)
        if 'view' in self.context and self.context['view'].action != 'create':
            self.fields.update({"contact": ContactSerializer()})

    class Meta:
        model = AmountGiven
        depth = 1
        fields = (
            'id', 'contact', 'amount', 'given_date', 'created'
        )


Description
The issue was the DRF expects a dict like object from contact field since you are defined a nested serializer. So, I removed the nested relationship dynamically with the help of overriding the __init__() method

like image 1
JPG Avatar answered Nov 13 '22 15:11

JPG