I have a Django URL like this:
url(
r'^project_config/(?P<product>\w+)/(?P<project_id>\w+)/$',
'tool.views.ProjectConfig',
name='project_config'
),
views.py:
def ProjectConfig(request, product, project_id=None, template_name='project.html'):
...
# do stuff
The problem is that I want the project_id
parameter to be optional.
I want /project_config/
and /project_config/12345abdce/
to be equally valid URL patterns, so that if project_id
is passed, then I can use it.
As it stands at the moment, I get a 404 when I access the URL without the project_id
parameter.
There are several approaches.
One is to use a non-capturing group in the regex: (?:/(?P<title>[a-zA-Z]+)/)?
Making a Regex Django URL Token Optional
Another, easier to follow way is to have multiple rules that matches your needs, all pointing to the same view.
urlpatterns = patterns('',
url(r'^project_config/$', views.foo),
url(r'^project_config/(?P<product>\w+)/$', views.foo),
url(r'^project_config/(?P<product>\w+)/(?P<project_id>\w+)/$', views.foo),
)
Keep in mind that in your view you'll also need to set a default for the optional URL parameter, or you'll get an error:
def foo(request, optional_parameter=''):
# Your code goes here
Django > 2.0 version:
The approach is essentially identical with the one given in Yuji 'Tomita' Tomita's Answer. Affected, however, is the syntax:
# URLconf
...
urlpatterns = [
path(
'project_config/<product>/',
views.get_product,
name='project_config'
),
path(
'project_config/<product>/<project_id>/',
views.get_product,
name='project_config'
),
]
# View (in views.py)
def get_product(request, product, project_id='None'):
# Output the appropriate product
...
Using path()
you can also pass extra arguments to a view with the optional argument kwargs
that is of type dict
. In this case your view would not need a default for the attribute project_id
:
...
path(
'project_config/<product>/',
views.get_product,
kwargs={'project_id': None},
name='project_config'
),
...
For how this is done in the most recent Django version, see the official docs about URL dispatching.
project_id
still in the path in case of the default using a dict
. This can lead to seemingly weird behavior, as the argument provided in the dict
will always be used (if I remember correctly).
You can use nested routes
Django <1.8
urlpatterns = patterns(''
url(r'^project_config/', include(patterns('',
url(r'^$', ProjectConfigView.as_view(), name="project_config")
url(r'^(?P<product>\w+)$', include(patterns('',
url(r'^$', ProductView.as_view(), name="product"),
url(r'^(?P<project_id>\w+)$', ProjectDetailView.as_view(), name="project_detail")
))),
))),
)
Django >=1.8
urlpatterns = [
url(r'^project_config/', include([
url(r'^$', ProjectConfigView.as_view(), name="project_config")
url(r'^(?P<product>\w+)$', include([
url(r'^$', ProductView.as_view(), name="product"),
url(r'^(?P<project_id>\w+)$', ProjectDetailView.as_view(), name="project_detail")
])),
])),
]
This is a lot more DRY (Say you wanted to rename the product
kwarg to product_id
, you only have to change line 4, and it will affect the below URLs.
Edited for Django 1.8 and above
Even simpler is to use:
(?P<project_id>\w+|)
The "(a|b)" means a or b, so in your case it would be one or more word characters (\w+) or nothing.
So it would look like:
url(
r'^project_config/(?P<product>\w+)/(?P<project_id>\w+|)/$',
'tool.views.ProjectConfig',
name='project_config'
),
None
. Meaning that you can't rely on a default value in the view's signature for this: you have to explicitly test it inside and assign in consequence.
Thought I'd add a bit to the answer.
If you have multiple URL definitions then you'll have to name each of them separately. So you lose the flexibility when calling reverse since one reverse will expect a parameter while the other won't.
Another way to use regex to accommodate the optional parameter:
r'^project_config/(?P<product>\w+)/((?P<project_id>\w+)/)?$'
Reverse for 'edit_too_late' with arguments '()' and keyword arguments '{'pk': 128}' not found. 1 pattern(s) tried: ['orders/cannot_edit/((?P<pk>\\d+)/)?$']
Django = 2.2
urlpatterns = [
re_path(r'^project_config/(?:(?P<product>\w+)/(?:(?P<project_id>\w+)/)/)?$', tool.views.ProjectConfig, name='project_config')
]
Use ? work well, you can check on pythex. Remember to add the parameters *args and **kwargs in the definition of the view methods
url('project_config/(?P<product>\w+)?(/(?P<project_id>\w+/)?)?', tool.views.ProjectConfig, name='project_config')
Success story sharing