Nevow render用法(转)

<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"> </script> <script type="text/javascript"> _uacct = "UA-99018-3"; urchinTracker(); </script>
1Nevow Object Publishing
2=======================
3
4In Nevow Object Traversal, we learned about the
5nevow.inevow.IResource.renderHTTP method, which is the most basic way to send
6HTML to a browser when using Nevow. However, it is not very convenient (or
7clean) to generate HTML tags by concatenating strings in Python code. In the
8Nevow Deployment documentation, we saw that it was possible to render a Hello
9World page using a nevow.rend.Page subclass and providing a "docFactory"::
10
11  >>> from nevow import rend, loaders
12  >>> class HelloWorld(rend.Page):
13  ...     docFactory = loaders.stan("Hello, world!")
14  ...
15  >>> HelloWorld().renderSynchronously()
16  'Hello, world!'
17
18This example does nothing interesting, but the concept of a loader is important
19in Nevow. The rend.Page.renderHTTP implementation always starts rendering HTML
20by loading a template from the docFactory.
21
22* `The stan DOM`_
23* `Tag instances`_
24* `Functions in the DOM`_
25* `Accessing query parameters and form post data`_
26* `Generators in the DOM`_
27* `Methods in the DOM`_
28* `Data specials`_
29* `Render specials`_
30* `Pattern specials`_
31* `Slot specials`_
32* `Data directives`_
33* `Render directives`_
34* `Flatteners`_
35
36The stan DOM
37------------
38
39Nevow uses a DOM-based approach to rendering HTML. A tree of objects is first
40constructed in memory by the template loader. This tree is then processed one
41node at a time, applying functions which transform from various Python types to
42HTML strings.
43
44Nevow uses a nonstandard DOM named "stan". Unlike the W3C DOM, stan is made up
45of simple python lists, strings, and instances of the nevow.stan.Tag class.
46During the rendering process, "Flattener" functions convert from rich types to
47HTML strings. For example, we can load a template made up of some nested lists
48and Python types, render it, and see what happens::
49
50  >>> class PythonTypes(rend.Page):
51  ...     docFactory = loaders.stan(["Hello", 1, 1.5, True, ["Goodbye", 3]])
52  ...
53  >>> PythonTypes().renderSynchronously()
54  'Hello11.5TrueGoodbye3'
55
56Tag instances
57-------------
58
59So far, we have only rendered simple strings as output. However, the main
60purpose of Nevow is HTML generation. In the stan DOM, HTML tags are represented
61by instances of the nevow.stan.Tag class. Tag is a very simple class, whose
62instances have an "attributes" dictionary and a "children" list. The Tag
63flattener knows how to recursively flatten attributes and children of the tag.
64To show you how Tags really work before you layer Nevow's convenience syntax on
65top, try this horrible example::
66
67  >>> from nevow import stan
68  >>> h = stan.Tag('html')
69  >>> d = stan.Tag('div')
70  >>> d.attributes['style'] = 'border: 1px solid black'
71  >>> h.children.append(d)
72  >>> class Tags(rend.Page):
73  ...     docFactory = loaders.stan(h)
74  ...
75  >>> Tags().renderSynchronously()
76  '<html><div style="border: 1px solid black"></div></html>'
77
78So, we see how it is possible to programatically generate HTML by constructing
79and nesting stan Tag instances. However, it is far more convenient to use the
80overloaded operators Tag provides to manipulate them. Tag implements a __call__
81method which takes any keyword arguments and values and updates the attributes
82dictionary; it also implements a __getitem__ method which takes whatever is
83between the square brackets and appends them to the children list. A simple
84example should clarify things::
85
86  >>> class Tags2(rend.Page):
87  ...     docFactory = loaders.stan(stan.Tag('html')[stan.Tag('div')(style="border: 1px solid black")])
88  ...
89  >>> Tags2().renderSynchronously()
90  '<html><div style="border: 1px solid black"></div></html>'
91
92This isn't very easy to read, but luckily we can simplify the example even
93further by using the nevow.tags module, which is full of "Tag prototypes" for
94every tag type described by the XHTML 1.0 specification::
95
96  >>> class Tags3(rend.Page):
97  ...     docFactory = loaders.stan(tags.html[tags.div(style="border: 1px solid black")])
98  ...
99  >>> Tags3().renderSynchronously()
100  '<html><div style="border: 1px solid black"></div></html>'
101
102Using stan syntax is not the only way to construct template DOM for use by the
103Nevow rendering process. Nevow also includes loaders.xmlfile which implements a
104simple tag attribute language similar to the Zope Page Templates (ZPT) Tag
105Attribute Language (TAL). However, experience with the stan DOM should give you
106insight into how the Nevow rendering process really works. Rendering a template
107into HTML in Nevow is really nothing more than iterating a tree of objects and
108recursively applying "Flattener" functions to objects in this tree, until all
109HTML has been generated.
110
111Functions in the DOM
112--------------------
113
114So far, all of our examples have generated static HTML pages, which is not
115terribly interesting when discussing dynamic web applications. Nevow takes a
116very simple approach to dynamic HTML generation. If you put a Python function
117reference in the DOM, Nevow will call it when the page is rendered. The return
118value of the function replaces the function itself in the DOM, and the results
119are flattened further. This makes it easy to express looping and branching
120structures in Nevow, because normal Python looping and branching constructs are
121used to do the job::
122
123  >>> def repeat(ctx, data):
124  ...     return [tags.div(style="color: %s" % (color, ))
125  ...         for color in ['red', 'blue', 'green']]
126  ...
127  >>> class Repeat(rend.Page):
128  ...     docFactory = loaders.stan(tags.html[repeat])
129  ...
130  >>> Repeat().renderSynchronously()
131  '<html><div style="color: red"></div><div style="color: blue"></div><div style="color: green"></div></html>'
132
133However, in the example above, the repeat function isn't even necessary, because
134we could have inlined the list comprehension right where we placed the function
135reference in the DOM. Things only really become interesting when we begin
136writing parameterized render functions which cause templates to render
137differently depending on the input to the web application.
138
139The required signature of functions which we can place in the DOM is (ctx,
140data). The "context" object is essentially opaque for now, and we will learn how
141to extract useful information out of it later. The "data" object is anything we
142want it to be, and can change during the rendering of the page. By default, the
143data object is whatever we pass as the first argument to the Page constructor,
144**or** the Page instance itself if nothing is passed. Armed with this knowledge,
145we can create a Page which renders differently depending on the data we pass to
146the Page constructor::
147
148  class Root(rend.Page):
149      docFactory = loaders.stan(tags.html[
150      tags.h1["Welcome."],
151      tags.a(href="foo")["Foo"],
152      tags.a(href="bar")["Bar"],
153      tags.a(href="baz")["Baz"]])
154
155      def childFactory(self, ctx, name):
156          return Leaf(name)
157 
158 
159  def greet(ctx, name):
160      return "Hello. You are visiting the ", name, " page."
161 
162  class Leaf(rend.Page):
163      docFactory = loaders.stan(tags.html[greet])
164
165Armed with this knowledge and the information in the Object Traversal
166documentation, we now have enough information to create dynamic websites with
167arbitrary URL hierarchies whose pages render dynamically depending on which URL
168was used to access them.
169
170Accessing query parameters and form post data
171---------------------------------------------
172
173Before we move on to more advanced rendering techniques, let us first examine
174how one could further customize the rendering of a Page based on the URL query
175parameters and form post information provided to us by a browser. Recall that
176URL parameters are expressed in the form::
177
178  http://example.com/foo/bar?baz=1&quux=2
179
180And form post data can be generated by providing a form to a browser::
181
182  <form action="" method="POST">
183    <input type="text" name="baz" />
184    <input type="text" name="quux" />
185    <input type="submit" />
186  </form>
187
188Accessing this information is such a common procedure that Nevow provides a
189convenience method on the context to do it. Let's examine a simple page whose
190output can be influenced by the query parameters in the URL used to access it::
191
192  def showChoice(ctx, data):
193      choice = ctx.arg('choice')
194      if choice is None:
195          return ''
196      return "You chose ", choice, "."
197
198  class Custom(rend.Page):
199      docFactory = loaders.stan(tags.html[
200      tags.a(href="?choice=baz")["Baz"],
201      tags.a(href="?choice=quux")["Quux"],
202      tags.p[showChoice]])
203
204The procedure is exactly the same for simple form post information::
205
206  def greet(ctx, data):
207      name = ctx.arg('name')
208      if name is None:
209          return ''
210      return "Greetings, ", name, "!"
211
212  class Form(rend.Page):
213      docFactory = loaders.stan(tags.html[
214      tags.form(action="", method="POST")[
215          tags.input(name="name"),
216          tags.input(type="submit")],
217      greet])
218
219Note that ctx.arg returns only the first argument with the given name. For
220complex cases where multiple arguments and lists of argument values are
221required, you can access the request argument dictionary directly using the
222syntax::
223
224  def arguments(ctx, data):
225      args = inevow.IRequest(ctx).args
226      return "Request arguments are: ", str(args)
227
228Generators in the DOM
229---------------------
230
231One common operation when building dynamic pages is iterating a list of data and
232emitting some HTML for each item. Python generators are well suited for
233expressing this sort of logic, and code which is written as a python generator
234can perform tests (if) and loops of various kinds (while, for) and emit a row of
235html whenever it has enough data to do so. Nevow can handle generators in the
236DOM just as gracefully as it can handle anything else::
237
238  >>> from nevow import rend, loaders, tags
239  >>> def generate(ctx, items):
240  ...     for item in items:
241  ...         yield tags.div[ item ]
242  ...
243  >>> class List(rend.Page):
244  ...     docFactory = loaders.stan(tags.html[ generate ])
245  ...
246  >>> List(['one', 'two', 'three']).renderSynchronously()
247  '<html><div>one</div><div>two</div><div>three</div></html>'
248
249As you can see, generating HTML inside of functions or generators can be very
250convenient, and can lead to very rapid application development. However, it is
251also what I would call a "template abstraction violation", and we will learn how
252we can keep knowledge of HTML out of our python code when we learn about
253patterns and slots.
254
255Methods in the DOM
256------------------
257
258Up until now, we have been placing our template manipulation logic inside of
259simple Python functions and generators. However, it is often appropriate to use
260a method instead of a function. Nevow makes it just as easy to use a method to
261render HTML::
262
263  class MethodRender(rend.Page):
264      def __init__(self, foo):
265          self.foo = foo
266
267      def render_foo(self, ctx, data):
268          return self.foo
269
270      docFactory = loaders.stan(tags.html[ render_foo ])
271
272Using render methods makes it possible to parameterize your Page class with more
273parameters. With render methods, you can also use the Page instance as a state
274machine to keep track of the state of the render. While Nevow is designed to
275allow you to render the same Page instance repeatedly, it can also be convenient
276to know that a Page instance will only be used one time, and that the Page
277instance can be used as a scratch pad to manage information about the render.
278
279Data specials
280-------------
281
282Previously we saw how passing a parameter to the default Page constructor makes
283it available as the "data" parameter to all of our render methods. This "data"
284parameter can change as the page render proceeds, and is a useful way to ensure
285that render functions are isolated and only act upon the data which is available
286to them. Render functions which do not pull information from sources other than
287the "data" parameter are more easily reusable and can be composed into larger
288parts more easily.
289
290Deciding which data gets passed as the data parameter is as simple as changing
291the "Data special" for a Tag. See the Glossary under "Tag Specials" for more
292information about specials. Assigning to the data special is as simple as
293assigning to a tag attribute::
294
295  >>> def hello(ctx, name):
296  ...     return "Hello, ", name
297  ...
298  >>> class DataSpecial(rend.Page):
299  ...     docFactory = loaders.stan(tags.html[
300  ...     tags.div(data="foo")[ hello ],
301  ...     tags.div(data="bar")[ hello ]])
302  ...
303  >>> DataSpecial().renderSynchronously()
304  '<html><div>Hello, foo</div><div>Hello, bar</div></html>'
305
306Data specials may be assigned any python value. Data specials are only in scope
307during the rendering of the tag they are assigned to, so if the "hello" renderer
308were placed in the DOM inside the html node directly, "Hello, None" would be
309output.
310
311Before data is passed to a render function, Nevow first checks to see if there
312is an IGettable adapter for it. If there is, it calls IGettable.get(), and
313passes the result of this as the data parameter instead. Nevow includes an
314IGettable adapter for python functions, which means you can set a Tag data
315special to a function reference and Nevow will call it to obtain the data when
316the Tag is rendered. The signature for data methods is similar to that of render
317methods, (ctx, data). For example::
318
319  def getName(ctx, data):
320      return ctx.arg('name')
321
322  def greet(ctx, name):
323      return "Greetings, ", name
324
325  class GreetName(rend.Page):
326      docFactory = loaders.stan(tags.html[
327      tags.form(action="")[
328          tags.input(name="name"),
329          tags.input(type="submit")],
330      tags.div(data=getName)[ greet ]])
331
332Data specials exist mainly to allow you to construct and enforce a
333Model-View-Controller style separation of the Model code from the View. Here we
334see that the greet function is capable of rendering a greeting view for a name
335model, and that the implementation of getName may change without the view code
336changing.
337
338Render specials
339---------------
340
341Previously, we have seen how render functions can be placed directly in the DOM,
342and the return value replaces the render function in the DOM. However, these
343free functions and methods are devoid of any contextual information about the
344template they are living in. The render special is a way to associate a render
345function or method with a particular Tag instance, which the render function can
346then examine to decide how to render::
347
348  >>> def alignment(ctx, data):
349  ...     align = ctx.tag.attributes.get('align')
350  ...     if align == 'right':   
351  ...         return ctx.tag["Aligned right"]
352  ...     elif align == 'center':
353  ...         return ctx.tag["Aligned center"]
354  ...     else:
355  ...         return ctx.tag["Aligned left"]
356  ...
357  >>> class AlignmentPage(rend.Page):
358  ...     docFactory = loaders.stan(tags.html[
359  ...     tags.p(render=alignment),     
360  ...     tags.p(render=alignment, align="center"),
361  ...     tags.p(render=alignment, align="right")])
362  ...
363  >>> AlignmentPage().renderSynchronously()
364  '<html><p>Aligned left</p><p align="center">Aligned center</p><p align="right">Aligned right</p></html>'
365
366Note how the alignment renderer has access to the template node as "ctx.tag". It
367can examine and change this node, and the return value of the render function
368replaces the original node in the DOM. Note that here we are returning the
369template node after changing it. We will see later how we can instead mutate the
370context and use slots so that the knowledge the renderer requires about the
371structure of the template is reduced even more.
372
373Pattern specials
374----------------
375
376When writing render methods, it is easy to inline the construction of Tag
377instances to generate HTML programatically. However, this creates a template
378abstraction violation, where part of the HTML which will show up in the final
379page output is hidden away inside of render methods instead of inside the
380template. Pattern specials are designed to avoid this problem. A node which has
381been tagged with a pattern special can then be located and copied by a render
382method. The render method does not need to know anything about the structure or
383location of the pattern, only it's name.
384
385We can rewrite our previous generator example so that the generator does not
386have to know what type of tag the template designer would like repeated for each
387item in the list::
388
389  >>> from nevow import rend, loaders, tags, inevow
390  >>> def generate(ctx, items):
391  ...     pat = inevow.IQ(ctx).patternGenerator('item')
392  ...     for item in items:
393  ...         ctx.tag[ pat(data=item) ]
394  ...     return ctx.tag
395  ...
396  >>> def string(ctx, item):
397  ...     return ctx.tag[ str(item) ]
398  ...
399  >>> class List(rend.Page):
400  ...     docFactory = loaders.stan(tags.html[
401  ...     tags.ul(render=generate)[
402  ...         tags.li(pattern="item", render=string)]])
403  ...
404  >>> List([1, 2, 3]).renderSynchronously()
405  '<html><ol><li>1</li><li>2</li><li>3</li></ol></html>'
406
407Note that we have to mutate the tag in place and repeatedly copy the item
408pattern, applying the item as the data special to the resulting Tag. It turns
409out that this is such a common operation that nevow comes out of the box with
410these two render functions::
411
412  >>> class List(rend.Page):
413  ...     docFactory = loaders.stan(tags.html[
414  ...     tags.ul(render=rend.sequence)[
415  ...         tags.li(pattern="item", render=rend.data)]])
416  ...
417  >>> List([1, 2, 3]).renderSynchronously()
418  '<html><ul><li>1</li><li>2</li><li>3</li></ul></html>'
419
420Slot specials
421-------------
422
423The problem with render methods is that they are only capable of making changes
424to their direct children. Because of the architecture of Nevow, they should not
425attempt to change grandchildren or parent nodes. It is possible to write one
426render method for every node you wish to change, but there is a better way. A
427node with a slot special can be "filled" with content by any renderer above the
428slot. Creating a slot special is such a frequent task that there is a prototype
429in nevow.tags which is usually used.
430
431Let us examine a renderer which fills a template with information about a
432person:
433         
434  >>> from nevow import loaders, rend, tags
435  ...
436  >>> person = ('Donovan', 'Preston', 'Male', 'California')
437  ...
438  >>> def render_person(ctx, person):
439  ...     firstName, lastName, sex, location = person
440  ...     ctx.fillSlots('firstName', firstName)
441  ...     ctx.fillSlots('lastName', lastName)
442  ...     ctx.fillSlots('sex', sex)
443  ...     ctx.fillSlots('location', location)
444  ...     return ctx.tag
445  ...
446  >>> class PersonPage(rend.Page):
447  ...     docFactory = loaders.stan(tags.html(render=render_person)[
448  ...     tags.table[
449  ...         tags.tr[
450  ...             tags.td[tags.slot('firstName')],
451  ...             tags.td[tags.slot('lastName')],
452  ...             tags.td[tags.slot('sex')],
453  ...             tags.td[tags.slot('location')]]]])
454  ...
455  >>> PersonPage(person).renderSynchronously()
456  '<html><table><tr><td>Donovan</td><td>Preston</td><td>Male</td><td>California</td></tr></table></html>'
457
458Using patterns in combination with slots can lead to very powerful template
459abstraction. Nevow also includes another standard renderer called "mapping"
460which takes any data which responds to the "items()" message and inserts the
461items into appropriate slots::
462
463  >>> class DictPage(rend.Page):
464  ...     docFactory = loaders.stan(tags.html(render=rend.mapping)[
465  ...         tags.span[ tags.slot('foo') ], tags.span[ tags.slot('bar') ]])
466  ...
467  >>> DictPage(dict(foo=1, bar=2)).renderSynchronously()
468  '<html><span>1</span><span>2</span></html>'
469
470Data directives
471---------------
472
473So far, we have always placed data functions directly in the Data special
474attribute of a Tag. Sometimes, it is preferable to look up a data method from
475the Page class as the Page has being rendered. For example, a base class may
476define a template and a subclass may provide the implementation of the data
477method. We can accomplish this effect by using a data directive as a Tag's data
478special::
479
480  class Base(rend.Page):
481      docFactory = loaders.stan(tags.html[
482          tags.div(data=tags.directive('name'), render=rend.data)])
483
484  class Subclass(Base):
485      def data_name(self, ctx, data):
486          return "Your name"
487
488The data directive is resolved by searching for the IContainer implementation in
489the context. rend.Page implements IContainer.get by performing an attribute
490lookup on the Page with the prefix 'data_*'. You can provide your own IContainer
491implementation if you wish, and also you should know that IContainer
492implementations for list and dict are included in the nevow.accessors module.
493
494A common gotcha is that the closest IContainer is used to resolve data
495directives. This means that if a list is being used as the data during the
496rendering process, data directives below this will be resolved against the
497IContainer implementation in nevow.accessors.ListAccessor. If you are expecting
498a data directive to invoke a Page's data_* method but instead get a KeyError,
499this is why.
500
501Render directives
502-----------------
503
504Render directives are almost exactly the same, except they are resolved using
505the closest IRendererFactory implementation in the context. Render directives
506can be used to allow subclasses to override certain render methods, and also can
507be used to allow Fragments to locate their own prefixed render methods.
508
509Flatteners
510----------
511
512TODO This section isn't done yet.
513
514Nevow's flatteners use a type/function registry to determine how to render
515objects which Nevow encounters in the DOM during the rendering process.
516"Explicit is better than implicit", so in most cases, explicitly applying render
517methods to data will be better than registering a flattener, but in some cases
518it can be useful::
519
520  class Person(object):
521      def __init__(self, firstName, lastName):
522          self.firstName = firstName
523          self.lastName = lastName
524
525  def flattenPerson(person, ctx):
526      return flat.partialflatten(
527                ctx,
528                (person.firstName, " ", person.lastName))
529
530  from nevow import flat
531  flat.registerFlattener(flattenPerson, Person)
532
533  def insertData(ctx, data):
534      return data
535
536  class PersonPage(rend.Page):
537      docFactory = loaders.stan(tags.html[ insertData ])
538

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值