1
votes

I have some problem with handling nested serializers (DRF).

I want to collect data with some structure like this:

{
    'some_tekst': 'test_text',
    'measurements': [
        {'int_test': 1, 'char_test': '1'},
        {'int_test': 2, 'char_test': '2'}
    ]
}

I'm using the documentation (https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects) and i'm looking at (https://medium.com/@raaj.akshar/creating-reverse-related-objects-with-django-rest-framework-b1952ddff1c) and still have the HTTP 400 response:

'{"measurements":["This field is required."]}'

models.py

class Data(models.Model):
    datetime = models.CharField(max_length=100)

    def __str__(self):
        return 'just testing'


class Measurement(models.Model):
    # EDIT:
    # data = models.ForeignKey(Data, on_delete=models.CASCADE) #deleted

    char_test = models.CharField(max_length=100)
    test = models.IntegerField(default=0)


    def __str__(self):
        return self.char_test

serializers.py:

class MeasurementSerializer(serializers.ModelSerializer):
    class Meta:
        model = Measurement
        fields = '__all__'

class DataSerializer(serializers.ModelSerializer):
    # EDIT:
    #measurements = MeasurementSerializer(many=True)
    measurements = MeasurementSerializer(many=True, source='measurement_set')

    class Meta:
        model = Data
        fields = ['datetime', 'measurements']

    # EDIT:
    #def create(self, validated_data):
    #    measurement_validated_data = validated_data.pop('measurements')
    #    data = Data.objects.create(**validated_data)
    #    Measurement.objects.create(data=data, **measurement_validated_data)
    #    return data
    def create(self, validated_data):
        measurement_validated_data = validated_data.pop('measurements')
        data = Data.objects.create(**validated_data)
        for measurement_data in measurement_validated_data:
            Measurement.objects.create(data=data, **measurement_data)
        return data

views.py

class DataViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    serializer_class = DataSerializer
    queryset = Data.objects.all()


class MeasurementViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    serializer_class = MeasurementSerializer
    queryset = Measurement.objects.all()

And my simple post request:

data = {
    "datetime": "testdatatime",
    "measurements": [
        {'test': 1, 'char_test': '1'},
        {'test': 2, 'char_test': '2'},
    ],
}
r = requests.post(
    'http://127.0.0.1:8000/api/data/',
    data=data,
)

Did I forget something?

Edit: There is API endpoint from DRF:

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "name": "Data List",
    "description": "API endpoint that allows groups to be viewed or edited.",
    "renders": [
        "application/json",
        "text/html"
    ],
    "parses": [
        "application/json",
        "application/x-www-form-urlencoded",
        "multipart/form-data"
    ],
    "actions": {
        "POST": {
            "datetime": {
                "type": "string",
                "required": true,
                "read_only": false,
                "label": "Datetime",
                "max_length": 200
            },
            "measurements": {
                "type": "field",
                "required": true,
                "read_only": false,
                "label": "Measurements",
                "child": {
                    "type": "nested object",
                    "required": true,
                    "read_only": false,
                    "children": {
                        "id": {
                            "type": "integer",
                            "required": false,
                            "read_only": true,
                            "label": "ID"
                        },
                        "char_test": {
                            "type": "string",
                            "required": true,
                            "read_only": false,
                            "label": "Char test",
                            "max_length": 100
                        },
                        "test": {
                            "type": "integer",
                            "required": false,
                            "read_only": false,
                            "label": "Test",
                            "min_value": -2147483648,
                            "max_value": 2147483647
                        }
                    }
                }
            }
        }
    }
}

Edit 2: Error on the django side (console)

django_web  | Bad Request: /api/data/
django_web  | [31/Mar/2020 22:54:39] "POST /api/data/ HTTP/1.1" 400 44
1

1 Answers

2
votes

DRF is unable to figure out the source for the measurement serializer field. Do one of the following:

  1. Add a related_name:
class Measurement(models.Model):
    data = models.ForeignKey(Data, on_delete=models.CASCADE, related_name='measurements')
  1. Specify a source attribute to the serializer field
class DataSerializer(serializers.ModelSerializer):
    measurements = MeasurementSerializer(many=True, source='measurement_set')

Do one or the other, not both.

Also, there's a logical error in your create function

    def create(self, validated_data):
        measurement_validated_data = validated_data.pop('measurements') # Remember, this is an array
        data = Data.objects.create(**validated_data)
        for measurement_data in measurement_validated_data:
            Measurement.objects.create(data=data, **measurement_data)
        return data