Django可选URL参数

225

我有一个像这样的 Django URL:

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
问题在于我希望 project_id 参数是可选的。
我想使 /project_config//project_config/12345abdce/ 成为同样有效的 URL 模式,这样如果传递了 project_id ,那么我就可以使用它。
目前情况是,如果我没有使用 project_id 参数访问 URL,我会得到 404 错误。
7个回答

468

2023年更新

这个答案已经过时,但仍然有活动。

请参考@j-i-l下面的答案,适用于Django > 2,并参考当前文档

原始2013年答案

有几种方法。

一种方法是在正则表达式中使用非捕获组:(?:/(?P<title>[a-zA-Z]+)/)?
使一个正则表达式Django URL标记可选

另一种更容易理解的方法是使用多个规则来满足您的需求,所有规则都指向同一个视图。

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),
)

请记住,在您的视图中,您还需要为可选的URL参数设置一个默认值,否则会出现错误。
def foo(request, optional_parameter=''):
    # Your code goes here

90
请投票支持多路线选项。+1 - Burhan Khalid
4
你能否通过为每个URL模式命名来解决反转问题? - Ted
10
我们能不能给每个视图都取同样的名称? - eugene
2
@Yuji'Tomita'Tomita 我知道,所以对于 Eugene 的问题,不幸的是,我们不能合理地拥有相同名称的多个视图,即使我们将它们实现为获取可选参数的一种方式。 - nnyby
3
@eugene 是的,我们可以有两个相同名称的URL,反转函数会根据参数智能地选择适用的那一个。 - Arpit Singh
显示剩余12条评论

65

Django > 2.0 版本:

这种方法基本上与Yuji 'Tomita' Tomita的答案相同。但是受影响的是语法:

# 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
    ...

使用path(),您还可以通过可选参数kwargs将额外的参数传递给视图,它是dict类型。 在这种情况下,您的视图不需要project_id属性的默认值:

    ...
    path(
        'project_config/<product>/',
        views.get_product,
        kwargs={'project_id': None},
        name='project_config'
    ),
    ...

关于如何在最新版的Django中完成这个操作,请参见URL分发的官方文档


2
你的代码里混淆了 project_id 和 product_id,是吗? - Andreas Bergström
@AndreasBergström 非常感谢您指出这一点!您说得很对!我匆忙更正了它,但稍后会再次检查。希望现在没问题了!如果使用dict的默认值,路径中仍然存在project_id。这可能会导致看似奇怪的行为,因为dict中提供的参数将始终被使用(如果我记得正确的话)。 - j-i-l
1
@jojo 这是否意味着第二个选项中的 'project_config/foo/bar' 将自动将 {'project_id': 'bar'} kwargs 传递给视图? - Original BBQ Sauce
在表单中使用 action="{{}}" 而不是 onsubmit,在我的情况下,只有这种方法才有效。 - undefined

37
你可以使用嵌套路由。
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")
        ])),
    ])),
]

这样更符合DRY原则(假设你想将product参数重命名为product_id,你只需要修改第4行代码,下面的URL也会受到影响。

适用于Django 1.8及以上版本


1
嵌套很好。此外,它通过使用缩进更清晰地分离了代码中不同的URL部分。 - Patrick
嵌套的问题在于,如果您有多个可选参数,则最终不会DRY,因为例如,对于3个可选参数,您有8种可能的URL组合。 您必须处理出现参数1,未出现参数1但出现参数2以及未出现参数1和参数2但出现参数3的情况。 与具有多个可选参数的单个字符串相比,URL段落将更难阅读。 使用符号常量来表示可选参数子字符串将使其非常易于阅读,并且只有一个URL。 - Bogatyr
我认为你说得对,但那更多是由于视图/URL设计不佳的结果。这个例子可以重新设计得更好。 - Jacob Valenta
扁平比嵌套更好。 - pjdavis

34

更简单的方法是使用:

(?P<project_id>\w+|)
"

“(a|b)” 表示 a 或 b,所以在你的情况下,它将是一个或多个单词字符(\w+)或什么都没有。

因此它看起来会像这样:

"
url(
    r'^project_config/(?P<product>\w+)/(?P<project_id>\w+|)/$',
    'tool.views.ProjectConfig',
    name='project_config'
),

10
我喜欢这个解决方案的简洁性,但要注意:这样做,视图仍将接收参数的值,该值将为None。这意味着您不能依赖视图签名中的默认值:您必须在内部明确测试它并相应地进行分配。 - Anto
1
这正是我在寻找的 =) - Mike Brian Olivera
3
如果project_id不存在,那么最后一个斜杠怎么处理? - iamkhush
你可以在斜杠后面添加一个问号,或者将斜杠包含在project_id模式中。 - Juan José Brown

9

我想在答案中再添加一点内容。

如果您有多个URL定义,则必须单独为每个定义命名。因此,当调用反向函数(reverse)时,由于其中一个反向函数将期望参数而另一个则不会,因此您将失去灵活性。

使用正则表达式另一种处理可选参数的方法:

r'^project_config/(?P<product>\w+)/((?P<project_id>\w+)/)?$'

4
在Django 1.6中,这个对我来说会抛出一个异常。我建议不要使用它。错误信息为"Reverse for 'edit_too_late' with arguments '()' and keyword arguments '{'pk': 128}' not found. 1 pattern(s) tried: ['orders/cannot_edit/((?P<pk>\d+)/)?$']"。 - Patrick

5

Django = 2.2

urlpatterns = [
    re_path(r'^project_config/(?:(?P<product>\w+)/(?:(?P<project_id>\w+)/)/)?$', tool.views.ProjectConfig, name='project_config')
]

2
使用 ? 可以很好地工作,您可以在 pythex 上进行检查。请记得在视图方法的定义中添加参数 *args 和 **kwargs。
url('project_config/(?P<product>\w+)?(/(?P<project_id>\w+/)?)?', tool.views.ProjectConfig, name='project_config')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接