Consolidate and modularize Django URL definitions

Problem

You want to split your Django project's urls.py file into multiple files. You want to use shortcuts where possible to make URL definitions less verbose. You want to modularize URL definitions to make it easier to add new URLs and update old URLs.

Solution

Declare URLs in separate files and use the include argument in the url method to load these different files. Declare URLs as Python lists and use the include argument to keep everything in a single file.

How it works

By default, Django looks up URL definitions in the urls.py file inside a project's main directory -- it's worth mentioning this is on account of the ROOT_URLCONF variable in settings.py. However, once a project grows beyond a couple of URLs, it can become difficult to manage them all inside this single file. Take for example the urls.py file illustrated in listing 1.

Listing 1 - Django urls.py with no URL consolidation


from django.conf.urls import url
from django.views.generic import TemplateView
from coffeehouse.about import views as about_views
from coffeehouse.stores imports views as stores_views

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/',about_views.index),
    url(r'^about/contact',about_views.contact),
    url(r'^stores/',stores_views.index),
    url(r'^stores/(?P<store_id>\d+)/',stores_views.detail,{'location':'headquarters'}),
   ]

As you can see in listing 1, there are a couple of URLs that have redundant roots -- about/ and stores/. Grouping these URLs separately can be helpful because it keeps common URLs in their own files and avoids the difficulties of making changes to one big urls.py file.

Listing 2 shows an updated version of urls.py where URLs with the about/ and stores/ roots are placed in separate files.

Listing 2 - Django urls.py with include to consolidate URLs

from django.conf.urls import include, url
from django.views.generic import TemplateView

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/',include('coffeehouse.about.urls')),
    url(r'^stores/',include('coffeehouse.stores.urls'),{'location':'headquarters'}),
   ]

Listing 2 makes use of the include argument to load URLs from a completely separate file. In this case, include('coffeehouse.about.urls') tells Django to load URL definitions from the Python module coffeehouse.about.urls, which parting from a Django base directory corresponds to the file route /coffeehouse/about/urls.py. In this case, I kept using the urls.py file name and placed it under the corresponding Django about app directory since it deals with about/ URLs. However, you can use any file name or path you like for URL definitions (e.g. coffeehouse.allmyurl.resturls to load URLs from a file route /coffeehouse/allmyurls/resturls.py).

The second include statement in listing 2 works just like the first one, where include('coffeehouse.stores.urls') tells Django to load URL definitions from the Python module coffeehouse.stores.urls. However, notice this second statement appends an additional dictionary as a URL extra option, which means all the URLs in the include statement will also receive this extra option.

Listing 3 illustrates the contents of the file /coffeehouse/about/urls.py linked via include('coffeehouse.about.urls').

Listing 3 - Django /coffeehouse/about/urls.py loaded via include

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$',views.index),
    url(r'^contact$',views.contact),
]

A quick look at listing 3 and you will notice the structure is pretty similar to the main urls.py file, however, there are some minor differences. While the URL regular expression r'^$' can look like it matches the home page, it isn't. Because the file in listing 3 is linked via include in the main urls.py file, Django joins the URL regular expression with the parent URL regular expression. So the first URL in listing 3 actually matches /about/ and the second URL in listing 3 actually matches /about/contact. Also because the urls.py file in listing 3 is placed alongside the app's views.py file, the import statement uses the from . import views syntax.

In addition to using the include option to reference a separate file with URL definitions, the include option can also accept URL definitions as a Python list. In essence, this allows you to keep all URL definitions in the main urls.py file, but give it more modularity. This approach is illustrated in listing 4.

Listing 4 - Django urls.py with inline include statements

from django.conf.urls import include, url
from django.views.generic import TemplateView

from coffeehouse.about import views as about_views
from coffeehouse.stores imports views as stores_views


store_patterns = [
    url(r'^$',stores_views.index),
    url(r'^(?P<store_id>\d+)/$',stores_views.detail),
]

about_patterns = [
    url(r'^$',about_views.index),
    url(r'^contact$',about_views.contact),
]

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html')),
    url(r'^about/',include(about_patterns)),
    url(r'^stores/',include(store_patterns),{'location':'headquarters'}),
   ]