6

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!

Nick S.
  • 113
  • 6
  • 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 Feb 17 '16 at 20:11
  • 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 Feb 17 '16 at 20:17
  • @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. Feb 17 '16 at 20:36
  • 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 Feb 18 '16 at 11:14
  • 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 Mar 09 '16 at 17:42
  • I am working on DRF now. I observed their documentation didn't covered complex matters elaborately. I have solved some problem within last few days. Could you please give your problem/error screenshot so i can understand your problem – Amir Mar 15 '16 at 12:27
  • Dear Nick, did my solution below helped you? – iulian Apr 15 '16 at 08:26

1 Answers1

1

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).

iulian
  • 5,494
  • 3
  • 29
  • 39