One method would be to make the regular expression read all the given filters as a single string, and then split them up into individual values in the view.
I came up with the following URL:
(r'^(?P<city>[^/]+)/(?P<state>[^/]+)(?P<filters>(?:/[^/]+/[^/]+)*)/?$',
'views.my_view'),
Matching the required city and state is easy. The filters
part is a bit more complicated. The inner part - (?:/[^/]+/[^/]+)*
- matches filters given in the form /name/value
. However, the *
quantifier (like all Python regular expression quantifiers) only returns the last match found - so if the url was /radius/80/company/mycompany/
only company/mycompany
would be stored. Instead, we tell it not to capture the individual values (the ?:
at the start), and put it inside a capturing block which will store all filter values as a single string.
The view logic is fairly straightforward. Note that the regular expression will only match pairs of filters - so /company/mycompany/radius/
will not be matched. This means we can safely assume we have pairs of values. The view I tested this with is as follows:
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)
Two things to note about this. First, it allows unknown filter entries into your view. For example, /fakefilter/somevalue
is valid. The view code above ignores these, but you probably want to report an error to the user. If so, alter the code getting the values to
radius = filters.pop('radius', None)
company = filters.pop('company', None)
Any entries remaining in the filters
dictionary are unknown values about which you can complain.
Second, if the user repeats a filter, the last value will be used. For example, /radius/80/radius/50
will set the radius to 50. If you want to detect this, you will need to scan the list of values before it is converted to a dictionary:
given = set()
for name in split[::2]:
if name in given:
# Repeated entry, complain to user or something.
else:
given.add(name)