Nested serializers change JSON field to array of strings from array of models
Right now, I'm developing a social media app where each user creates an account and must fill out a form specifying their basic info and I am storing that information using Django models. I have two fields "Personality traits" and "Interests" where each user can have multiple interests and personality traits, to model this I'm using a foreign key relationship. My models.py looks like this:
class ListingAccount(models.Model):
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
email = models.EmailField(max_length=254, null=True)
date_of_birth = models.DateField(max_length=8, null=True)
occupation = models.CharField(max_length=30, null=True)
age_range = models.CharField(max_length=7, null=True)
tell_us_about_yourself = models.TextField(null=True)
created = models.DateTimeField(auto_now_add=True) # unsure if needed
class PersonalTrait(models.Model):
trait = models.CharField(max_length=200, null = True)
listing_account = models.ForeignKey(ListingAccount, related_name='personal_traits', on_delete=models.CASCADE, null=True)
class Interest(models.Model):
interest = models.CharField(max_length=200, null=True)
listing_account = models.ForeignKey(ListingAccount, related_name='interests', on_delete=models.CASCADE, null=True)
My serializers.py looks like this:
class PersonalTraitsSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalTrait
fields = ['trait']
class InterestsSerializer(serializers.ModelSerializer):
class Meta:
model = Interest
fields = ['interest']
class ListingAccountSerializer(serializers.ModelSerializer):
personal_traits = PersonalTraitsSerializer(many=True)
interests = InterestsSerializer(many=True)
class Meta:
model = ListingAccount
fields = ['id', 'first_name', 'last_name', 'email', 'date_of_birth',
'occupation', 'age_range', 'tell_us_about_yourself', 'created',
'personal_traits', 'interests']
def create(self, validated_data):
interests_data = validated_data.pop('interests')
personal_traits_data = validated_data.pop('personal_traits')
listing_account = ListingAccount.objects.create(**validated_data)
Interest.objects.create(listing_account = listing_account, **interests_data)
PersonalTrait.objects.create(listing_account = listing_account, **personal_traits_data)
for interest_data in interests_data:
Interest.objects.create(listing_account = listing_account, **interest_data)
for personal_trait_data in personal_traits_data:
PersonalTrait.objects.create(listing_account = listing_account, **personal_trait_data)
return listing_account
As of right now once I make a post request the JSON is formatted in this form
{
"id": 14,
"first_name": "WEW",
"last_name": "WWEEEEEEE",
"email": "fgWEGEEWGWEG@gmail.com",
"date_of_birth": null,
"occupation": "typical enjoyer",
"age_range": "18-20",
"tell_us_about_yourself": "k",
"created": "2023-01-18T22:33:17.455085Z",
"personal_traits": [
{
"trait": "happy"
},
{
"trait": "few"
}
],
"interests": [
{
"interest": "skateboarding"
}
]
}
I was wondering how I can alter my seralizers.py such that instead of interests and personal_traits being formatted such that its an array of strings formatted such as this instead:
{
"id": 14,
"first_name": "WEW",
"last_name": "WWEEEEEEE",
"email": "fgWEGEEWGWEG@gmail.com",
"date_of_birth": null,
"occupation": "typical enjoyer",
"age_range": "18-20",
"tell_us_about_yourself": "k",
"created": "2023-01-18T22:33:17.455085Z",
"personal_traits": ["Happy", "Sad"],
"interests": ["skateboarding", "Snowboarding"]
}
Using a nested ModelSerializer
as a field (as in interests = InterestSerializer(many=True)
) will produce a JSON object, not a string -- because it's designed to serialize the structured data in models.
Since you want your values to be strings, you need a StringRelatedField
serializer (https://www.django-rest-framework.org/api-guide/relations/#stringrelatedfield).
You will also need to ensure that a __str__
method is defined on the models you want to serialize as strings, as this is how StringRelatedField
serializes those objects.
So you can get rid of your PersonalTraitSerializer
and InterestSerializer
, and instead use this:
class ListingAccountSerializer(serializers.ModelSerializer):
personal_traits = serializers.StringRelatedSerializer(many=True)
interests = serializers.StringRelatedSerializer(many=True)
class Meta:
...
And you'd want to add those __str__
methods to the models:
class PersonalTrait(models.Model):
trait = models.CharField(max_length=200, null = True)
listing_account = models.ForeignKey(ListingAccount, related_name='personal_traits', on_delete=models.CASCADE, null=True)
def __str__(self):
return f"{self.trait}"
class Interest(models.Model):
interest = models.CharField(max_length=200, null=True)
listing_account = models.ForeignKey(ListingAccount, related_name='interests', on_delete=models.CASCADE, null=True)
def __str__(self):
return f"{self.interest}"
Two other options:
- Use
SerializerMethodField
fields (https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield), and define the custom behaviour you want in methods on theListingAccountSerializer
. But this is a read-only field and won't work for object creation. - Keep using your custom serializer classes for
PersonalTrait
andInterest
, but override theto_representation
method on each to return a string instead of a dict (https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior). I don't like this because it breaks the basic expectations you'd have aboutModelSerializer
behaviour, and there's already a serializer that does what you need. I'd consider this option only if you need the__str__
representation of your model classes to be different from what you want in the serializer.