|
1 | | -Templates |
2 | | -========= |
| 1 | +Templating |
| 2 | +========== |
3 | 3 |
|
4 | | -Writing HTML from inside Python can get cumbersome; it's much more fun to write Python from inside HTML. Luckily, web.py makes that pretty easy. |
| 4 | +There are almost as many Python templating systems as there are web |
| 5 | +frameworks (and, indeed, it seems like many templating systems are |
| 6 | +adopting web framework-like features). The following are the goals of `templetor`, which is the (codename of) templating system of web.py. |
5 | 7 |
|
6 | | -Let's make a new directory for our templates (we'll call it templates). Inside, make a new file whose name ends with HTML (we'll call it index.html). Now, inside, you can just write normal HTML: |
7 | 8 |
|
8 | | -<em>Hello</em>, world! |
9 | | -Or you can use web.py's templating language to add code to your HTML: |
| 9 | +1. The templating system has to *look* decent. No ``<%#foo#%>`` crud. |
| 10 | +2. Reuse Python terms and semantics as much as possible. |
| 11 | +3. Expressive enough to do real computation. |
| 12 | +4. Usable for any text language, not just HTML and XML. |
| 13 | + |
| 14 | +And requirements for the implementation as well: |
| 15 | + |
| 16 | +4. Sandboxable so that you can let untrusted users write templates. |
| 17 | +5. Simple and fast implementation. |
| 18 | + |
| 19 | +So here it is. |
| 20 | + |
| 21 | +Variable substitution |
| 22 | +--------------------- |
| 23 | + |
| 24 | +:: |
| 25 | + |
| 26 | + Look, a $string. |
| 27 | + Hark, an ${arbitrary + expression}. |
| 28 | + Gawk, a $dictionary[key].function('argument'). |
| 29 | + Cool, a $(limit)ing. |
| 30 | + |
| 31 | + Stop, \$money isn't evaluated. |
| 32 | + |
| 33 | +We use basically the same semantics as (rejected) `PEP |
| 34 | +215 <http://www.python.org/peps/pep-0215.html>`__. Variables can go |
| 35 | +anywhere in a document. |
| 36 | + |
| 37 | +Newline suppression |
| 38 | +------------------- |
| 39 | + |
| 40 | +:: |
| 41 | + |
| 42 | + If you put a backslash \ |
| 43 | + at the end of a line \ |
| 44 | + (like these) \ |
| 45 | + then there will be no newline. |
| 46 | + |
| 47 | +renders as all one line. |
| 48 | + |
| 49 | +Expressions |
| 50 | +----------- |
10 | 51 |
|
11 | 52 | :: |
12 | 53 |
|
13 | | - $def with (name) |
| 54 | + Here are some expressions: |
| 55 | + |
| 56 | + $for var in iterator: I like $var! |
14 | 57 |
|
15 | | - $if name: |
16 | | - I just wanted to say <em>hello</em> to $name. |
| 58 | + $if times > max: |
| 59 | + Stop! In the name of love. |
17 | 60 | $else: |
18 | | - <em>Hello</em>, world! |
| 61 | + Keep on, you can do it. |
| 62 | + |
| 63 | + That's all, folks. |
| 64 | + |
| 65 | +All your old Python friends are here: ``if``, ``while``, ``for``, |
| 66 | +``else``, ``break``, ``continue``, and ``pass`` also act as you'd |
| 67 | +expect. (Obviously, you can't have variables named any of these.) The |
| 68 | +Python code starts at the ``$`` and ends at the ``:``. The ``$`` has to |
| 69 | +be at the beginning of the line, but that's not such a burden because of |
| 70 | +newline suppression (above). |
| 71 | + |
| 72 | +Also, we're very careful about spacing -- all the lines will render with |
| 73 | +no spaces at the beginning. (Open question: what if you want spaces at |
| 74 | +the beginning?) Also, a trailing space might break your code. |
| 75 | + |
| 76 | +There are a couple changes from Python: ``for`` and ``while`` now take |
| 77 | +an ``else`` clause that gets called if the loop is never evaluated. |
| 78 | + |
| 79 | +(Possible feature to add: Django-style for loop variables.) |
| 80 | + |
| 81 | +Comments |
| 82 | +-------- |
| 83 | + |
| 84 | +:: |
| 85 | + |
| 86 | + $# Here's where we hoodwink the folks at home: |
| 87 | + |
| 88 | + Please enter in your deets: |
| 89 | + |
| 90 | + CC: [ ] $#this is the important one |
| 91 | + SSN: $#Social Security Number#$ [ ] |
| 92 | + |
| 93 | +Comments start with ``$#`` and go to ``#$`` or the end of the line, |
| 94 | +whichever is first. |
| 95 | + |
| 96 | +Code |
| 97 | +---- |
| 98 | + |
| 99 | +**NOTE: This feature has not been implemented in the current web.py |
| 100 | +implementation of templetor.** |
| 101 | + |
| 102 | +:: |
| 103 | + |
| 104 | + Sometimes you just need to break out the Python. |
| 105 | + |
| 106 | + $ mapping = { |
| 107 | + $ 'cool': ['nice', 'sweet', 'hot'], |
| 108 | + $ 'suck': ['bad', 'evil', 'awful'] |
| 109 | + $ } |
| 110 | + |
| 111 | + Isn't that $mapping[thought]? |
| 112 | + That's$ del mapping $ fine with me. |
| 113 | + |
| 114 | + $ complicatedfunc() |
| 115 | + |
| 116 | + $ for x in bugs: |
| 117 | + $ if bug.level == 'severe': |
| 118 | + Ooh, this one is bad. |
| 119 | + $ continue |
| 120 | + And there's $x... |
| 121 | + |
| 122 | +**Body of loops have to be indented with exactly 4 spaces.** |
| 123 | + |
| 124 | +Code begins with a ``$`` and a space and goes until the next ``$`` or |
| 125 | +the end of the line, whichever comes first. Nothing ever gets output if |
| 126 | +the first character after the ``$`` is a space (so ``complicatedfunc`` |
| 127 | +above doesn't write anything to the screen like it might without the |
| 128 | +space). |
| 129 | + |
| 130 | +Python integration |
| 131 | +------------------ |
| 132 | + |
| 133 | +A template begins with a line like this: |
| 134 | + |
| 135 | +:: |
| 136 | + |
| 137 | + $def with (name, title, company='BigCo') |
19 | 138 |
|
20 | | -As you can see, the templates look a lot like Python files except for the def with statement at the top (saying what the template gets called with) and the $s placed in front of any code. Currently, the templetor requires the $def statement to be the first line of the file. |
| 139 | +which declares that the template takes those arguments. (The ``with`` |
| 140 | +keyword is special, like ``def`` or ``if``.) |
21 | 141 |
|
| 142 | +**Don't forget to put spaces in the definition** |
22 | 143 |
|
23 | | -Now go back to code.py. Under the first line, add: |
| 144 | +The following *will not work*: |
24 | 145 |
|
25 | 146 | :: |
26 | 147 |
|
27 | | - render = web.template.render('templates/') |
| 148 | + $def with (name,title,company='BigCo') |
28 | 149 |
|
29 | | -This tells web.py to look for templates in your templates directory. Then change index.GET to: |
| 150 | +Inside Python, the template looks like a function that takes these |
| 151 | +arguments. It returns a storage object with the special property that |
| 152 | +evaluating it as a string returns the value of the body of the template. |
| 153 | +The elements in the storage object are the results of the ``def``\ s and |
| 154 | +the ``set``\ s. |
| 155 | + |
| 156 | +Perhaps an example will make this clearer. Here's a template, "entry": |
| 157 | + |
| 158 | +:: |
| 159 | + |
| 160 | + $def with (post) |
| 161 | + |
| 162 | + $var title: $post.title |
| 163 | + |
| 164 | + <p>$markdown(post.body)</p> |
| 165 | + |
| 166 | + <p class="byline">by $post.author</p> |
| 167 | + |
| 168 | +Here's another; "base": |
| 169 | + |
| 170 | +:: |
| 171 | + |
| 172 | + $def with (self) |
| 173 | + <html><head> |
| 174 | + <title>$self.title</title> |
| 175 | + </head><body> |
| 176 | + <h1>$self.title</h1> |
| 177 | + |
| 178 | + $:self |
| 179 | + </body></html> |
| 180 | + |
| 181 | +Now let's say we compile both from within Python, the first as |
| 182 | +``entry``, the second as ``base``. Here's how we might use them: |
| 183 | + |
| 184 | +:: |
| 185 | + |
| 186 | + print base( entry( post ) ) |
| 187 | + |
| 188 | +``entry`` takes the argument post and returns an object whose string |
| 189 | +value is a bit of HTML showing the post with its title in the property |
| 190 | +``title``. ``base`` takes this object and places the title in the |
| 191 | +appropriate place and displays the page itself in the body of the page. |
| 192 | +The Python code prints out the result. |
| 193 | + |
| 194 | +*Where did ``markdown`` come from? It wasn't passed as an argument.* You |
| 195 | +can pass a list of functions and variables to the template compiler to |
| 196 | +be made globally available to templates. *Why $:self?* See below |
| 197 | + |
| 198 | +Here's an example: |
| 199 | + |
| 200 | +:: |
| 201 | + |
| 202 | + import template |
| 203 | + render = template.render('templates/') |
| 204 | + template.Template.globals['len'] = len |
| 205 | + |
| 206 | + print render.base(render.message('Hello, world!')) |
| 207 | + |
| 208 | +The first line imports templetor. The second says that our templates are |
| 209 | +in the directory ``templates/``. The third give all our templates access |
| 210 | +to the ``len`` function. The fourth grabs the template ``message.html``, |
| 211 | +passes it the argument ``'Hello, world!'``, passes the result of |
| 212 | +rendering it to `mcitp <http://www.buyitcert.com/mcitp.html>`__ the |
| 213 | +template ``base.html`` and prints the result. (If your templates don't |
| 214 | +end in ``.html`` or ``.xml``, templetor will still find them, but it |
| 215 | +won't do its automatic HTML-encoding.) |
| 216 | + |
| 217 | +Turning Off Filter |
| 218 | +------------------ |
| 219 | + |
| 220 | +By default ``template.render`` will use ``web.websafe`` filter to do |
| 221 | +HTML-encoding. To turn it off, put a : after the $ as in: |
| 222 | + |
| 223 | +:: |
| 224 | + |
| 225 | + $:form.render() |
| 226 | + |
| 227 | +Output from form.render() will be displayed as is. |
| 228 | + |
| 229 | +:: |
| 230 | + |
| 231 | + $:fooBar $# fooBar = <span>lorem ipsum</span> |
| 232 | + |
| 233 | +Output from variable in template will be displayed as is. |
| 234 | + |
| 235 | +Including / nesting templates |
| 236 | +----------------------------- |
| 237 | + |
| 238 | +If you want to nest one template within another, you nest the |
| 239 | +``render()`` calls, and then include the variable (unfiltered) in the |
| 240 | +page. In your handler: |
30 | 241 |
|
31 | 242 | :: |
32 | 243 |
|
33 | | - name = 'Bob' |
34 | | - return render.index(name) |
| 244 | + print render.foo(render.bar()) |
35 | 245 |
|
36 | | -('index' is the name of the template and 'name' is the argument passed to it) |
| 246 | +or (to make things a little more clear): |
37 | 247 |
|
38 | | -Visit your site and it should say hello to Bob. |
| 248 | +:: |
| 249 | + |
| 250 | + barhtml = render.bar() |
| 251 | + print render.foo(barhtml) |
39 | 252 |
|
40 | | -But let's say we want to let people enter their own name in. Replace the two lines we added above with: |
| 253 | +Then in the template ``foo.html``: |
41 | 254 |
|
42 | 255 | :: |
43 | 256 |
|
44 | | - i = web.input(name=None) |
45 | | - return render.index(i.name) |
| 257 | + $def with (bar) |
| 258 | + html goes here |
| 259 | + $:bar |
| 260 | + more html |
46 | 261 |
|
47 | | -Visit / and it should say hello to the world. Visit /?name=Joe and it should say hello to Joe. |
| 262 | +This replaces the ``$:bar`` with the output of the ``render.bar()`` call |
| 263 | +(which is why it must be ``$:``/unfiltered, so |
| 264 | +`ccnp <http://www.buyitcert.com/ccnp.html>`__ that you get un-encoded |
| 265 | +HTML (unless you want something else of course)). You can pass variables |
| 266 | +in, in the same way: |
48 | 267 |
|
49 | | -Of course, having that ? in the URL is kind of ugly. Instead, change your URL line at the top to: |
| 268 | +:: |
50 | 269 |
|
51 | | -'/(.*)', 'index' |
52 | | -and change the definition of index.GET to: |
| 270 | + print render.foo(render.bar(baz), qux) |
| 271 | + |
| 272 | +In the template bar (``bar.html``): |
53 | 273 |
|
54 | 274 | :: |
55 | 275 |
|
56 | | - def GET(self, name): |
57 | | - return render.index(name) |
| 276 | + $def with (baz) |
| 277 | + bar stuff goes here + baz |
| 278 | + |
| 279 | +In template foo (``foo.html``): |
58 | 280 |
|
59 | | -and delete the line setting name. Now visit /Joe and it should say hello to Joe. |
| 281 | +:: |
| 282 | + |
| 283 | + $def with (bar, qux) |
| 284 | + html goes here |
| 285 | + $:bar |
| 286 | + Value of qux is $qux |
60 | 287 |
|
61 | 288 | Escaping |
62 | | -```````` |
| 289 | +-------- |
| 290 | + |
63 | 291 | web.py automatically escapes any variables used in templates, so that if for some reason name is set to a value containing some HTML, it will get properly escaped and appear as plain text. If you want to turn this off, write $:name instead of $name. |
64 | 292 |
|
0 commit comments