Order of includes in root urls.py

Someone asked me a question about whether the order of include() calls in a root urls.py file matters. I thought the answer was yes, but after trying to come up with a meaningful example I’m not so sure about that.

Imagine a piano store that sells pianos as its main business, but has a small side room with some synths. The online store is almost entirely focused on pianos, but they’ve got a few synths as well. Their developer made a pianos app whose URLs start by matching the empty string but later added a synths app. Here’s the root urls.py file:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('pianos.urls')),
    path('synths/', include('synths.urls')),
]

I thought that if a path for an include() call matched the empty string, that path had to come last in the list of patterns, otherwise it could match some URLs that should match patterns listed later. But I’m struggling to find anything that should match a synths URL that would get matched by the middle pianos pattern here.

I can come up with a scenario where someone decides to sell a few synths in the piano department, and puts a pattern like path("synths/.../") in pianos/urls.py. That could certainly duplicate a URL that already exists in the synths app. But moving the pianos include() call to the last position in the root urls.py wouldn’t fix that. The duplicated URL in pianos would then be matched by the synths URL. So in this scenario the real problem is a lack of coordination between apps about maintaining unique URL paths, not the order of includes in the root urls.py, right? Is it a best practice not to have any app match the empty string in the root urls.py, even it it’s the main app in the project?

Is there a URL that should match the synths app in the above root urls.py, but gets grabbed by the pianos include() call, that would be fixed by moving the pianos include() call to the last position? Or does the order here really not matter in any reasonable set of URLs?

The order does matter - especially where regex paths are concerned.

But first, you ask:

I guess it depends upon what you mean by “fix”.

A url will only dispatch to one view - and it will dispach to the first view matching the url pattern.

You answered that above when you said:

So it does “fix” that, in that those urls would now be handled by synths.urls and not by pianos.urls.

It’s up to you to decide which of those modules will handle /synths/yamaha/mx88bk.

That is correct.

That’s an architectural decision. In general, I would say it’s not best practice to not have it. Or, to attempt to untorture that sentence by removing the double negative - I believe it’s a best practice to have it. You likely still want to have the empty string to map it to a “home” page. I just don’t suggest mapping the empty string to an included url module.

Now, in the non-trivial case, a real-world example of the value for doing this is the opportunity to override urls handled by third-party modules.

Let’s say for instance that you want to provide a very customized page for editing an instance of a model - doing stuff that the built-in admin won’t do. But, you want to integrate that page within the admin url structure.

You could define a url such as:
path('admin/auth/user/<int:pk>/change/', my_user_editor, name='user_edit')

If it resides above the path('admin/', admin.site.urls), definition, then it’s your view that will be called to edit the User model and not the system-defined view.

Thanks, this has been really helpful.

Is it a best practice not to have any app match the empty string in the root urls.py , even it it’s the main app in the project?

That’s an architectural decision. In general, I would say it’s not best practice to not have it. Or, to attempt to untorture that sentence by removing the double negative - I believe it’s a best practice to have it. You likely still want to have the empty string to map it to a “home” page. I just don’t suggest mapping the empty string to an included url module.

I certainly get the value in having the empty string map to a home page.

I’m on the fence about mapping the empty string to an included URL module though. I can see the benefits of namespacing every app’s URLs by including a root word in the include pattern. But if a project has one main app, then most of the URLS will have an extra word that isn’t all that necessary. Sometimes it can be nice to have shorter, cleaner URLs.

I think the big takeaway, as always, is to make sure you understand the URL dispatch model, and understand how the decisions you make here will affect URL dispatching in your specific project.