One of my pet peeves when it comes to Django is that you can’t call methods that require arguments in templates. While this is fine most of the time, it does mean that you need to have one property or method per call you want to make, which sometimes gets very cumbersome.
I needed a way to define various dynamic permissions that are calculated at runtime (for irrelevant reasons, Django’s permissions framework wasn’t a good fit), and writing properties like can_register
, can_add_tags
, can_subscribe
got tedious. These tended to be defined all over the place, rather than in one central spot, and it was hard to add more checks without cluttering the classes.
I would much prefer to have a single method (let’s call it can()
) that accepted a string with the permission I wanted to check, and return True
or False
, depending. This is easy to do in the views, but templates would never be able to call it with an argument.
However, since Django can do dictionary-style attribute lookups, I could add a dictionary interface over the method, and allow it to be called both from the views (user.can("add_tags")
) and templates ({{ user.can.add_tags }}
).
To perform this sort of dynamic call/lookup with an argument in templates, the easiest thing to do is to define a decorator that will enable dict-style access to the method, as well as call-style access. Here’s the code:
class AttrLookup(object):
def __init__(self, func, instance=None):
self._func = func
self._instance = instance
def __get__(self, instance, owner):
# Return a new instance of the decorator with the class embedded, rather than
# store instance here, to avoid race conditions in multithreaded scenarios.
return AttrLookup(self._func, instance)
def __getitem__(self, argument):
return self._func(self._instance, argument)
def __call__(self, argument=None):
# When resolving something like {{ user.can.change_number }}, Django will call
# each element in the dot sequence in order (or otherwise try to access it).
# When trying to call can(), that will fail, because it expects an extra
# argument, so Django will fail and leave it at that.
# If there's no argument passed, we return itself, so Django can continue with
# the attribute lookup down the chain.
if argument is None:
return self
else:
return self._func(self._instance, argument)
Then, just use it like so:
class User(models.Model):
@AttrLookup
def can(self, permission):
if permission == "some_permission":
# Obviously, your check will be more complicated than this.
return True
return False
How it works should be pretty straightforward. The decorator is called with the bound method on instantiation (AttLookup(can)
) and returns an AttrLookup instance (let’s call it attr_lookup
). When that gets accessed dictionary-style, __getitem__
calls the decorated method. Same when it’s called directly, except there’s a small hitch with how Django evaluates templates that needs to be worked around.
As you can see, this is a pretty simple and straightforward way to enable calls of methods that accept a single argument in Django templates. I’m not entirely sure it’s not too clever, but what it does to the decorated function is straightforward and seems like good design. If you disagree, please let me know in the comments, as I’m curious to know if there’s a better way. In any case, I hope you’ll find the decorator useful!