1

I'm trying to create a writeable nested serialiser for my API endpoint, however upon entering my parent serialiser's create method the nested data doesn't appear in my validated_data dictionary like it is supposed to in the example here. Instead, the nested key is not even present in the dictionary. Instead, it looks like: {'foo': 'bar'}. So, the nested keys appear to be flattened, and any other nested object with the same keys are overwritten.

Any clues as to what the problem might be? I have some fairly complicated validation logic, however after trimming all of this out the problem wasn't recitified, so it appears to be irrelevant.

My models are defined thus:

class Payment(models.Model):
    id = models.AutoField(primary_key=True)
    foo = models.CharField(max_length=15, blank=True, null=True)

class Booking(models.Model):
    id = models.AutoField(primary_key=True)
    payment = models.ForeignKey(Payment, blank=True, null=True)

My serializers:

class PaymentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Payment
        fields = '__all__'

class BookingSerializer(serializers.ModelSerializer):
    payment = PaymentSerializer(source='*', write_only=True)

    def create(self, validated_data):
        print("Creating booking", validated_data) # Outputs "Creating booking {'foo': 'bar'}"
        payment_data = validated_data.pop('payment') # Obviously errors at this point
        primary_guest = Payment.objects.create(payment_data)
        booking = Booking.objects.create(**validated_data)
        # other creation related code

        return booking

    class Meta:
        fields = '__all__'

My ViewSet:

class PrebookingViewSet(viewsets.ModelViewSet):
    queryset = Booking.objects.all().order_by('id')
    serializer_class = BookingSerializer

My request contains the following POST body:

{
    "payment": {
        "foo": "bar"
    }
}
andrensairr
  • 452
  • 2
  • 5
  • 17
  • What are you expecting here -- many foos under a payment? – gregory Sep 05 '18 at 00:26
  • No, I'm expected the validated_data dictionary to be nested as with the documentation example, linked. Much like the request body. My example is highly simplified. – andrensairr Sep 05 '18 at 00:39

2 Answers2

1

The thing is you've defined as source='*',.

From the DRF Serializer Doc

The value source='*' has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation

That is, your payload {"payment": {"foo": "bar"}} will goes into PaymentSerializer instead of {"foo": "bar"}


Solution
Simply remove the source='*' ;)

class BookingSerializer(serializers.ModelSerializer):
    payment = PaymentSerializer(write_only=True)

    def create(self, validated_data):
        # your code
JPG
  • 82,442
  • 19
  • 127
  • 206
  • That's right, thanks, I just solved it myself. Any hints as to why it had that effect on validated_data in the top-level serialiser? It doesn't seem intuitive. – andrensairr Sep 05 '18 at 02:46
  • I think the link I provided is self-explanatory, isn't it? – JPG Sep 05 '18 at 03:13
0

It appears after all the troubleshooting I've done, I stumbled on the solution to the problem more or less while coming up with an example to share in my question. It's a good exercise. The problem was caused by my nested serialiser field definition.

Instead of:

payment = PaymentSerializer(source='*', write_only=True)

I needed:

payment = PaymentSerializer(write_only=True)

My validated_data dictionary now contains a nested dictionary under the payment key.

I'm not sure why I'd included that, I think I've used it elsewhere to unflatten fields into a nested representation. I can't seem to find any documentation on how it's suppoed to work though, and why it caused the behaviour I've seen here. Any input on this is welcome!

andrensairr
  • 452
  • 2
  • 5
  • 17