Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST: How to use Router in application level urls.py?

My Django project structure:

mysite/
    mysite/
        ...
        urls.py
    scradeweb/
        ...
        models.py
        serializers.py
        views.py
        urls.py
    manage.py

If I use Django REST router in the project level urls.py (mysite/urls.py) like below, everything works fine:

# mysite/urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from .settings import USER_CREATED_APPS
from rest_framework.routers import DefaultRouter
from scradeweb import views

router = DefaultRouter()
router.register(r'threads', views.ThreadViewSet, )
router.register(r'posts', views.PostViewSet)

urlpatterns = patterns('',
                url(r'^admin/', include(admin.site.urls)),
                url(r'api-auth/', include('rest_framework.urls', namespace='rest_framework')),
                url(r'scradeweb/', include('scradeweb.urls', namespace='scradeweb')),
                url(r'^', include(router.urls)),
                )

I like keeping all my application (scradeweb) related code within its directory, so I move router to scradeweb/urls.py:

# scradeweb/urls.py
from django.conf.urls import url, patterns, include
from scradeweb import views
from rest_framework.routers import DefaultRouter


router = DefaultRouter()
router.register(r'threads', views.ThreadViewSet, )
router.register(r'posts', views.PostViewSet)

urlpatterns = router.urls

When I go to http://127.0.0.1:8000/scradeweb/posts/, it raises the exception:

ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "thread-detail". 
You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.

Why does it not work?

Here is my scradeweb/models.py

class Thread(models.Model):

    thread_id = models.IntegerField()
    sticky = models.NullBooleanField()
    prefix = models.CharField(max_length=255)
    title = models.CharField(max_length=255)
    start_time = models.DateTimeField()
    author = models.CharField(max_length=255)
    url = models.URLField(unique=True)
    forum = models.ForeignKey(Forum, related_name='threads')

    class Meta:
        ordering = ('start_time', )


class Post(models.Model):

    post_id = models.IntegerField()
    url = models.URLField(unique=True)
    post_number = models.IntegerField()
    start_time = models.DateTimeField(blank=True)
    last_edited_time = models.DateTimeField(blank=True, null=True)
    author = models.CharField(max_length=255, blank=True)
    content = models.TextField(blank=True)
    thread = models.ForeignKey(Thread, related_name='posts')

    class Meta:
        ordering = ('post_number', )

scradeweb/serializers.py:

from rest_framework import serializers
from scradeweb.models import Thread, Post


class ThreadSerializer(serializers.HyperlinkedModelSerializer):

    posts = \
        serializers.HyperlinkedRelatedField(
            many=True,
            read_only=True,
            view_name='post-detail',
        )

    class Meta:

        model = Thread
        fields = ('pk', 'thread_id', 'title', 'url', 'posts')
        read_only_fields = ('thread_id', )


class PostSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Post

scradeweb/views.py:

...

class ThreadViewSet(viewsets.ReadOnlyModelViewSet):

    queryset = Thread.objects.all()
    serializer_class = ThreadSerializer


class PostViewSet(viewsets.ModelViewSet):

    queryset = Post.objects.all()
    serializer_class = PostSerializer
like image 436
Hieu Avatar asked Jan 01 '15 07:01

Hieu


People also ask

How does URL routing work in Django?

Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL, matching against path_info . Once one of the URL patterns matches, Django imports and calls the given view, which is a Python function (or a class-based view).

How do you use a router in DRF?

Routers are used with ViewSets in django rest framework to auto config the urls. Routers provides a simple, quick and consistent way of wiring ViewSet logic to a set of URLs. Router automatically maps the incoming request to proper viewset action based on the request method type(i.e GET, POST, etc).

What is the difference between ViewSet and ModelViewSet?

It is more rigid than a generic APIView but it takes away from you some boilerplate/manual config that you would have to repeat again and again in most cases. One step further is the ModelViewSet , which is an extension of the ViewSet for when you are working with Django models.


2 Answers

The problem here is that you are using Django REST Framework with a namespace. Many components do not work well with them, which doesn't mean they can't be used, so you need to work around the issues by doing a lot of manual work. The main problem is with hyperlinked relations (Hyperlinked*Fields), because they have to reverse views, and that requires that the namespace is specified.

The first place to start at is the router level, which doesn't currently support namespaces. This is only an issue for the DefaultRouter, as it will build out the index page that contains a list of reversed urls, which will trigger errors right away. This has been fixed in the upcoming release, but for now you're stuck without it. At best (DRF 3.0+), the index page will be completely empty (or point to incorrect urls), in the worst case scenario (DRF 2.x) it will always trigger an internal server error.

The second place to look at is the serializer fields. By default, the HyperlinkedModelSerializer will automatically generate HyperlinkedRelatedField and HyperlinkedIdentityField fields with non-namespaced urls. When you are using namespaces, you have to override all of these automatically generated fields. This generally means you are better off just not using a HyperlinkedModelSerializer, and instead controlling the output with a ModelSerializer.

The first (and easiest) thing to fix is the url field that is automatically generated for all objects. This is automatically generated with view_name set to the detail page (thread-detail in this case), without a namespace. You are going to need to override the view_name and include the namespace in front of it (so scradeweb:thread-detail in this case).

class ThreadSerializer(serializers.ModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name="scradeweb:thread-detail",
    )

You are also going to need to override all of the automatically generated related fields. This is slightly more difficult for writable fields, but I would always recommend looking at repr(MySerializer()) in those cases to see the arguments for the automatically generated fields. For the case of read-only fields, it's a matter of just copying over the default arguments and changing the view_name again.

class ThreadSerializer(serializers.ModelSerializer):
    posts = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='scradeweb:post-detail',
    )

This will need to be done for all fields, and it doesn't look like Django REST Framework will be adding the ability to do it automatically in the near future. While this will not be enjoyable at first, it will give you a great opportunity to appreciate everything DRF previously did for you automatically.

like image 138
Kevin Brown-Silva Avatar answered Oct 18 '22 00:10

Kevin Brown-Silva


I have had the same kind of issue and fix it just change the "HyperlinkedModelSerializer" to "ModelSerializer". I am just following the rest framework tutorial and got stucked in this part

class TodoSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model =  TodoList
        fields =  '__all__'

to:

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model =  TodoList
        fields =  '__all__'
like image 36
Wes Alves Avatar answered Oct 18 '22 01:10

Wes Alves