Problems getting Hyperlinked Serializers working

948 views
Skip to first unread message

Nerdz rool

unread,
Jul 15, 2013, 12:56:59 PM7/15/13
to django-res...@googlegroups.com
I've been having a few issues related to HyperlinkedModelSerializers, and maybe I just missed something in the documentation, but I've tried nearly everything and gotten nowhere with it. This is somewhat related this posting on Reddit. In the Reddit post, the author created a blog post about his own problems with Hyperlinked Serialization and ultimately his way of fixing the issue was to remove the URL parameter from the API. And, indeed, this solves issues, but at that point you may as well not have a hyperlinked serializer at all (and that's what he ultimately decided on). I commented that simply using a Viewset would fix his problem and allow him to have URLs in the API because the views for each of the HTTP methods would be created for him based in the generic classes for each function. I recreated his app to make sure what I was saying was correct, and indeed it was. To me this made sense...

I was playing with rest framework on a non-serious project not long afterwards and I hit something similar to the problem he was having. In my case, I decided to experiment only using the built-in models for Django itself (this should also make the example easier to replicate). So all of the auth models like User, Permissions, Group, ContentType, etc... The serializers are pretty simple at their basic level. They all use their primary keys to link up to each other. Everything will work out of the box as expected if you make them with HyperlinkedModelSerializers and ModelViewSets then using a simple router to do all the routing. To make the example simple, I just did this in the default app that is created with a startproject command from Django:

serializers.py:
class ContentTypeSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = ContentType

class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group

class PermissionsSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Permission

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User

views.py:
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class GroupViewSet(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

class PermissionsViewSet(viewsets.ModelViewSet):
    queryset = Permission.objects.all()
    serializer_class = PermissionsSerializer

class ContentTypeViewSet(viewsets.ModelViewSet):
    queryset = ContentType.objects.all()
    serializer_class = ContentTypeSerializer

urls.py:
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'permissions', PermissionsViewSet)
router.register(r'contenttypes', ContentTypeViewSet)

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

In my settings, I just had to include my projectname app in the installed apps (as well as rest_framework, obviously) and created a basic configuration for rest framework itself:
 REST_FRAMEWORK = {
    'DEFAULT_MODEL_SERIALIZER_CLASS' :
        'rest_framework.serializers.HyperlinkedModelSerializer',

    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ],

    'PAGINATE_BY' : 10,
}

This should work completely (and be very bad, because the password hashes and everything is also included in the API by default, so don't use it for anything other than creating this test.). I wanted to do a simple change which is, rather than the user's primary key be in its URL, it should be the user's username (instead of users/1/ pointing to a user, I want /users/<username>/). This should work fine, since Django's default User class has unique usernames, so each username will point to exactly one model. Changing this, for me, for some reason, has been difficult. I would have thought, based on this that the only thing that had to get changed was the UserSerializer's Meta class, and adding lookup_field = 'username' would make it work. This does nothing, and the URL for a user is still based on the primary key of the user.

So, after thinking about it, I thought perhaps the ViewSet was causing the issues, since the ViewSet is ultimately used in the router, perhaps the URLs are determined there. I realized you can attach a lookup_field to ViewSets, so I made the UserViewSet have a lookup_field = 'username'. This, when viewing the users list, causes a somewhat familiar error from the Reddit posting: Exception: could not resolve URL for field using view name "user-detail". However, one thing I noticed is that the way the Users are looked up was actually changed. If I visit /users/1/ I will get a HTTP 404, as I would expect (since there is no user with the username = 1). However if I visit my superadmin's username, I still get the exception. The exception is caused for similar reasons as the Reddit posting, I would assume. It is trying to create the URL of the form /users/<username> but get confused ~somewhere~ but if I exclude the "url" parameter, it functions correctly.

I've tried a few other things, like setting the URLs explicitly among other things, but they don't really seem to change the fact that it can't find the user-detail view. Am I just missing something very simple? Or am I misunderstanding the uses of one of the classes? Django version is 1.5.1, rest-framework is on 2.3.4, django-filter is on 0.6 and markdown is on 2.3.1. Python 2.7 or 3.3, doesn't make a difference, so I think it's just that I'm being stupid and its not a bug or something like that...

Tom Christie

unread,
Jul 15, 2013, 6:04:15 PM7/15/13
to django-res...@googlegroups.com
I think the problem is that you need to *both* set `lookup_field = 'username'` on the ViewSet (which will ensure that the User object lookups are keyed off the username, and that the URL conf created by the routers uses the `username` kwarg) *and* set `lookup_field = 'username'` on the serializer field (which will ensure that the hyperlinks are configured to point at the correct view.)

Hyperlinked serializers can sometimes be a bit fiddly to get right - the error messaging isn't as clear as I'd like it to be.  Right now all it gives you is basically 'your URLs and your serializers don't match up', which isn't all that helpful, but it's difficult to figure out how to point the developer more precisely in the right direction.

Nerdz rool

unread,
Jul 15, 2013, 10:14:20 PM7/15/13
to django-res...@googlegroups.com
That is actually what I am doing when I get the Exception: could not resolve URL for field using view name "user-detail" while trying to view the users list (or a specific user but have the API generate the url field).

views.py
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username'

class GroupViewSet(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    lookup_field = 'pk'

class PermissionsViewSet(viewsets.ModelViewSet):
    queryset = Permission.objects.all()
    serializer_class = PermissionsSerializer
    lookup_field = 'pk'

class ContentTypeViewSet(viewsets.ModelViewSet):
    queryset = ContentType.objects.all()
    serializer_class = ContentTypeSerializer
    lookup_field = 'pk'

serializers.py

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        lookup_field = 'username'

class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group
        lookup_field = 'pk'

class PermissionsSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Permission
        lookup_field = 'pk'
 
class ContentTypeSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = ContentType
        lookup_field = 'pk'

With the rest of the files the same as described before will still error about finding the user-detail. 
Reply all
Reply to author
Forward
0 new messages