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_type
s can send Request
to every type of obj_type
s. e.g. we can have a Monkey
which sends a request to an Alien
through their unique PublicID
s.
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:
- 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.
- 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
orupdate
methods. Since I have overridden therun_validation
and createdvalidate_<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.
- 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.
why is the
to_representation()
method called when I try to accesss.data
? I assumed that since the serializer instance was created from serialized data, the serialization process would not take place again.Is it a problem that I cannot access
s.data
? Is my implementation wrong?