Introduction
Update: I realized I didn't fully explained the whole picture of the issue I am describing here, so I have added a few lines more in the introduction section.
In a project with an application named concerts that lists concerts from european cities, consider a classic two-level URLconf where the root urls.py module "includes" (i.e. uses the include method from django.conf.urls.defaults) a second URLconf module.
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^concerts/', include('concerts.urls'))
)
concerts/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('concerts.views',
url(regex=r'^list/$', view='my_view', name='concerts_list_all')
)
Let's say you want to extend the URLconf and have two kinds of URL to call the same view, the first to display all concerts, the second for concerts in a given city:
/concerts/list/ /barcelona/concerts/list/ /london/concerts/list/ /paris/concerts/list/
There are two situations we want to overcome in this scenario. First, only one view must be used, so a little bit of business logic must be implemented. The function behaves differently if it receives the location keyword argument or not:
concerts/views.py
def my_view(request, location=None):
if location is not None:
# filter concerts QuerySet by location, for example
else:
# query all concerts
return render_to_response('base.html', {'location': location},
context_instance=RequestContext(request))
The second one is the one that causes me to go through this workaround. As we are making use of the Django named urls, so I have to be able to use two named ursl with their respective params each one. For example, in Django template language:
{% url concerts_list %}
{% url concerts_list_location location %}
First attempt: easily discarded
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^concerts/', include('concerts.urls'))
(r'^barcelona/concerts/', include('concerts.urls'))
(r'^london/concerts/', include('concerts.urls'))
(r'^paris/concerts/', include('concerts.urls'))
)
With this solution, I would have to create a new pattern each time a city was added to the project database. Obviously, we should remember to update the URLconf in some manual or automatic way. No way.
Second attempt: creating two urls.py
The last 3 URLs can be done easily by creating a URL pattern with a keyword argument location in the root URLconf:
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^concerts/', include('concerts.urls'))
(r'^(?P<location>\w+)/concerts/', include('concerts.urls_location'))
)
Then we duplicate concerts/urls.py to create concerts/urls_location.py and modify this latest one to use another named URL name:
concerts/urls_location.py
from django.conf.urls.defaults import *
urlpatterns = patterns('concerts.views',
url(regex=r'^list/$', view='my_view', name='concerts_list_location')
)
If we wouldn't change the name from the url and left it the same for both files, the Django URL dispatcher would mess up.
So, if we make a request to /concerts/list/, concerts.urls would be included, and for a request like /barcelona/concerts/list/ concerts/urls_location.py would be.
But it is stupid to have two files with so small changes between them (only the url name params). Also, every modification in one of them must be replicated in the other. It sucks.
Final solution #1
concerts/urls.py
from django.conf.urls.defaults import *
base_urls = ({
'regex': r'^$',
'view': 'my_view',
'name': 'my_view',
},)
urls = [url(**base_url) for base_url in base_urls]
urlpatterns = patterns('myapp.views', *urls)
concerts/urls_location.py
from django.conf.urls.defaults import *
from myapp.urls import urlpatterns as orig_urlpatterns
new_urlpatterns = orig_urlpatterns[:]
for u in new_urlpatterns:
u.name = '_'.join([u.name, 'alt'])
urlpatterns = patterns('', *new_urlpatterns)
In the first URLconf module I create a tuple of url params dictionaries. This structure is the one to mantain. the patterns is automatically populated with it. Then, in the second one, before populating patterns we append a suffix into each url name param.
So, for example, in a template we can make unique calls:
{% url concerts_list_all %} {# renders '/concerts/list/' #}
{% url concerts_list_location "barcelona" %} {# renders '/barcelona/concerts/list/' #}
Final solution #2
I recently came to a more simple and pythonic solution which also has the advantage that concerts/urls.py can be left in the common django syntax for URLconf modules:
concerts/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('myapp.views',
url(
regex = r'^$',
view = 'my_view',
name = 'my_view',
),
)
concerts/urls_location.py
from django.conf.urls.defaults import *
from myapp.urls import urlpatterns as orig_urlpatterns
new_urlpatterns = orig_urlpatterns[:]
for u in new_urlpatterns:
u.name = '_'.join([u.name, 'alt'])
urlpatterns = patterns('', *new_urlpatterns)