Django-rest-framework: Could not resolve URL if router is included under a namespace

Created on 24 Mar 2015  Â·  16Comments  Â·  Source: encode/django-rest-framework

Reproduction

urls.py

from django.conf.urls import patterns, include, url
from django.contrib.auth import get_user_model
from rest_framework import routers, serializers, viewsets

User = get_user_model()

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'is_superuser', 'url',)

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

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = patterns(
    '',
    url(r'^api/', include(router.urls, namespace='api')),      # HERE.
)

Add a user (with manage.py createsuperuser or whatever), and visit /api/users/ to see this error:

ImproperlyConfigured at /api/users/

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

Description

After some digging into the source, the reason seems to be that rest_framework.utils.field_mapping.get_detail_view_name does not take URL namespaces into account, and generates a wrong view name (user-detail in the example; the correct name should be api:user-detail).

I know that the documentation says that if you use namespaces, you need to provide view_name manually, but this still seems wrong to me. Namespacing with view names is pretty common practice (AFAIK), and Django REST Framework’s API root does support it. It might be able to (weakly IMO) justify requiring explicit configuration for custom fields, but with automatically generated fields this should be handled automatically. Or at least provide better error messages.

Enhancement

Most helpful comment

I have the same problem and didn't realize there is a workaround:

class DashboardSerializer(HyperlinkedModelSerializer):
    class Meta:
        extra_kwargs = {'url': {'view_name': 'api_v1:dashboard-detail'}}        

All 16 comments

This has been around for a long time. (Issue numbers here.) I'm inclined to agree it's sub-optimal.

The trouble is, how to handle it correctly? (There was an example — number — that automatically added the namespace in reverse but it was _somewhat heavy-handed_.)

IMO since a view(set) can be used multiple times in one project (and are in different namespace), each serializer used by the view(set) should know what namespace it should use. So get_relation_kwargs and get_url_kwargs should take an additional parameter specifying the namespace, and use it for the underlying get_detail_view_name. So what I have in mind is like

  1. router = routers.DefaultRouter(namespace='api'). namespace becomes self.namespace in DefaultRouter, and is passed to as_view to instantiate the viewset.
  2. Viewsets (all views, actually) take an optional namespace argument in __init__, and use it to instantiate relational fields.
  3. Relational fields use the namespace to generate view names that can be reversed correctly.

I’m not even sure whether this would actually work, but hopefully this sounds reasonable.

I’m not even sure whether this would actually work...

Nothing that a failing test case plus patch couldn't answer :-)

IIRC the issues come with handling non-namespaced views and reversing requests across namespaces — where, once we go down this road, there's an expectation that the correct namespace will be inferred, rather than being specified by the dev. Doing it at the URL Conf level may be the right way to go here.

If you want to work on a Pull Request for this that would be great — a proof of concept would be enough to base a discussion around.

I guess I’ll try to come up with a proof of concept, and go from there then. Thanks!

I have the same problem and didn't realize there is a workaround:

class DashboardSerializer(HyperlinkedModelSerializer):
    class Meta:
        extra_kwargs = {'url': {'view_name': 'api_v1:dashboard-detail'}}        

Great @rrauenza! It worked for me.

I'm using the following hack for now:

from rest_framework import relations                      
original_reverse = relations.reverse                      
def hack_reverse(alias, **kwargs):                        
    namespace = kwargs['request'].resolver_match.namespace
    name = "%s:%s" % (namespace, alias)                   
    return original_reverse(name, **kwargs)               
relations.reverse = hack_reverse                          

Which effectively does the same thing as in my PR (which sadly isn't building). I guess it may not be a solution for everyone depending on how you have your namespaces configured.

Edit: reading @uranusjr's comment above, it seems reasonable that it should "just work" if the related view can be found under the same namespace as the current view.

Can confirm, same issue here due to loading in url(r'^api/v1/', include("foobar.api.urls", namespace="api")), sadly routers.DefaultRouter(namespace='api') doesn't work, the hack of @rrauenza does.

3816 should probably fix this. Not dead sure though.

@rrauenza worked for me! Thanks

@rrauenza also worked for me! Thanks!

Closing this given #3816 is now merged. Can consider reopening if the issue comes up again.

3816 was not merged and this is still an issue as far as I can tell. As this comment suggests, it's impossible for a pluggable app to explicitly declare its full namespace.

@tyrdavis #4219 handled #3816.

The discussions on all these tickets are very long and not exactly clear. If you still have an issue here, can I suggest opening a new ticket explaining the particular issue afresh, referencing this family of tickets, with a smallest possible reproduce, and ideally a test case demonstrating it? (There's a decent chance of prompt handling that way.)

Using

python 3.6
Django version 2.0.1

Code

In project urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'api/accounts/', include('clients.urls', namespace='client')),

In the apps (clients) urls.py

app_name="clients"
urlpatterns = [
     path(r'client_trades', ClientTradeView.as_view(), name='trade-list'),
    path(r'client_trades/<trade_id>', ClientOrdersView.as_view(), name='trade-detail'),
]

Serializer

class OrderHyperlinkedSerializer(serializers.HyperlinkedModelSerializer):
    trades = serializers.HyperlinkedIdentityField(many=True, read_only=True, view_name='clients:trade-list')
    class Meta:
        model = Order
        fields = (
        'last_update', 'order_id', 'status', 'symbol', 'trades')
## Error

File "/Users/thanos/anaconda/envs/tweets/lib/python3.6/site-packages/rest_framework/relations.py", line 391, in to_representation
    raise ImproperlyConfigured(msg % self.view_name)
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "clients:trade-list". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.

Does it have to be so difficult ?

view_name='clients:trade-list' and namespace='client' ?

Was this page helpful?
0 / 5 - 0 ratings