Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django URL namespaces - the template has to know its namespace?

I've been tinkering with (my first) Django project that started out with Django 1.6 and has just recently moved to Django 1.8. I've also been working through Django Patterns & Best Practices, learning how I should have structured it all along :-)

My project has several sub-apps and a typical main urls.py with lines like:

 (r'', include('noc.apps.frontpage.urls')),

Within each app, I've prefixed all the URL names with the app name, e.g. frontpage_edit_page and used {% url %} throughout the templates to refer between views.

Then I read about URL namespaces, and thought I could de-uglify my URL names. As I first interpreted it, if I added a namespace to each include() in the main urls.py, everything would function as before, because the URL names referred to would all be resolved by the 'local' app. But it doesn't seem to work that way.

With this in the main urls.py:

(r'', include('noc.apps.frontpage.urls', namespace='frontpage', app_name='frontpage')),

This in the app urls.py:

 url(r'^frontpage/edit/(?P<slug>[A-Za-z0-9]+)$', views.edit_page, name='front_page_edit_page'),

and {% url 'front_page_edit_page' slug=page.slug %} in a template inside that app, I get a NoReverseMatch exception with 0 URLs tried.

All the examples I can find are talking about prefixing URLs with the namespace - frontpage:front_page_edit_page but 1) how is this an improvement on the previous app prefix on the URL name? and 2) how could you ever have two instances of the same app, which is supposed to be a benefit... So I'm assuming that this is for linking between apps, not within apps.

So what is it that I am missing? Do I need to embed the app_name or namespace in my view function too? It's true that if I do prefix all my URL names with the namespace, even within the app, I get a rendered page, but it seems to defeat the point.

like image 773
AnotherHowie Avatar asked Feb 10 '23 00:02

AnotherHowie


2 Answers

The way you're meant to do it is indeed to add the namespace to the URL tag;

{% url 'frontpage:edit_page' slug='SLUG' %}

But it would be better practice to structure your main project URls file like this;

urlpatterns = patterns(
    '',
    url(r'^admin/', include(admin.site.urls)),  # NOQA
    url(r'frontpage', include('noc.apps.frontpage.urls', namespace='frontpage', app_name='frontpage')),

That way you can specify the path for each app in the main URLs file, and avoid repetition;

urlpatterns = patterns(
    'noc.apps.frontpage.views',
    url(r'^edit/(?P<slug>[A-Za-z0-9]+)$', 'edit_page', name='edit_page'),

With this you can introduce a RESTful URL structure to all your apps so you'll end up with things like;

urlpatterns = patterns(
    '',
    url(r'^admin/', include(admin.site.urls)),  # NOQA
    url(r'frontpage/', include('noc.apps.frontpage.urls', namespace='frontpage', app_name='frontpage')),
    url(r'contact/', include('noc.apps.contact.urls', namespace='contact', app_name='contact')),
    url(r'myapp/', include('noc.apps.myapp.urls', namespace='myapp', app_name='myapp')),

All your apps can follow a similar structure then;

urlpatterns = patterns(
    'noc.apps.contact.views',
    url(r'^$', 'index', name='index'),
    url(r'^add/$', 'add', name='add'),
    url(r'^edit/(?P<slug>[A-Za-z0-9]+)$', 'edit', name='edit'),

urlpatterns = patterns(
    'noc.apps.myapp.views',
    url(r'^$', 'index', name='index'),
    url(r'^add/$', 'add', name='add'),
    url(r'^edit/(?P<slug>[A-Za-z0-9]+)$', 'edit', name='edit'),

Multiple instances of frontpage could be achieved using the top level namespace;

urlpatterns = patterns(
    '',
    url(r'^admin/', include(admin.site.urls)),  # NOQA
    url(r'frontpage/', include('noc.apps.frontpage.urls', namespace='frontpage1', app_name='frontpage')),
    url(r'frontpage/', include('noc.apps.frontpage.urls', namespace='frontpage2', app_name='frontpage')),

That way you should be able to target the top level instance namespace, followed by the app namespace like this;

{% url 'frontpage1:frontpage:edit_page' slug='SLUG' %}
{% url 'frontpage2:frontpage:edit_page' slug='SLUG' %}

But if you would like to keep your template links more generic I believe you can leave out the top level namespace and django will resolve to the current app which you have to add to the request object. This is detailed at the end of Reversing Namespaced URLs

like image 154
markwalker_ Avatar answered Feb 13 '23 02:02

markwalker_


For anyone to reference.

This worked for me, Django 2.2.2

# project urls.py
# DjpollsConfig is my app config
from djpolls.apps import DjpollsConfig
djpolls_app_name = DjpollsConfig.name

urlpatterns = [
    path(djpolls_app_name, include('djpolls.urls', namespace='djpolls_app_name'))
]
# app urls.py
from django_proj.urls import djpolls_app_name
app_name = djpolls_app_name
# app template
{% url 'djpolls_app_name:detail' question.id %}

Hope it helps!

like image 22
Nam Le Avatar answered Feb 13 '23 03:02

Nam Le