I am working on some apps that use row level permissions. For many permission tests I intend to make custom tags so I can locate the permission test used by the template and by the view in a common module.
However, frequently, before I decide what the actual end-tag is going to be I need a way to expression a more powerful 'if' structure in the template language.
Frequently the existing if and if_has_perm tags are just not powerful enough to express these things.
This may make the template language unacceptably more like a programming language but it has helped me quickly modify complex permission statements without having to next and repeat four or five clauses.
As you can see this is intended to work with django off of the row level permissions branch.
Here is an example of a template that is using the 'fancy_if' statement:
{% fancy_if or ( and not discussion.locked not discussion.closed "asforums.post_discussion" discussion ) "asforums.moderate_forum" discussion.forum %}
<li><a href="./create_post/?in_reply_to={{ post.id }}">{% icon "comment_add" "Reply to post" %}</a></li>
{% end_fancy_if %}
{% fancy_if or "asforums.moderate_forum" discussion.forum ( and not discussion.closed ( or eq post.author user eq discussion.author user ) ) %}
{% fancy_if or "asforums.moderate_forum" discussion.forum eq post.author user %}
<li><a href="{{ post.get_absolute_url }}update/">{% icon "comment_edit" "Edit Post" %}</a></li>
{% end_fancy_if %}
<li><a href="{{ post.get_absolute_url }}delete/">{% icon "comment_delete" "Delete Post" %}</a></li>
{% end_fancy_if %}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | ##############################################################################
#
@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' <variable>|None
eq <variable>|'string' <variable>|'string'
<variable>
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 "<FancyIf: %s >" % 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
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.