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...