toggle_params

Issue #1 resolved
David Townshend created an issue

How about adding a toggle_params template tag? This would be useful for switch-type options (e.g. filters on views, etc). A simple implementation is:

@register.simple_tag
def toggle_params(url, **kwargs):
    url = UrlHelper(url)
    for param, value in kwargs.items():
        value = unicode(value)
        if value in url.query_dict.getlist(param):
            url.del_params(**{param: value})
        else:
            url.overload_params(**{param: value})
        return url.get_full_path()
    return ''

Usage is similar to overload_params, but if the query dict already has the param=value, then they are removed instead.

Comments (5)

  1. Branko Vukelic

    It's not a bad thing to have, for sure. However, my original idea was that UrlHelper should have all the features that you get with template tags. Now, my gut feeling tells me that adding toggle_params to UrlHelper is not such a good idea. The API would be a bit ugly without letting UrlHelper instance know about the on-off states of different params.

    On the other hand, letting UrlHelper know about such states, is well outside the scope of simply manipulating URLs. This calls for a higher-level API that would encapsulate various business logic patterns. A new higher-level API would open a whole new world of possibilities for working with URLs, too. Once that's done, we can talk about adding template tags that expose such functionality to the template. I'll give it some though and post ideas here.

    Meanwhile, I'm on vacation until 15th, so I'm not sure about the timeline for the new design. Very likely after 15th.

    As for your implementation, I'm not sure that return url.get_full_path() should be in the for loop. I think replacing return '' with return url.get_full_path() would be a better idea. If there are no arguments, it would simply return the original URL intact that way.

  2. David Townshend reporter

    Reading your code I assumed you were trying to keep the logic in UrlHelper, my implementation was really just meant to be conceptual and, as you've already pointed out, not well tested! I just dumped my own implementation (for a simpler use case - one parameter) and tweaked it to fit.

    Surely UrlHelper already has all the info it needs to track on/off state? A toggle_params method in UrlHelper could be (once again, untested)

    def toggle_params(self, **kwargs):
        for param, value in kwargs.items():
            value = unicode(value)
            if value in self.query_dict.getlist(param):
                self.del_params(**{param: value})
            else:
                self.overload_params(**{param: value})
    

    However, that doesn't mean that a higher API wouldn't be better!

    I have also implemented my own {% ifhasparams %} conditional tag, which I'll can post if you're interested. Alternatively I can work on a fork and send a pull request. That would at least allow me to write some proper tests.

  3. Branko Vukelic

    Yeah, logic for manipulating URLs, but not business logic, of how you use the URL. I hope I'm making sense. Of course, it's still possible that I don't.

    So, thinking outloud, there are two scenarios that I had in mind when it comes to how you use this whole package. One is to manipulate URLs in the controller and templatetags code (in Python). The other is to manipulate URLs in string format within the templates using template tags and filters. The complete API must support all functionality in both scenarios. So, toggle_param should be an UrlHelper method like the one you provided in the comment, and then you write a very simple filter or tag that exposes it.

    Now, while the solution you proposed works, I'm not happy with the way you need to pass kwargs to it. It raises questions like what happens if the parameter is there, but has a different value? It won't get removed. Is that what user expects? There could be cases for both complete removal of all params of the same name, and there could be cases if you just need to toggle a specific name-value pair. The API is too ambiguous, I think, and there is no mechanism to say what should happen in case of multiple parameters of the same name.

    So my thought is to provide an more high-level API that would be something like the forms framework for Django. You define all the params you want, and maybe provide an interface so users can do something with the params.

    Let's consider this (just tossing it out for discussion):

    class QueryHelper(UrlHelper):
        togglable_params = {}
    
        def toggle_param(self, param):
            # some logic to toggle params here
    
    
    class MyQuery(QueryHelper):
        togglable_params = {
            'include_all': ('allowed', 'on', 'values',)
        }
    
    q = MyQuery(url)
    
    q.toggle('include_all')
    

    Since it's a subclass of UrlHelper, it will have all the methods of the UrlHelper, but it will also add properties that are specific to your application's business logic. With this in place, we can then proceed to provide a logical API for toggle_params tag/filter.

    What do you think? Does this make sense?

  4. Log in to comment