Creating a custom serializer in django

I am facing a problem, because I have to create a custom serializer to fulfill my needs.

These are my models:

class PublicID(models.Model):
    TYPE_FIELDS = (
        ('M', 'monkey'),
        ('A', 'alien'),
        ('P', 'person'),
    )

    public_id = models.CharField(unique=True, max_length=48, editable=False)
    obj_type = models.CharField(choices=TYPE_FIELDS, max_length=1)


class Request(models.Model):
    source = models.ForeignKey(PublicID, related_name='source_request_set',
        on_delete=models.CASCADE)
    dest = models.ForeignKey(PublicID, related_name='dest_request_set',
        on_delete=models.CASCADE)


class Monkey(models.Model):
    nickname = models.CharField(max_length=255)
    public_id = models.OneToOneField(PublicID, related_name='monkey',
        blank=True, on_delete=models.CASCADE)


class Alien(models.Model):
    alias = models.CharField(max_length=255)
    public_id = models.OneToOneField(PublicID, related_name='alien',
        blank=True, on_delete=models.CASCADE)


class Person(models.Model):
    first_name = models.CharField(max_length=255)
    public_id = models.OneToOneField(PublicID, related_name='person',
        blank=True, on_delete=models.CASCADE)

Note that all types of obj_types can send Request to every type of obj_types. e.g. we can have a Monkey which sends a request to an Alien through their unique PublicIDs.

My Serializer looks like this:

class RequestSerializer(serializers.ModelSerializer):
    class Meta:
        model = Request
        exclude = ['id']
    
    def validate_source(self, value):
        obj_type = value.get('obj_type')
        if obj_type is None:
            raise serializers.ValidationError('`obj_type` field is required.')
        # other checks
        return value

    def validate_dest(self, value):
        obj_type = value.get('obj_type')
        if obj_type is None:
            raise serializers.ValidationError('`obj_type` field is required.')
        # other checks
        return value
    
    def run_validation(self, attrs):
        source = attrs.get('source')
        dest = attrs.get('dest')
        if source is None or dest is None:
            raise serializers.ValidationError('`source` and `dest` fields are '
                                              'required.')
        self.validate_source(source)
        self.validate_dest(dest)
        return attrs
    
    def to_representation(self, instance):
        source_type = instance.source.obj_type
        dest_type = instance.dest.obj_type
        print(source_type, dest_type)
        representation = {}
        if source_type == 'monkey':
            representation['source'] = {
                'nickname': instance.source.monkey.nickname,
                'obj_type': 'monkey',
            }
        elif source_type == 'alien':
            representation['source'] = {
                'alias': instance.source.alien.alias,
                'obj_type': 'alien',
            }
        elif source_type == 'person':
            representation['source'] = {
                'username': instance.source.person.username,
                'obj_type': 'person',
            }
        
        if dest_type == 'monkey':
            representation['dest'] = {
                'nickname': instance.dest.monkey.nickname,
                'obj_type': 'monkey',
            }
        elif dest_type == 'alien':
            representation['dest'] = {
                'alias': instance.dest.alien.alias,
                'obj_type': 'alien',
            }
        elif dest_type == 'person':
            representation['dest'] = {
                'username': instance.dest.person.username,
                'obj_type': 'person',
            }

        return representation

Expected behavior:

My Serializer has to be able to:

  1. Given a Request, it should be able to serialize it into:
{
    'source': {'nickname': 'john', 'obj_type': 'monkey'},
    'dest': {'alias': 'jim', 'obj_type': 'alien'}
}

This is completed successfully through the modification of the to_representation method.

  1. Given the serialized data (let's assume that they are the data produced above in bullet (1)), it should be able to validate them in order to use them in create or update methods. Since I have overridden the run_validation and created validate_<field> methods, it validates the data successfully.

My question is, where is the correct place to deserialize the data:

{
    'source': {'nickname': 'john', 'obj_type': 'monkey'},
    'dest': {'alias': 'jim', 'obj_type': 'alien'}
}

into a Request object that has source and dest which are PublicID objects? In my research, the best way I have found is to override the to_internal_value() method, in order to get the object if it exists, or raise exception if it does not. If it does not exist, I will catch the exception and use .save(create=True) instead.

  1. Is this the most pythonic way, and best practice django-wise to do it?

Another thing is that if I validate the data and store the serializer return value into a variable s, I cannot access s.data.

in to_representation

source_type = instance.source.obj_type

AttributeError: 'ReturnDict' object has no attribute 'source'

However, I do be able to access s.validated_data and the values are correct.

  1. why is the to_representation() method called when I try to access s.data? I assumed that since the serializer instance was created from serialized data, the serialization process would not take place again.

  2. Is it a problem that I cannot access s.data? Is my implementation wrong?

Back to Top