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
|
Comments