Django has some fantastic features for pretty URLs, but I haven't been able to find an elegant way to handle multiple GET vars.
Now, it may just be that making requests with GET vars isn't very 'django'-like, but I find GET vars are a very appropriate vector for view filters. Sticking sort and search filters into a URL is problematic, because it will result in unnecessarily long URLs filled with placeholders. After the jump, I look at some examples and explain how I've decided to solve the problem (for now).
For example, which looks more sensible?
http://myapp.com/blog/technical/date/desc
or
http://myapp.com/blog/technical/?sort=date&order=desc
Well, the first doesn't look too bad at this point, but what happens when you add a couple of filters that are seldom used. Say, a location-based search.
http://my.app.com/blog/technical/94607/10/date/desc
Our URL that uses GET is longer but much more readable:
http://my.app.com/blog/technical?zip=94607&dist=10&sort=date&order=desc
Notice we're still keeping some things in the URL, like the 'technical' category. I believe anything that has a 'default' view should probably have it's own URL. Once you're filtering results of that default view you *might* consider going to GET vars.
Managing multiple GET vars in a template isn't simple, however. First off, I hope you know well enough to NEVER EVER use a request string in a template. A request string is typically made up of untrusted user data. If any of this untrusted user data gets cached in a template, you now are wide-open to XSS attacks. I'm not absolutely positive this overcautious when it comes to django's http.request object and caching, but using user data to write a page is a bad habit, and can bite you other ways as well (a poorly validated user request used as a base can cascade into a link that will return a 404 or some other error).
Hardcoding get queries into the template is not a viable solution, because we prevent the user from "building" a set of filters -- what if I wanted to do a sort and then add a location filter, or vice versa? If we hardcode our queries, it'll start the user from scratch each time. This can be very frustrating -- I'm sure you've encountered apps where you've wanted to apply multiple filters simultaneously and have been unable to.
We could use a form that builds up a string instead of hardcoded links, but this limits our design possiblities.
What we want to be able to do is have each link setting a GET variable look at the existing GET variables and overwrite an existing variable if necessary. This is pretty simple to do using template filter.
Here's the template filter definition I've added to my application:
from django import template
from urllib import urlencode
from cgi import parse_qs
register = template.Library()
def createGet(dictQuery,modifyQueryString):
if modifyQueryString!=None:
print modifyQueryString
modifyDictQuery=parse_qs(modifyQueryString)
print modifyDictQuery
dictQuery.update(modifyDictQuery)
return urlencode(dictQuery,doseq=1)
register.filter('createGet', createGet)
Usage is simple. In your view, create a dict that has VALIDATED GET vars. Again, VALIDATED. You do not want to simply copy the request.GET dict because this could open you up to XSS exploits. Pass this dict in your template context. In the template, after loading your custom template tags, you'd use it somewhat like this:
Sort By:
<a href="?{{ validatedGetVars|createGet('sort=date')}}">Date</a>
<a href="?{{ validatedGetVars|createGet('sort=votes')}}">Votes</a>
Order:
<a href="?{{ validatedGetVars|createGet('order=desc')}}">Desc</a>
<a href="?{{ validatedGetVars|createGet('order=asc')}}">Asc</a>
A couple notes:
* This would probably be much more elegant as a template tag instead of a filter. I'll probably investigate this possibility as I move forward, but this works for now.
* the doseq=1 allows urlencode to process the parse_qs dict result properly. Because parse_qs returns each value in the key/value pairs as a list, it is not appropriate to directly use the resulting dict value as a GET var value.
* Make sure to restart your dev server after adding the templatetags/ dir or django may not see your new modules.