5
votes

I'm trying to create a writable nested serializer. My parent model is Game and the nested models are Measurements. I am trying to post this data to my DRF application using AJAX. However, when try to post the data, the nested Measurements are empty OrderedDict().

Here are my models:

class Game(models.Model):
    start_timestamp = models.DateTimeField(auto_now_add=False)
    end_timestamp = models.DateTimeField(auto_now_add=False)
    date_added = models.DateTimeField(auto_now_add=True)


class Measurement(models.Model):
    game = models.ForeignKey(Game, on_delete=models.PROTECT, related_name='measurements')
    measurement_type = models.CharField(max_length=56)
    measurement = models.CharField(max_length=56)
    timestamp = models.DateTimeField(auto_now_add=False)
    date_added = models.DateTimeField(auto_now_add=True)

Here are my serializers:

class MeasurementSerializer(serializers.ModelSerializer):
        timestamp = serializers.DateTimeField(input_formats=(['%Y-%m-%d %H:%M:%S.%Z', 'iso-8601']), required=False)

        class Meta:
            model = Measurement
            fields = ('measurement_type', 'measurement', 'timestamp')


class GameSerializer(serializers.ModelSerializer):
    start_timestamp = serializers.DateTimeField(input_formats=(['%Y-%m-%d %H:%M:%S.%Z', 'iso-8601']))
    end_timestamp = serializers.DateTimeField(input_formats=(['%Y-%m-%d %H:%M:%S.%Z', 'iso-8601']))
    measurements = MeasurementSerializer(many=True)

    class Meta:
        model = Game
        fields = ('id', 'start_timestamp', 'end_timestamp', 'measurements')

    def create(self, validated_data):
        measurements = validated_data.pop('measurements')
        game = Game.objects.create(**validated_data)
        for measurement in measurements:
            Measurement.objects.create(game=game, **measurement)
        return game

My view for Game is the following:

class GameList(generics.ListCreateAPIView):
    queryset = Game.objects.all()
    serializer_class = GameSerializer

I followed this tutorial for the structure.

I am trying to post to this API via AJAX, the code below:

 $.ajax({
         url: base_url + '/games/',
         dataType: "json",
         data: {
                "start_timestamp": "2016-02-16 14:51:43.000000",
                "end_timestamp": "2016-02-16 14:53:43.000000",
                "measurements":[
                    {'measurement_type':'type1', 'measurement':'71', 'timestamp':'2016-02-16 14:53:43.000000'},
                    {'measurement_type':'type1', 'measurement':'72', 'timestamp':'2016-02-16 14:54:43.000000'},
                    {'measurement_type':'type1', 'measurement':'73', 'timestamp':'2016-02-16 14:55:43.000000'},
                ]
                },
         type: 'POST'
     })
     .error(function(r){})
     .success(function(data){})
 });

On posting this data, I find in the create method within the GameSerializer that the validate_data.pop('measurements') contains a list of 3 ordered dictionaries (OrderedDict()) that are empty.

UPDATE: I've found that that the initial_data coming in via request.data is structured like so:

'emotion_measurements[0][measurement_type]' (4397175560) = {list} ['type1']
'emotion_measurements[0][measurement]' (4397285512) = {list} ['71']
'emotion_measurements[0][timestamp]' (4397285600) = {list} ['2016-02-16 14:53:43.000000']
'emotion_measurements[1][measurement_type]' (4397175040) = {list} ['type1']
'emotion_measurements[1][measurement]' (4397285864) = {list} ['72']
'emotion_measurements[1][timestamp]' (4397285952) = {list} ['2016-02-16 14:54:43.000000']
'emotion_measurements[2][measurement_type]' (4397175040) = {list} ['type1']
'emotion_measurements[2][measurement]' (4397285864) = {list} ['73']
'emotion_measurements[2][timestamp]' (4397285952) = {list} ['2016-02-16 14:55:43.000000']

Has anyone encountered this issue before? Thanks!

UPDATE #2

I was able to resolve this (although I believe it is more of a workaround than a solution) by adding the following to my MeasurementSerializer:

def to_internal_value(self, data):
        formatted_data = json.dumps(data)
        formatted_data = formatted_data.replace("[", "").replace("]","")
        formatted_data = json.loads(formatted_data)
        return formatted_data

The Measurement data coming in was a QueryDict when I believe I needed a Dict. There were also some extra brackets around the key and values so I had to remove those as well.

Still seeking a better answer than this!

1
Could you check what's in self.initial_data.get('measurements')? And also, just to be on the safe side: check what JSON your server has received. (Your AJAX code looks fine and it most certainly reaches the server just as intended - but well...)Risadinha
The only think I can find after looking over your code several times is that maybe (maybe!) your timestamp fails to parse according to your pattern. I cannot find anything else, also comparing to my own serializers and to the DRF example. As the timestamp is optional, maybe test it without these values, just to make sure. But if that is the problem, the question would be why you aren't seeing the validation errors.Risadinha
@Risadinha I've updated with the initial_data structure. The structure is a QueryDict, which I'm not too familiar with. I also had the same train of thought making timestamp not required in case it was a date time formatting issue but still the same issue.Nick S.
Your "workaround" looks - hm - not satisfying using (de)serialization inside a serializer to resolve a serialization problem. Imho, it should be better to transform it in python, without json (de)serialization. If you want to go on trying other solutions, you could: remove the many=True and add source='measurements'. (I have several m2m serializers that do not specify many (they do override to_internal_value and to_representation, though).Risadinha
I wonder, don't DRF developers really understand that without a working solution for writable nested serializers out-of-the-box, nobody's going to use DRF? I've been f***ing with writing a custom implementation for 2 weeks already and it still doesn't work correctly. But I need it. You can't chain 5 POSTs to nested API resources instead of a single nested structure. Man, we need some generic implementation out of the box.Boris Burkov

1 Answers

1
votes

The problem here is on the front-end side. By default the server interprets the data as application/x-www-form-urlencoded and in order for it to understand that you are sending it a json, you need to specify the contentType in your $.ajax request:

$.ajax({
     url: base_url + '/games/',
     dataType: "json",
     data: {...},
     contentType: 'application/json; charset=UTF-8',  // add this line
     type: 'POST'
 })
 .error(function(r){})
 .success(function(data){});

Now your validated_data.pop('measurements') in create() method of your GameSerializer should yield three objects with your measurements (but don't forget to redo your workaround from Update#2).