7

How can I add a custom hyperlink field in a serializer? I would like to have a hyperlink field in my serializer that has query params in it. Since there is no way to pass query params from HyperlinkedRelatedField or HyperlinkedIdentityField as far as I know, I've tried using a SerializerMethodField. However, this only serializes to a string, and is not a clickable URL when I visit the API through my browser. My code looks something like this:

class MySerializer(serializers.HyperlinkedModelSerializer):
    custom_field = serializers.SerializerMethodField()

    class Meta:
        model = MyModel
        fields = ('url', 'custom_field')

    def get_custom_field(self, obj):
        result = '{}?{}'.format(
            reverse('my-view'),
            urllib.urlencode({'param': 'foo'})
        )
        return result

Also, I am having trouble understanding the difference between a HyperlinkedRelatedField and a HyperlinkedIdentityField, so a brief explanation would be appreciated.

3
  • 1
    You must read.. django-rest-framework.org/tutorial/… its help you. :-) Commented Feb 22, 2016 at 12:38
  • 3
    I have read it, it does not talk about custom hyperlinks Commented Feb 22, 2016 at 19:22
  • @b_pcakes - Mind accepting my answer if it meets your expectations? Thanks! Commented Sep 20, 2017 at 17:08

1 Answer 1

12

This should do the trick:

from rest_framework.reverse import reverse

class MySerializer(serializers.HyperlinkedModelSerializer):
    custom_field = serializers.SerializerMethodField()

    class Meta:
        model = MyModel
        fields = ('url', 'custom_field')

    def get_custom_field(self, obj):
        result = '{}?{}'.format(
            reverse('my-view', args=[obj.id], request=self.context['request']),
            'param=foo'
        )
        return result

The reverse function in rest_framework takes a view name (whatever view you'd like to link to), either an args list (the object id, in this case) or kwargs, and a request object (which can be accessed inside the serializer at self.context['request']). It can additionally take a format parameter and any extra parameters (as a dictionary) that you want to pass to it.

The reverse function then builds a nice, fully-formed URL for you. You can add query params to it by simply adding as many ?{}&{}&{} to your result variable and then filling in the series of query params beneath the 'param=foo' inside your format function with whatever other params you want.

The HyperlinkedIdentityField is used on the object itself that is being serialized. So a HyperlinkedIdentifyField is being used in place of your primary key field on MyModel because you are using a HyperlinkedModelSerializer which creates a HyperlinkedIdentityField for the pk of the object itself being serialized.

The HyperlinkedRelatedField is used to define hyperlinked relationships to RELATED objects. So if there were a MySecondModel with a foreign key relationship to MyModel and you wanted to have a hyperlink on your MyModel serializer to all the related MySecondModel objects you would use a HyperlinkedRelatedField like so (remember to add the new field to your fields attribute in Meta):

class MySerializer(serializers.HyperlinkedModelSerializer):
    custom_field = serializers.SerializerMethodField()
    mysecondmodels = serializers.HyperlinkedRelatedField(
        many=True
        read_only=True,
        view_name='mysecondmodel-detail'
    )

    class Meta:
        model = MyModel
        fields = ('url', 'custom_field', 'mysecondmodels')

    def get_custom_field(self, obj):
        result = '{}?{}'.format(
            reverse('my-view', args=[obj.id], request=self.context['request']),
            'param=foo'
        )
        return result

If it were a OneToOneField rather than ForeignKey field on MySecondModel then you would set many=False.

Hope this helps!

Sign up to request clarification or add additional context in comments.

2 Comments

I receive KeyError for key 'request'. What to do to have self.context['request'] as you say?
@mirek When you use your serializer in a view you need to add context: MySerializer(MyModel, context={'request': request}). If you're using a ModelViewSet self.request or override get_serializer_context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.