django - 可选URL参数的正则表达式
我在Django中有一个视图,可以接受多种不同的过滤参数,但这些参数都是可选的。如果我有6个可选的过滤器,难道真的要为这6个过滤器的每一种组合都写一个网址吗?还是说有办法定义网址中哪些部分是可选的呢?
举个简单的例子,如果只有2个过滤器,我可能会有这些网址的组合:
/<city>/<state>/
/<city>/<state>/radius/<miles>/
/<city>/<state>/company/<company-name>/
/<city>/<state>/radius/<miles>/company/<company-name>/
/<city>/<state>/company/<company-name>/radius/<miles>/
这些网址都指向同一个视图,唯一必需的参数是城市和州。可是如果有6个过滤器,这就变得难以管理了。
那么,有什么好的方法可以实现我想要的效果呢?
4 个回答
你也可以只设置一个网址,这个网址只检查路径的开头部分,确保它们是相同的,然后在你的视图中解析 request.path
。另一方面,如果你有很多可选的过滤参数,并且它们可以以各种组合出现,最好的解决办法通常是通过 GET
参数来进行过滤,特别是当用于过滤的网址不需要为任何搜索引擎进行优化时……
这正是使用GET参数的场景。你的网址配置应该是 /city/state/
,然后各种筛选条件可以作为GET变量放在后面:
/city/state/?radius=5&company=google
现在,在你的视图中,你可以像平常一样接受 city
和 state
作为参数,但其他的筛选条件都存储在 request.GET
的查询字典里。
一种方法是让正则表达式把所有的过滤器当作一个整体的字符串来读取,然后在视图中把它们拆分成单独的值。
我想出了以下这个网址:
(r'^(?P<city>[^/]+)/(?P<state>[^/]+)(?P<filters>(?:/[^/]+/[^/]+)*)/?$',
'views.my_view'),
匹配所需的城市和州很简单。filters
部分就稍微复杂一点。里面的部分 - (?:/[^/]+/[^/]+)*
- 用来匹配形如/name/value
的过滤器。不过,*
这个符号(和Python中的其他正则表达式符号一样)只会返回最后找到的匹配项,所以如果网址是/radius/80/company/mycompany/
,那么只会存储company/mycompany
。为了避免这个问题,我们告诉它不要单独捕获这些值(开头的?:
就是这个意思),而是把它放在一个捕获块里,这样就能把所有过滤器的值作为一个整体字符串存储。
视图逻辑相对简单。需要注意的是,正则表达式只会匹配成对的过滤器 - 所以像/company/mycompany/radius/
这样的不会被匹配。这意味着我们可以安全地假设我们有成对的值。我测试的视图如下:
def my_view(request, city, state, filters):
# Split into a list ['name', 'value', 'name', 'value']. Note we remove the
# first character of the string as it will be a slash.
split = filters[1:].split('/')
# Map into a dictionary {'name': 'value', 'name': 'value'}.
filters = dict(zip(split[::2], split[1::2]))
# Get the values you want - the second parameter is the default if none was
# given in the URL. Note all entries in the dictionary are strings at this
# point, so you will have to convert to the appropriate types if desired.
radius = filters.get('radius', None)
company = filters.get('company', None)
# Then use the values as desired in your view.
context = {
'city': city,
'state': state,
'radius': radius,
'company': company,
}
return render_to_response('my_view.html', context)
关于这一点有两个要注意的地方。首先,它允许未知的过滤器进入你的视图。例如,/fakefilter/somevalue
是有效的。上面的视图代码会忽略这些,但你可能想要向用户报告一个错误。如果是这样,可以把获取值的代码改成:
radius = filters.pop('radius', None)
company = filters.pop('company', None)
在filters
字典中剩下的任何条目都是未知值,你可以对此提出投诉。
第二,如果用户重复使用某个过滤器,最后的值会被使用。例如,/radius/80/radius/50
会把半径设置为50。如果你想检测这种情况,就需要在把值转换成字典之前先扫描一下这个值列表:
given = set()
for name in split[::2]:
if name in given:
# Repeated entry, complain to user or something.
else:
given.add(name)