Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-serializer

Output lookup fields in serialiser for OneToOne relationship


I'd like to output in my API fields coming from two different (related) models.

I have this models.py:

class User(AbstractBaseUser):
    first_name = models.CharField(max_length=30, blank=True, null=True)
    surname = models.CharField(max_length=30, blank=True, null=True)

    def __str__(self):
        return "%s %s" % (self.first_name, self.surname) # I don't want to change this.

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile', null=True, blank=True)
    first_nameUnregistered = models.CharField(max_length=120, null=True, blank=True)
    surnameUnregistered = models.CharField(max_length=120, null=True, blank=True)

This is my serialiser:

class ProfileSerialiser(serializers.HyperlinkedModelSerializer):

    user = serializers.StringRelatedField(many=False)

    class Meta:
        model = Profile
        fields = ('first_nameUnregistered', 'surnameUnregistered', 'user')

And my view:

class ProfileViewSet(viewsets.ModelViewSet):
    queryset = Profile.objects.all()
    serializer_class = ProfileSerialiser

Right now what I see when I go to my /api/profliles endpoint it:

[
    {
        "first_nameUnregistered": "",
        "surnameUnregistered": "",
        "user": "The full user name, coming from def __str__(self):"
    },
[...]

I want to get something like:

[
    {
        "first_nameUnregistered": "",
        "surnameUnregistered": "",
        "user.first_name": "first name"
        "user.surnamee": "surname"
    },
[...]

Lookups such as profile__user.first_name seem not to work, I cannot merge the two querysets because they're from different models, and I cannot use properties in the fields definition of the serialiser. How can I achieve what I want?

Solution

Thanks to @Jan Giacomelli for pointing me towards the source keyword. I had to add a default because if a field is empty the Django Rest Framework will throw an error:

Got AttributeError when attempting to get a value for field `user__first_name` on serializer `ProfileSerialiser`.
The serializer field might be named incorrectly and not match any attribute or key on the `Profile` instance.
Original exception text was: 'NoneType' object has no attribute 'first_name'.

My full working code, therefore, is:

class ProfileSerialiser(serializers.HyperlinkedModelSerializer):

    user__first_name = serializers.CharField(source='user.first_name', default='name')
    user__surname = serializers.CharField(source='user.surname', default='name')

    class Meta:
        model = Profile
        fields = ('id', 'first_nameUnregistered', 'surnameUnregistered', 'user__first_name', 'user__surname')

Solution

  • You can use source keyword argument in the field options. That way you can access properties of related objects.

    Change your serializer to:

    class ProfileSerialiser(serializers.HyperlinkedModelSerializer):
    
        user__first_name = serializers.CharField(source='user.first_name')
        user__surname = serializers.CharField(source='user.last_name')
    
        class Meta:
            model = Profile
            fields = ('first_nameUnregistered', 'surnameUnregistered', 'user__first_name', 'user__surname')
    

    It will return output like this:

    [
        {
            "first_nameUnregistered": "",
            "surnameUnregistered": "",
            "user__first_name": "first name"
            "user__surname": "surname"
        },
    [...]