Url consolidation and modularization

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 inside this single file. For example, look at the the urls.py file illustrated in listing 2-12.

Listing 2-12. Django urls.py with no url consolidation

# Contents coffeehouse/urls.py

from django.urls import path
from django.views.generic import TemplateView
from coffeehouse.about import views as about_views
from coffeehouse.stores import views as stores_views

urlpatterns = [
    path('',TemplateView.as_view(template_name='homepage.html')),
    path('about/contact/',about_views.contact),
    path('about/',about_views.index),
    path('stores/<int:store_id>/',stores_views.detail,{'location':'headquarters'}),
    path('stores/',stores_views.index),
   ]

As you can see in listing 2-12, 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-13 shows an updated version of the urls.py file in listing 2-13 with the about/ and stores/ roots are placed in separate files.

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

# Contents coffeehouse/urls.py

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

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

Listing 2-13 makes use of the include argument to load urls from completely separate files. 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-13 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 2-14 illustrates the contents of the file /coffeehouse/about/urls.py linked via include('coffeehouse.about.urls').

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

# Contents coffeehouse/about/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('',views.index),
    path('contact/',views.contact),
]

A quick look at listing 2-14 and you can see the structure is pretty similar to the main urls.py file, however, there are some minor differences. While the url path '' can look like it matches the home page, it isn't. Because the file in listing 2-14 is linked via include in the main urls.py file, Django joins the url path with the parent url regular expression. So the first url in listing 2-14 actually matches /about/ and the second url in listing 2-14 actually matches /about/contact/. Also because the urls.py file in listing 2-14 is placed alongside the app's views.py file, the import statement uses the relative path 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 2-15.

Listing 2-15 Django urls.py with inline include statements

# Contents coffeehouse/urls.py

from django.urls import include, path
from django.views.generic import TemplateView
from coffeehouse.about import views as about_views
from coffeehouse.stores import views as stores_views

store_patterns = [
    path('',stores_views.index),
    path('<int:store_id>)/',stores_views.detail),
]

about_patterns = [
    path('',about_views.index),
    path('contact/',about_views.contact),
]

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

The outcome of the url patterns in listing 2-15 is the same as listings 2-13 and 2-14. The difference is listing 2-15 uses the main urls.py file to declare multiple url lists, while listings 2-13 and 2-14 rely on url lists declared in different files.