0

I have a simple relational structure with projects containing several sequences with an intermediate meta model.

I can perform a GET request easily enough and it formats the data correctly. However, when I want to post the validated_data variable does not contain data formatted correctly, so I can't write a create/update method.

The data should look like:

{'name': 'something', 
'seqrecords': [{
                'id': 5, 
                'sequence': 'ACGG...AAAA', 
                'name': 'Z78529',
                'order': 1
               },
               {
                'id': 6,
                'sequence': 'CGTA...ACCC',
                'name': 'Z78527',
                'order': 2
               }, 
}

But instead it looks like this:

{'name': 'something',
 'projectmeta_set': [
                     OrderedDict([('order', 1)]),
                     OrderedDict([('order', 2)]),
                     OrderedDict([('order', 3)])
                    ]
}

Serializers:

class ProjectMetaSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.ReadOnlyField(source='sequence.id')
    name = serializers.ReadOnlyField(source='sequence.name')

    class Meta:
        model = ProjectMeta
        fields = ['id', 'name', 'order']


class ProjectSerializer(serializers.HyperlinkedModelSerializer):
    seqrecords = ProjectMetaSerializer(source='projectmeta_set', many=True)

    class Meta:
        model = Project
        fields = ['id', 'name', 'seqrecords']
        ReadOnlyField = ['id']

    def create(self, validated_data):
        project = Project(name=validated_data['name'])
        project.save()
        # This is where it all fails
        for seq in validated_data['seqrecords']:
            sequence = SeqRecord.objects.filter(id=seq['id'])
            meta = ProjectMeta(project=project,
                               sequence=sequence,
                               order=seq['order'])
            meta.save()
        return project

class SeqRecordSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = SeqRecord
        fields = ['id', 'name', 'sequence']
        read_only_fields = ['id']

Models:

class SeqRecord(models.Model):
    name = models.CharField(max_length=50)
    sequence = models.TextField()


class Project(models.Model):
    name = models.CharField(max_length=50)
    sequences = models.ManyToManyField(SeqRecord,
                                       through='primer_suggestion.ProjectMeta')


class ProjectMeta(models.Model):
    project = models.ForeignKey(Project)
    sequence = models.ForeignKey(SeqRecord)
    order = models.PositiveIntegerField()

View

class ProjectApiList(viewsets.ViewSetMixin, generics.ListCreateAPIView):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer

Is there some way to change the validation of the data, so it returns the things I need or can I write the create and functions some other way?

Christian Ravn
  • 187
  • 1
  • 2
  • 14

2 Answers2

1

To return the correct data format the function "to_internal" can be overridden in the ProjectSerializer, like this:

class ProjectSerializer(serializers.HyperlinkedModelSerializer):
    seqrecords = ProjectMetaSerializer(source='projectmeta_set', many=True)

    class Meta:
        model = Project
        fields = ['id', 'name', 'seqrecords']
        ReadOnlyField = ['id']

    def to_internal_value(self, data):
        ''' Override standard method so validated data have seqrecords '''
        context = super(ProjectSerializer, self).to_internal_value(data)
        context['seqrecords'] = data['seqrecords']

        return context

    def validate(self, data):
        ...

    def create(self, validated_data):
        ...
        return project

This method is of course dependent on good a validation function for the "seqrecords" field in validate().

Christian Ravn
  • 187
  • 1
  • 2
  • 14
0

As you see in the ProjectMetaSerializer, the fields id and name are ReadOnlyFields. So you can't use them in post request.

class ProjectMetaSerializer(serializers.ModelSerializer):
    seqrecords = SeqRecordSerializer(many=True)

    class Meta:
        model = ProjectMeta
        fields = ['seqrecords',]


class ProjectSerializer(serializers.ModelSerializer):
    project_meta = ProjectMetaSerializer(many=True)

    class Meta:
        model = Project
        fields = ['id', 'name', 'project_meta']
        ReadOnlyField = ['id']

def create(self, validated_data):
    project = Project(name=validated_data['name'])
    project.save()
    # This is where it all fails
    for seq in validated_data['seqrecords']:
        sequence = SeqRecord.objects.filter(id=seq['id'])
        meta = ProjectMeta(project=project,
                           sequence=sequence,
                           order=seq['order'])
        meta.save()
    return project

class SeqRecordSerializer(serializers.ModelSerializer):
    class Meta:
        model = SeqRecord
        fields = ['id', 'name', 'sequence']
Sagar Ramachandrappa
  • 1,411
  • 12
  • 18
  • I'm creating an entire new ProjectMeta based on the fields in the data, so I'm not writing to those fields. – Christian Ravn Oct 07 '16 at 14:18
  • Yes, But once you validate the data, the serializer removes the 'ReadOnlyFields' from the data as it is a post request and 'ReadOnlyFields' are not meant to be present in that request. – Sagar Ramachandrappa Oct 07 '16 at 14:28
  • Which leads me to my question how I can avoid that. – Christian Ravn Oct 07 '16 at 14:50
  • Is there any way to leave the sequences immutable? Since each of them occur in several different projects any change would also occur in every project. This is especially a risk, since the API will be used in several different services, not all written by me. – Christian Ravn Oct 10 '16 at 04:59
  • Also this gives an error "The serializer field might be named incorrectly and not match any attribute or key on the `Project` instance. Original exception text was: 'Project' object has no attribute 'project_meta'." When there is an intermediate model I don't think there is a way to do it without using a ReadOnlyField. – Christian Ravn Oct 10 '16 at 10:37