6

Is it possible in Django Rest Framework to add a HyperLink on the id field of each record in the ModelViewSet List View that links to the Detail View for that record?

The documentation says to add a view_name argument, but is this possible to do under a ModelViewSet because the views don't actually have names?

Aaron Lelevier
  • 19,850
  • 11
  • 76
  • 111

2 Answers2

13

first thing is that ALL ModelViewSet urls have names, even if you don´t set those names explicitly.

You can find out how the default urls are created within a router, in the docs: http://www.django-rest-framework.org/api-guide/routers/ (see 'SimpleRouter' url names table)

To see all the actual url names available in your application, try with this utility:

def print_url_pattern_names(patterns):
    """Print a list of urlpattern and their names"""
    for pat in patterns:
        if pat.__class__.__name__ == 'RegexURLResolver':            # load patterns from this RegexURLResolver
            print_url_pattern_names(pat.url_patterns)
        elif pat.__class__.__name__ == 'RegexURLPattern':           # load name from this RegexURLPattern
            if pat.name is not None:
                print '[API-URL] {} \t\t\t-> {}'.format(pat.name, pat.regex.pattern)

Then, in your urls.py:

urlpatterns = [
    url(r'^', include(router.urls)),
]

if settings.DEBUG:
    print_url_pattern_names(urlpatterns)

If you want the url to be different from your ModelViewSet´s name (as in my case) you can set it on the router with "base_name":

router.register('contents', media_views.MediaViewSet, base_name='contents')

Next thing you´ll need is extending HyperlinkedModelSerializer:

from rest_framework import serializers
# in this sample my object is of type "Media"
class MediaSerializer(serializers.HyperlinkedModelSerializer):
    #blablabla

You will have a serializer ready to show hyperlinks to your detail view, but there is one required step left for this to work. That´s where the "view_name" comes in:

from rest_framework import serializers
class MediaSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Media
        fields = ('url', ...[other fields you want to serialize])
        extra_kwargs = {
            'url': {'view_name': 'contents-detail'}
        }
  1. 'url' field is mandatory to show the link.
  2. With 'extra_kwargs' > view_name you´re telling the framework that your detail view´s name is 'content-detail'.
  3. 'content-detail' is just MY view´s name. You need to find out yours (from the url names utility above)

And this is the http response from the sample (see url field):

enter image description here

xleon
  • 6,201
  • 3
  • 36
  • 52
  • here you are setting `'contents-detail'` as the url name. Do you need to declare this url in the `urls.py`? – Aaron Lelevier Aug 17 '15 at 17:28
  • No. As I mentioned, the router/modelViewSet will create urls for you: http://www.django-rest-framework.org/api-guide/routers/. But in my particular case, given that I want the url to start with "content" rather than "media", I set that on the urls.py (edited the post) – xleon Aug 17 '15 at 20:38
  • 1
    I removed the `extra_kwargs` for the view_name because it isn't needed in a `HyperlinkedModelSerializer` and it worked. Also, this answer helped me get it working: http://stackoverflow.com/a/20553926/1913888. I still put +1 for your answer. Thank you. – Aaron Lelevier Aug 18 '15 at 20:55
2

Little update to xleon's answer:

With Python 3 and new Django, function to see all urlpatterns you should use something like this:

def print_url_pattern_names(patterns):
    """Print a list of urlpattern and their names"""
    for pat in patterns:
        if pat.__class__.__name__ == 'URLResolver':      # load patterns from this URLResolver
            print_url_pattern_names(pat.url_patterns)
        elif pat.__class__.__name__ == 'URLPattern':     # load name from this URLPattern
            if pat.name is not None:
                print('[API-URL] {:>50} -> {}'.format(pat.name, pat.pattern))

I spent some time resolving it, so maybe it will help somebody.

How I used it: run "python manage.py shell", then

import urls

then copy this function and run it like this:

print_url_pattern_names(urls.urlpatterns)