############################################################################## # @register.tag(name = 'fancy_if') def fancy_if(parser, token): """A fancy if tag. It has two notable features: 1) you can combine and, or, and not statements nested as much as you like. 2) you can test object like {% if %} AND you can test row level permissions in the same expression. NOTE: To simplify my sanity, I use a simple prefix expression grammar. So, for example, to test if a discussion is not locked or the user has moderate permission on the forum the discussion is in: {% fancy_if or not discussion.locked 'asforums.has_moderate' discussion.forum %} ... {% else %} {% end_fancy_if %} The syntax is: {% fancy_if expr %} An expr may be: ( expr ) or expr ... (a list of one or more expr's or'd together) and expr ... (a list of one or more expr's or'd together) not expr 'permission code name' |None eq |'string' |'string' If you wish to nest or & and you use '(' ')' to express the sub-expression. These characters MUST be separated by white space, ie: Good: ( and foo bar biz bat ) Bad: (and foo bar biz bat) NOTE: Permissions are expressed by being surrounded by matching double or single quotes. ie: 'asforums.forum_moderate' or 'asforums.disc_read'. NOTE: The permission expression MUST be two tokens. A permission and a variable reference. If you wish to do class level permission checking using the value 'None' (with no quotes!) as the variable reference. """ bits = token.contents.split() tag = bits.pop(0) if not bits: raise template.TemplateSyntaxError, \ "'fancy_if' statement requires at least one argument" expr = parse_fi_expressions(bits, parser) nodelist_true = parser.parse(('else', 'end_' + tag)) token = parser.next_token() if token.contents == 'else': nodelist_false = parser.parse(('end_' + tag,)) parser.delete_first_token() else: nodelist_false = template.NodeList() return FancyIfNode(expr, nodelist_true, nodelist_false) ############################################################################## # class FancyIfNode(template.Node): """The template Node object that represents our 'fancy if'. """ def __init__(self, expr, nodelist_true, nodelist_false): self.expr = expr self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false def __repr__(self): return "" % repr(self.expr) def __iter__(self): for node in self.nodelist_true: yield node for node in self.nodelist_false: yield node def get_nodes_by_type(self, nodetype): nodes = [] if isinstance(self, nodetype): nodes.append(self) nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) return nodes def render(self, context): if self.expr.eval(context): return self.nodelist_true.render(context) return self.nodelist_false.render(context) ############################################################################## # def parse_fi_expressions(tokens, parser): """Given a list of tokens that comprised the 'fancy_if' statement parse them in to a list of node trees that represents the expressions to be evaluated. """ try: token = tokens.pop(0) if token == "(": res = parse_fi_expressions(tokens, parser) if tokens.pop(0) != ")": raise template.TemplateSyntaxError, "Missing matching ')'" return res elif token == "not": if len(tokens) < 1: raise TemplateSyntaxError, "'%s' must come with an expres" \ "sion to operate on." % token return FiNot(parse_fi_expressions(tokens, parser)) elif token in ("and", "or"): if len(tokens) < 1: raise TemplateSyntaxError, "'%s' must come with a list of " \ "expressions to operate on." % token res = [] while len(tokens) > 0: if tokens[0] == ")": break res.append(parse_fi_expressions(tokens, parser)) if token == "and": return FiAnd(res) return FiOr(res) elif token[0] == token[-1] and token[0] in ('"', "'"): return FiPerm(token[1:-1], parser.compile_filter(tokens.pop(0))) elif token == "eq": return FiEquals(parser.compile_filter(tokens.pop(0)), parser.compile_filter(tokens.pop(0))) # Otherwise it is just a variable reference. # return FiVar(parser.compile_filter(token)) except IndexError: raise template.TemplateSyntaxError, "Mis-matched terms and " \ "operators. Ran out of tokens whilst parsing arguments." ############################################################################## # # Here we have a simple set of classes to define our parsed expression # syntax in a format that is easy to eval when we have a context we need # to render. We define a sub-class for each type of operation. They must # have an eval() method which returns True or False, and a __repr__ method # that gives us a simple string representation. # class FiExpr(object): """The root object for the expression tree in our fancy-if node. """ def __repr__(self): raise NotImplementedError def eval(self, context): raise NotImplementedError class FiNot(FiExpr): def __init__(self, expr): self.expr = expr def __repr__(self): return "(not: %s )" % repr(self.expr) def eval(self, context): return not self.expr.eval(context) class FiAnd(FiExpr): def __init__(self, expr_list): self.expr_list = expr_list def __repr__(self): return "(and:" + " ".join([repr(x) for x in self.expr_list]) + " )" def eval(self, context): for elt in self.expr_list: if not elt.eval(context): return False return True class FiOr(FiExpr): def __init__(self, expr_list): self.expr_list = expr_list def __repr__(self): return "(or:" + " ".join([repr(x) for x in self.expr_list]) + " )" def eval(self, context): for elt in self.expr_list: if elt.eval(context): return True return False class FiEquals(FiExpr): def __init__(self, obj_var1, obj_var2): self.obj_var1 = obj_var1 self.obj_var2 = obj_var2 def __repr__(self): return "( eq %s %s )" % (self.obj_var1, self.obj_var2) def eval(self, context): try: obj1 = self.obj_var1.resolve(context) except template.VariableDoesNotExist: obj1 = None try: obj2 = self.obj_var2.resolve(context) except template.VariableDoesNotExist: obj2 = None return obj1 == obj2 class FiPerm(FiExpr): def __init__(self, perm, obj_var): self.perm = perm self.obj_var = obj_var def __repr__(self): return "(has perm '%s' on %s )" % (self.perm, self.obj_var.var) def eval(self, context): if self.obj_var == None: obj = None else: try: obj = self.obj_var.resolve(context) except template.VariableDoesNotExist: obj = None try: user = template.resolve_variable("user", context) except template.VariableDoesNotExist: return settings.TEMPLATE_STRING_IF_INVALID return user.has_perm(self.perm, object=obj) class FiVar(FiExpr): def __init__(self, obj_var): self.obj_var = obj_var def __repr__(self): return self.obj_var.var def eval(self, context): try: obj = self.obj_var.resolve(context) except template.VariableDoesNotExist: obj = None if obj: return True return False