Django-rest-framework: app_name now required by Django 2 with router url patterns

Created on 5 Dec 2017  Â·  12Comments  Â·  Source: encode/django-rest-framework

Checklist

  • [x] I have verified that that issue exists against the master branch of Django REST framework.
  • [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • [x] This is not a usage question. (Those should be directed to the discussion group instead.)
  • [x] This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • [x] I have reduced the issue to the simplest possible case.
  • [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Steps to reproduce

With new changes to django.core.urlresolvers (1.11.7) -> django.urls (2.0): as per the instructions :
Register a router
router = DefaultRouter()
Register a viewset with the router
router.register('foo', FooViewSet, base_name='foo')
Add the url patterns:
urlpatterns += [ url('API/', include(router.urls, namespace='foo-api'), ]

Expected behavior

urlpatterns now contains the foo API url patterns, accessible using the namespace if needing to get the url string with django.urls.reverse

Actual behavior

django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.

Is the router providing the app_name? If so, the documentation needs updating with the right syntax for including router url patterns. Otherwise, the router itself needs updating.

Documentation

Most helpful comment

This isn't actually a bug I think.

For years, we've been skirting around the (confusing) distinction between an _application name(space)_ and an _instance namespace_. We've always just recommended using _instance namespace_, as per the examples in the docs.

What's happened with Django 2.0 is they've made it so you can't use an _instance namespace_ without also (first) using and _application name_. Instead of fixing code we should update our examples to the correct usage.

The include needs to go like this:

urlpatterns += [ url('API/', include((router.urls, 'foo-api')) ]

(The tendency is to include the second namespace='foo-api' _instance namespace_ parameter as well, but that's not needed — it defaults to None and is set to 'foo-api' in this case automatically.)

(Generally users want to be including an app-level urls module explicitly setting the app_name attribute there, rather than including the router by hand.)

I'll work on a PR for the docs here.

Maybe we could provide a utility on the router classes to return the (patterns, app_name,) tuple but to be honest I don't think it's necessary.

All 12 comments

Thanks for the report.
At first sight I though this was a documentation issue but I'm afraid it's a bit more than that.
The router was expecting the url's include to provide the name spacing while with Django 2.0 it should be up to the router to do that and from the look of it it can't do that yet.

5609 aims to fix this. For now, I'd remove the namespace from the patterns.

This isn't actually a bug I think.

For years, we've been skirting around the (confusing) distinction between an _application name(space)_ and an _instance namespace_. We've always just recommended using _instance namespace_, as per the examples in the docs.

What's happened with Django 2.0 is they've made it so you can't use an _instance namespace_ without also (first) using and _application name_. Instead of fixing code we should update our examples to the correct usage.

The include needs to go like this:

urlpatterns += [ url('API/', include((router.urls, 'foo-api')) ]

(The tendency is to include the second namespace='foo-api' _instance namespace_ parameter as well, but that's not needed — it defaults to None and is set to 'foo-api' in this case automatically.)

(Generally users want to be including an app-level urls module explicitly setting the app_name attribute there, rather than including the router by hand.)

I'll work on a PR for the docs here.

Maybe we could provide a utility on the router classes to return the (patterns, app_name,) tuple but to be honest I don't think it's necessary.

I followed the documentation improvement here, but was unable to get serializers.HyperlinkedModelSerializer to generate url and references to other objects correctly - it throws a mis-configuration exception complaining about missing reverse routes. I printed the URLs from the entire project using django_extensions and found that the registered views were named app_name:app_name:object-detail.

To work around this, I registered my URLs as

app_name = 'app_name'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^api/', include((router.urls, None))),

This restored the view names to app_name:object-detail to match my own regular django views. I then hacked the following function in rest_framework\utils\field_mapping.py line 60 to add the app_label (which was already in the format dictionary but unused:

def get_detail_view_name(model):
    """
    Given a model class, return the view name to use for URL relationships
    that refer to instances of the model.
    """
    return '%(app_label)s:%(model_name)s-detail' % {
        'app_label': model._meta.app_label,
        'model_name': model._meta.object_name.lower()
    }

Both changes together worked. Has anyone else tested this documentation-only fix with a HyperlinkedModelSerializer? I used requirements.txt containing:

Django==2.0.2
djangorestframework==3.7.7

If there's a supported way to do this without the hack I'd love to know.

@shuckc the comment here obviouslyisnt clear enough.

You don’t pass the 2-tuple when including from inside an app level urls module. There app_name is already used when you include the module later on.

As you’ve discovered, you’ll end up with a nested namespace if you do that.

The usage I’m talking about here is when you want to include the router at the top level and provide a namespace. The docs for that are currently non-functional.

There’s currently no way around your second issue. (Well, except manually specifying view-name in hyperlinked fields.) We don’t have a mechanism for knowing the app or instance namespace that’ll be required.

Often, with the right conventions in play, the app name would be enough so I’d certainly look at a PR making this pluggable.

Thanks - my mistake, I was following anything seemingly related, fairly blindly, to try and fix the HyperlinkedModelSerializer reverse exception.

If the function I changed was bumped out to rest_framework\compat.py and conditional on django.VERSION would that be acceptable?

def get_detail_view_name(model):
    """
    Given a model class, return the view name to use for URL relationships
    that refer to instances of the model.
    """
    if VERSION >= (2, 0):
        return '%(app_label)s:%(model_name)s-detail' % {
            'app_label': model._meta.app_label,
            'model_name': model._meta.object_name.lower()
        }

    return '%(model_name)s-detail' % {
        'model_name': model._meta.object_name.lower()
    }

@shuckc HyperlinkedRelatedField not being able to intuit your URL conf has nothing to do with Django 2.0. This has always been the case. (So it's nothing to do with compat.)

It's a fundamentally tricky issue.

In general, model._meta.app_label is not the same as the app_name variable you define in your urls.py. In can be, especially if you're careful with your naming, but it's not something we can just assume. (So as soon as you start seriously thinking about what's involved in a truly generic solution, you see that un-namespaced captures the only easy case, and the 80% one — how many times are your models identically named across apps? — beyond that "declare your fields" seems a lot better than a load of code that doesn't really work. Of course, PRs with suggestions always welcome.

This issue is about the change in Django 2.0 requiring the 2-tuple when using router.urls plus the namespace parameter. Please lets keep the discussion here to that.

I have the same problem here. If I write the second parameter in the include line,

url(r'^v1/', include('tektank.apps.agreements.api_v1.urls',namespace='v1')),

it fails with:

'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.

I am trying to version the api, so as the Doc states, I have to write the namespace parameter.

SAME ERROR if I write like this:

url(r'^v1/', include('tektank.apps.agreements.api_v1.urls','v1')),

I think (i am not sure if its correct) I make it work like this.
In my main urls.py

api_v1 =[                                                              
    url(r'^api/v1/', include('my_project.apps.agreements.api_v1.urls')),  
    url(                                                               
        r'^api/v1/', include('my_project.apps.authentication.api_v1.urls')
]

urlpatterns = [
    url(r'', include((api_v1,'v1'), namespace="v1")),
]

If instead, in the url patterns I include like this:

urlpatterns = [
    url(r'', include(api_v1, namespace="v1")),
]

I have the following error:
'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.

The thing is that 'v1' as the second argument, the "app_name", in fact its not an app name, I just put it there to not have the error....

+1 here myself.

@carltongibson "how many times are your models identically named across apps?" -- in my case, I've got a set of domains/environments which have essentially the same data models and schema set up in all of them, and the app I'm building is providing a way to read that data from one API. So I have the same list of models, repeated N times for N domains, and I'm trying to cure this using name spacing for URL routing to work correctly without having to duplicate all my code N times just to get the same result.

The current sticking point I've hit on that led me here is because I'm getting the

'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.

error when trying to apply a namespace to the API in a loop, e.g.

domain_router = {
    "DEV": {'router': None, 'patterns': None },
    "CERT": {'router': None, 'patterns': None },
    "PROD": {'router': None, 'patterns': None },
}

for k, v in domain_router.items():
    v['router'] = routers.DefaultRouter()
    v['router'].root_view_name = '{0}-api-root'.format(k.lower())
    v['router'].register(r'advanced-delta', views.AdvancedDeltaViewSet)
    v['router'].register(r'alpha-responses', views.AlphaResponsesViewSet)
   # etc, etc 

    v['patterns'] = [path(
        '{0}/api/'.format(k.lower()),
        include(v['router'].urls, '{0}'.format(k.lower()))     # << ERROR HERE
    ),]

    urlpatterns += [
        path(
            '{0}/api/schema/'.format(k.lower()),
            get_schema_view(title='Domain {0} API'.format(k), patterns=v['patterns']),
            name='{0}-api-schema'.format(k.lower())
        ),
        path(
            '{0}/api/docs/'.format(k.lower()),
            include_docs_urls(title='Domain {0} API'.format(k), namespace='{0}'.format(k.lower())),
        )
    ] + v['patterns']

I'm beginning to think I've hit an iceberg with this problem, and I'm still trying to find its depths :-/

Setting namespace to something is currently how to make versioned urls via NameSpaceVersioning.

With Django 2.0, what is the correct way to do this?

path("v1/", include((api_urls_v1, 'app'), namespace='v1')),

where 'app' is app_name.

This based on django's source code
image

Was this page helpful?
0 / 5 - 0 ratings