1# Copyright (c) 2004 Divmod. 2# See LICENSE for details. 3 4"""Page, Fragment and other standard renderers. 5 6This module contains classes and function responsible for rendering 7dynamic content and a few useful mixin classes for inheriting common 8functionality. 9 10Mostly, you'll use the renderers: 11 12 - B{Page} - Nevow's main resource type for rendering web pages and 13 locating child resource. 14 - B{Fragment} - useful for rendering more complex parts of a document 15 that require a set of data_* and render_* methods. 16 - B{sequence} - render each item in a sequence. 17 - B{mapping} - publish a dictionary by filling slots 18""" 19 20from time import time as now 21from cStringIO import StringIO 22import random 23import warnings 24 25from zope.interface import implements, providedBy 26 27import twisted.python.components as tpc 28from twisted.python.reflect import qual, accumulateClassList 29 30from nevow.context import WovenContext, NodeNotFound, PageContext 31from nevow import inevow, tags, flat, util, url 32from nevow.util import log 33 34import formless 35from formless import iformless, annotate 36 37 38def _getPreprocessors(inst): 39 """ 40 Accumulate elements from the sequences bound at the C{preprocessors} 41 attribute on all classes in the inheritance hierarchy of the class of 42 C{inst}. A C{preprocessors} attribute on the given instance overrides 43 all preprocessors from the class inheritance hierarchy. 44 """ 45 if 'preprocessors' in vars(inst): 46 return inst.preprocessors 47 preprocessors = [] 48 accumulateClassList( 49 inst.__class__, 50 'preprocessors', 51 preprocessors) 52 return preprocessors 53 54 55 56class RenderFactory(object): 57 implements(inevow.IRendererFactory) 58 59 def renderer(self, context, name): 60 """Return a renderer with the given name. 61 """ 62 63 # The named renderer can be parameterised, i.e. 'renderIt one,two,three' 64 args = [] 65 if name.find(' ') != -1: 66 name, args = name.split(None, 1) 67 args = [arg.strip() for arg in args.split(',')] 68 69 callable = getattr(self, 'render_%s' % name, None) 70 if callable is None: 71 warnings.warn( 72 "Renderer %r missing on %s will result in an exception." % ( 73 name, qual(type(self))), 74 category=DeprecationWarning, 75 stacklevel=1) 76 callable = lambda *a, **kw: context.tag[ 77 "The renderer named '%s' was not found in %r." % (name, self)] 78 79 if args: 80 return callable(*args) 81 82 return callable 83 84 render_sequence = lambda self, context, data: sequence(context, data) 85 render_mapping = lambda self, context, data: mapping(context, data) 86 render_string = lambda self, context, data: string(context, data) 87 render_xml = lambda self, context, data: context.tag.clear()[tags.xml(data)] 88 render_data = lambda self, context, data_: data(context, data_) 89 90 91class MacroFactory(object): 92 implements(inevow.IMacroFactory) 93 94 def macro(self, ctx, name): 95 """Return a macro with the given name. 96 """ 97 # The named macro can be parameterized, i.e. 'macroFoo foo,bar,baz' 98 args = [] 99 if name.find(' ') != -1: 100 name, args = name.split(None, 1) 101 args = [arg.strip() for arg in args.split(',')] 102 103 callable = getattr(self, 'macro_%s' % name, None) 104 if callable is None: 105 callable = lambda ctx, *args: ctx.tag[ 106 "The macro named '%s' was not found in %r." % (name, self)] 107 108 if args: 109 ## Macros are expanded in TagSerializer by calling them with a single arg, the context 110 return lambda ctx: callable(ctx, *args) 111 112 return callable 113 114 115class DataNotFoundError(Exception): 116 """Raised when a data directive could not be resolved on the page or its 117 original attribute by the DataFactory. 118 """ 119 120 121class DataFactory(object): 122 implements(inevow.IContainer) 123 124 def child(self, context, n): 125 args = [] 126 if n.find(' ') != -1: 127 name, args = n.split(None, 1) 128 args = [arg.strip() for arg in args.split(',')] 129 else: 130 name = n 131 132 callable = getattr(self, 'data_%s' % name, None) 133 ## If this page doesn't have an appropriate data_* method... 134 if callable is None: 135 ## See if our self.original has an IContainer... 136 container = inevow.IContainer(self.original, None) 137 if container is None: 138 raise DataNotFoundError("The data named %r was not found in %r." % (name, self)) 139 else: 140 ## And delegate to it if so. 141 return container.child(context, n) 142 143 if args: 144 return callable(*args) 145 146 return callable 147 148 149class FreeformChildMixin: 150 """Mixin that handles locateChild for freeform segments.""" 151 def locateChild(self, ctx, segments): 152 request = inevow.IRequest(ctx) 153 ## The method or property name we are going to validate against/affect 154 bindingName = None 155 156 name = segments[0] 157 if name.startswith('freeform_post!'): 158 configurableName, bindingName = name.split('!')[1:3] 159 elif name.startswith('freeform-action-post!'): 160 configurableName, request.args['freeform-actee'] = name.split('!')[1:3] 161 bindingName = request.args['freeform-action'][0] 162 if bindingName: 163 ctx.remember(self, inevow.IResource) 164 ctx.remember(request, inevow.IRequest) 165 cf = iformless.IConfigurableFactory(self) 166 def checkC(c): 167 if c is not None: 168 return self.webFormPost(request, self, c, ctx, bindingName, request.args) 169 return util.maybeDeferred(cf.locateConfigurable, ctx, configurableName).addCallback(checkC) 170 return NotFound 171 172 def child_freeform_hand(self, ctx): 173 carryoverHand = inevow.IHand(ctx, None) 174 if carryoverHand is not None: 175 inevow.ISession(ctx).setComponent(inevow.IHand, carryoverHand) 176 return carryoverHand 177 return inevow.IHand(inevow.ISession(ctx), None) 178 179 180class ConfigurableMixin(object): 181 """ 182 A sane L{IConfigurable} implementation for L{Fragment} and L{Page}. 183 184 Methods starting with C{bind_} automatically expose corresponding method 185 names. C{bind_*} should return an L{IBinding} (L{PropertyBinding} or 186 L{MethodBinding}), or, as a shortcut for L{MethodBinding}, a C{list} of 187 two-C{tuples} like this:: 188 189 def bind_foo(self, ctx): 190 return [('argName', String()), ('anotherArg', Integer())] 191 192 def foo(self, argName, anotherArg): 193 assert isinstance(argName, str) 194 assert isinstance(anotherArg, int) 195 """ 196 implements(iformless.IConfigurable) 197 198 def getBindingNames(self, ctx): 199 """Expose bind_* methods and attributes on this class. 200 """ 201 for name in dir(self): 202 if name.startswith('bind_'): 203 yield name[len('bind_'):] 204 205 def getBinding(self, ctx, name): 206 """Massage bind_* methods and attributes into an 207 IBinding. The bind_* method or attribute can either 208 already implement IBinding or be a list of twoples 209 which will be massaged into a MethodBinding as 210 described in the ConfigurableMixin class docstring. 211 """ 212 def _get_binding(binding): 213 if callable(binding): 214 binding = util.maybeDeferred(binding, ctx) 215 return binding 216 217 def _convert_list(binding): 218 if isinstance(binding, list): 219 binding = annotate.MethodBinding( 220 name, annotate.Method(arguments=[ 221 annotate.Argument(n, v, v.id) 222 for (n, v) in binding])) 223 return binding 224 225 binding = util.maybeDeferred(getattr, self, 'bind_%s' % name) 226 return binding.addCallback(_get_binding).addCallback(_convert_list) 227 228 def getDefault(self, forBinding): 229 """Get a default value for a given binding. If the 230 binding is a Property, get the current value of 231 that property off self. If not, simply return 232 forBinding.default. 233 """ 234 ## If it is a Property, get the value off self 235 if not isinstance(forBinding, annotate.Argument): 236 if hasattr(self, forBinding.name): 237 return getattr(self, forBinding.name) 238 return forBinding.default 239 240 def postForm(self, ctx, bindingName, args): 241 """Accept a form post to the given bindingName. 242 The post arguments are given in args. 243 244 This will invoke the IInputProcessor for the 245 binding with the given name. If it succeeds, the 246 property will be modified or the method will have 247 been called. If it fails, a ValidateError exception 248 will be raised. 249 """ 250 def _callback(binding): 251 ctx.remember(binding, iformless.IBinding) 252 ctx.remember(self, iformless.IConfigurable) 253 rv = iformless.IInputProcessor(binding).process(ctx, self, args) 254 ctx.remember(rv, inevow.IHand) 255 ctx.remember('%r success.' % bindingName, inevow.IStatusMessage) 256 return rv 257 return util.maybeDeferred(self.getBinding, ctx, 258 bindingName).addCallback(_callback) 259 260 261class ConfigurableFactory: 262 """Locates configurables by looking for methods that start with 263 configurable_ and end with the name of the configurable. The method 264 should take a single arg (other than self) - the current context. 265 """ 266 implements(iformless.IConfigurableFactory) 267 268 def locateConfigurable(self, context, name): 269 """formless.webform.renderForms calls locateConfigurable on the IConfigurableFactory 270 instance it retrieves from the context. It passes the "name" that was passed to it, 271 so if renderForms() was placed in the DOM, locateConfigurable will be called with 272 name = ''; if renderForms('foo') was placed in the DOM, locateConfigurable will 273 be called with name = 'foo'. 274 275 This default implementation of locateConfigurable looks for a configurable_* method 276 corresponding to the name which was passed. 277 """ 278 return util.maybeDeferred(getattr(self, 'configurable_%s'%name), 279 context).addCallback(iformless.IConfigurable) 280 281 def configurable_(self, context): 282 """Configurable factory for use when self is a configurable; 283 aka it implements IConfigurable or one or more TypedInterface 284 subclasses. Usage: 285 286 >>> class IFoo(TypedInterface): 287 ... def bar(): pass 288 ... bar = autocallable(bar) 289 ... 290 >>> class Foo(Page): 291 ... implements(IFoo) 292 ... 293 ... def bar(): 294 ... print "bar called through the web!" 295 ... 296 ... def render_forms(self, ctx, data): 297 ... return renderForms() # or renderForms('') 298 ... 299 ... docFactory = stan(render_forms). 300 """ 301 if filter(lambda x: issubclass(x, annotate.TypedInterface), providedBy(self)): 302 warnings.warn("[0.5] Subclassing TypedInterface to declare annotations is deprecated. Please provide bind_* methods on your Page or Fragment subclass instead.", DeprecationWarning) 303 from formless import configurable 304 return configurable.TypedInterfaceConfigurable(self) 305 return self 306 307 def configurable_original(self, ctx): 308 """Configurable factory for use when self.original is a configurable; 309 aka it implements IConfigurable or one or more TypedInterface 310 subclasses. Usage: 311 312 313 >>> class Foo(Page): 314 ... def __init__(self): 315 ... self.original = SomeConfigurable() 316 ... def render_forms(self, ctx, data): 317 ... return renderForms('original') 318 ... docFactory = stan(render_forms) 319 """ 320 return self.original 321 322_CARRYOVER = {} 323 324 325def defaultsFactory(ctx): 326 co = _CARRYOVER.get( 327 ctx.tag.args.get('_nevow_carryover_', [None])[0], None) 328 from formless import webform 329 defaults = webform.FormDefaults() 330 if co is not None: 331 e = iformless.IFormErrors(co, {}) 332 for k, v in e.items(): 333 defaults.getAllDefaults(k).update(v.partialForm) 334 return defaults 335 336 337def errorsFactory(ctx): 338 co = _CARRYOVER.get( 339 ctx.tag.args.get('_nevow_carryover_', [None])[0], None) 340 from formless import webform 341 errs = webform.FormErrors() 342 if co is not None: 343 e = iformless.IFormErrors(co, {}) 344 for k, v in e.items(): 345 errs.updateErrors(k, v.errors) 346 errs.setError(k, v.formErrorMessage) 347 return errs 348 349 350def handFactory(ctx): 351 co = _CARRYOVER.get( 352 ctx.tag.args.get('_nevow_carryover_', [None])[0], None) 353 return inevow.IHand(co, None) 354 355 356def statusFactory(ctx): 357 co = _CARRYOVER.get( 358 ctx.tag.args.get('_nevow_carryover_', [None])[0], None) 359 return inevow.IStatusMessage(co, None) 360 361 362def originalFactory(ctx): 363 return ctx.tag 364 365 366class Fragment(DataFactory, RenderFactory, MacroFactory, ConfigurableMixin): 367 """ 368 This class is deprecated because it relies on context objects 369 U{which are being removed from Nevow<http://divmod.org/trac/wiki/WitherContext>}. 370 371 @see: L{Element} 372 """ 373 implements(inevow.IRenderer, inevow.IGettable) 374 375 docFactory = None 376 original = None 377 378 def __init__(self, original=None, docFactory=None): 379 if original is not None: 380 self.original = original 381 self.toremember = [] 382 if docFactory is not None: 383 self.docFactory = docFactory 384 385 def get(self, context): 386 return self.original 387 388 def rend(self, context, data): 389 # Create a new context so the current context is not polluted with 390 # remembrances. 391 context = WovenContext(parent=context) 392 393 # Remember me as lots of things 394 self.rememberStuff(context) 395 396 preprocessors = _getPreprocessors(self) 397 398 # This tidbit is to enable us to include Page objects inside 399 # stan expressions and render_* methods and the like. But 400 # because of the way objects can get intertwined, we shouldn't 401 # leave the pattern changed. 402 old = self.docFactory.pattern 403 self.docFactory.pattern = 'content' 404 self.docFactory.precompiledDoc = None 405 try: 406 try: 407 doc = self.docFactory.load(context, preprocessors) 408 finally: 409 self.docFactory.pattern = old 410 self.docFactory.precompiledDoc = None 411 except TypeError, e: 412 # Avert your eyes now! I don't want to catch anything but IQ 413 # adaption exceptions here but all I get is TypeError. This whole 414 # section of code is a complete hack anyway so one more won't 415 # matter until it's all removed. ;-). 416 if 'nevow.inevow.IQ' not in str(e): 417 raise 418 doc = self.docFactory.load(context, preprocessors) 419 except NodeNotFound: 420 doc = self.docFactory.load(context, preprocessors) 421 else: 422 if old == 'content': 423 warnings.warn( 424 """[v0.5] Using a Page with a 'content' pattern is 425 deprecated.""", 426 DeprecationWarning, 427 stacklevel=2) 428 429 context.tag = tags.invisible[doc] 430 return context 431 432 def remember(self, obj, inter=None): 433 """Remember an object for an interface on new PageContexts which are 434 constructed around this Page. Whenever this Page is involved in object 435 traversal in the future, all objects will be visible to .locate() calls 436 at the level of a PageContext wrapped around this Page and all contexts 437 below it. 438 439 This does not affect existing Context instances. 440 """ 441 self.toremember.append((obj, inter)) 442 443 def rememberStuff(self, ctx): 444 ctx.remember(self, inevow.IRenderer) 445 ctx.remember(self, inevow.IRendererFactory) 446 ctx.remember(self, inevow.IMacroFactory) 447 ctx.remember(self, inevow.IData) 448 449 450class ChildLookupMixin(FreeformChildMixin): 451 ## 452 # IResource methods 453 ## 454 455 children = None 456 def locateChild(self, ctx, segments): 457 """Locate a child page of this one. ctx is a 458 nevow.context.PageContext representing the parent Page, and segments 459 is a tuple of each element in the URI. An tuple (page, segments) should be 460 returned, where page is an instance of nevow.rend.Page and segments a tuple 461 representing the remaining segments of the URI. If the child is not found, return 462 NotFound instead. 463 464 locateChild is designed to be easily overridden to perform fancy lookup tricks. 465 However, the default locateChild is useful, and looks for children in three places, 466 in this order: 467 468 - in a dictionary, self.children 469 - a member of self named child_<childname>. This can be either an 470 attribute or a method. If an attribute, it should be an object which 471 can be adapted to IResource. If a method, it should take the context 472 and return an object which can be adapted to IResource. 473 - by calling self.childFactory(ctx, name). Name is a single string instead 474 of a tuple of strings. This should return an object that can be adapted 475 to IResource. 476 """ 477 478 if self.children is not None: 479 r = self.children.get(segments[0], None) 480 if r is not None: 481 return r, segments[1:] 482 483 w = getattr(self, 'child_%s'%segments[0], None) 484 if w is not None: 485 if inevow.IResource(w, None) is not None: 486 return w, segments[1:] 487 r = w(ctx) 488 if r is not None: 489 return r, segments[1:] 490 491 r = self.childFactory(ctx, segments[0]) 492 if r is not None: 493 return r, segments[1:] 494 495 return FreeformChildMixin.locateChild(self, ctx, segments) 496 497 def childFactory(self, ctx, name): 498 """Used by locateChild to return children which are generated 499 dynamically. Note that higher level interfaces use only locateChild, 500 and only nevow.rend.Page.locateChild uses this. 501 502 segment is a string representing one element of the URI. Request is a 503 nevow.appserver.NevowRequest. 504 505 The default implementation of this always returns None; it is intended 506 to be overridden.""" 507 return None 508 509 def putChild(self, name, child): 510 if self.children is None: 511 self.children = {} 512 self.children[name] = child 513 514 515class Page(Fragment, ConfigurableFactory, ChildLookupMixin): 516 """A page is the main Nevow resource and renders a document loaded 517 via the document factory (docFactory). 518 """ 519 520 implements(inevow.IResource) 521 522 buffered = False 523 524 beforeRender = None 525 afterRender = None 526 addSlash = None 527 528 flattenFactory = lambda self, *args: flat.flattenFactory(*args) 529 530 def renderHTTP(self, ctx): 531 if self.beforeRender is not None: 532 return util.maybeDeferred(self.beforeRender,ctx).addCallback( 533 lambda result,ctx: self._renderHTTP(ctx),ctx) 534 return self._renderHTTP(ctx) 535 536 def _renderHTTP(self, ctx): 537 request = inevow.IRequest(ctx) 538 ## XXX request is really ctx now, change the name here 539 if self.addSlash and inevow.ICurrentSegments(ctx)[-1] != '': 540 request.redirect(str(request.URLPath().child(''))) 541 return '' 542 543 log.msg(http_render=None, uri=request.uri) 544 545 self.rememberStuff(ctx) 546 547 def finishRequest(): 548 carryover = request.args.get('_nevow_carryover_', [None])[0] 549 if carryover is not None and _CARRYOVER.has_key(carryover): 550 del _CARRYOVER[carryover] 551 if self.afterRender is not None: 552 return util.maybeDeferred(self.afterRender,ctx) 553 554 if self.buffered: 555 io = StringIO() 556 writer = io.write 557 def finisher(result): 558 request.write(io.getvalue()) 559 return util.maybeDeferred(finishRequest).addCallback(lambda r: result) 560 else: 561 writer = request.write 562 def finisher(result): 563 return util.maybeDeferred(finishRequest).addCallback(lambda r: result) 564 565 preprocessors = _getPreprocessors(self) 566 doc = self.docFactory.load(ctx, preprocessors) 567 ctx = WovenContext(ctx, tags.invisible[doc]) 568 569 return self.flattenFactory(doc, ctx, writer, finisher) 570 571 def rememberStuff(self, ctx): 572 Fragment.rememberStuff(self, ctx) 573 ctx.remember(self, inevow.IResource) 574 575 def renderString(self, ctx=None): 576 """Render this page outside of the context of a web request, returning 577 a Deferred which will result in a string. 578 579 If twisted is not installed, this method will return a string result immediately, 580 and this method is equivalent to renderSynchronously. 581 """ 582 io = StringIO() 583 writer = io.write 584 585 def finisher(result): 586 return io.getvalue() 587 588 ctx = PageContext(parent=ctx, tag=self) 589 self.rememberStuff(ctx) 590 doc = self.docFactory.load(ctx) 591 ctx = WovenContext(ctx, tags.invisible[doc]) 592 593 return self.flattenFactory(doc, ctx, writer, finisher) 594 595 def renderSynchronously(self, ctx=None): 596 """Render this page synchronously, returning a string result immediately. 597 Raise an exception if a Deferred is required to complete the rendering 598 process. 599 """ 600 io = StringIO() 601 602 ctx = PageContext(parent=ctx, tag=self) 603 self.rememberStuff(ctx) 604 doc = self.docFactory.load(ctx) 605 ctx = WovenContext(ctx, tags.invisible[doc]) 606 607 def raiseAlways(item): 608 raise NotImplementedError("renderSynchronously can not support" 609 " rendering: %s" % (item, )) 610 611 list(flat.iterflatten(doc, ctx, io.write, raiseAlways)) 612 613 return io.getvalue() 614 615 def child_(self, ctx): 616 """When addSlash is True, a page rendered at a url with no 617 trailing slash and a page rendered at a url with a trailing 618 slash will be identical. addSlash is useful for the root 619 resource of a site or directory-like resources. 620 """ 621 # Only allow an empty child, by default, if it's on the end 622 # and we're a directoryish resource (addSlash = True) 623 if self.addSlash and len(inevow.IRemainingSegments(ctx)) == 1: 624 return self 625 return None 626 627 def webFormPost(self, request, res, configurable, ctx, bindingName, args): 628 """Accept a web form post, either redisplaying the original form (with 629 errors) if validation fails, or redirecting to the appropriate location after 630 the post succeeds. This hook exists specifically for formless. 631 632 New in 0.5, _nevow_carryover_ is only used if an autocallable method 633 returns a result that needs to be carried over. 634 635 New in 0.5, autocallables may return a nevow.url.URL or URLOverlay 636 instance rather than setting IRedirectAfterPost on the request. 637 638 New in 0.5, autocallables may return a Page instance to have that Page 639 instance rendered at the post target URL with no redirects at all. Useful 640 for multi-step wizards. 641 """ 642 def redirectAfterPost(aspects): 643 hand = aspects.get(inevow.IHand) 644 refpath = None 645 if hand is not None: 646 if isinstance(hand, Page): 647 refpath = url.here 648 if 'freeform_hand' not in inevow.IRequest(ctx).prepath: 649 refpath = refpath.child('freeform_hand') 650 if isinstance(hand, (url.URL, url.URLOverlay)): 651 refpath, hand = hand, None 652 653 if refpath is None: 654 redirectAfterPost = request.getComponent(iformless.IRedirectAfterPost, None) 655 if redirectAfterPost is None: 656 ref = request.getHeader('referer') 657 if ref: 658 refpath = url.URL.fromString(ref) 659 else: 660 refpath = url.here 661 else: 662 warnings.warn("[0.5] IRedirectAfterPost is deprecated. Return a URL instance from your autocallable instead.", DeprecationWarning, 2) 663 ## Use the redirectAfterPost url 664 ref = str(redirectAfterPost) 665 refpath = url.URL.fromString(ref) 666 667 if hand is not None or aspects.get(iformless.IFormErrors) is not None: 668 magicCookie = '%s%s%s' % (now(),request.getClientIP(),random.random()) 669 refpath = refpath.replace('_nevow_carryover_', magicCookie) 670 _CARRYOVER[magicCookie] = C = tpc.Componentized() 671 for k, v in aspects.iteritems(): 672 C.setComponent(k, v) 673 674 destination = flat.flatten(refpath, ctx) 675 request.redirect(destination) 676 from nevow import static 677 return static.Data('You posted a form to %s' % bindingName, 'text/plain'), () 678 679 return util.maybeDeferred( 680 configurable.postForm, ctx, bindingName, args 681 ).addCallback( 682 self.onPostSuccess, request, ctx, bindingName, redirectAfterPost 683 ).addErrback( 684 self.onPostFailure, request, ctx, bindingName, redirectAfterPost 685 ) 686 687 def onPostSuccess(self, result, request, ctx, bindingName, redirectAfterPost): 688 if result is None: 689 message = "%s success." % formless.nameToLabel(bindingName) 690 else: 691 message = result 692 693 return redirectAfterPost({inevow.IHand: result, inevow.IStatusMessage: message}) 694 695 def onPostFailure(self, reason, request, ctx, bindingName, redirectAfterPost): 696 reason.trap(formless.ValidateError) 697 return redirectAfterPost({iformless.IFormErrors: {bindingName: reason.value}}) 698 699 700def sequence(context, data): 701 """Renders each item in the sequence using patterns found in the 702 children of the element. 703 704 Sequence recognises the following patterns: 705 706 - header: Rendered at the start, before the first item. If multiple 707 header patterns are provided they are rendered together in the 708 order they were defined. 709 710 - footer: Just like the header only renderer at the end, after the 711 last item. 712 713 - item: Rendered once for each item in the sequence. If multiple 714 item patterns are provided then the pattern is cycled in the 715 order defined. 716 717 - divider: Rendered once between each item in the sequence. Multiple 718 divider patterns are cycled. 719 720 - empty: Rendered instead of item and divider patterns when the 721 sequence contains no items. 722 723 Example:: 724 725 <table nevow:render="sequence" nevow:data="peopleSeq"> 726 <tr nevow:pattern="header"> 727 <th>name</th> 728 <th>email</th> 729 </tr> 730 <tr nevow:pattern="item" class="odd"> 731 <td>name goes here</td> 732 <td>email goes here</td> 733 </tr> 734 <tr nevow:pattern="item" class="even"> 735 <td>name goes here</td> 736 <td>email goes here</td> 737 </tr> 738 <tr nevow:pattern="empty"> 739 <td colspan="2"><em>they've all gone!</em></td> 740 </tr> 741 </table> 742 743 """ 744 tag = context.tag 745 headers = tag.allPatterns('header') 746 pattern = tag.patternGenerator('item') 747 divider = tag.patternGenerator('divider', default=tags.invisible) 748 content = [(pattern(data=element), divider(data=element)) for element in data] 749 if not content: 750 content = tag.allPatterns('empty') 751 else: 752 ## No divider after the last thing. 753 content[-1] = content[-1][0] 754 footers = tag.allPatterns('footer') 755 756 return tag.clear()[ headers, content, footers ] 757 758 759def mapping(context, data): 760 """Fills any slots in the element's children with data from a 761 dictionary. The dict keys are used as the slot names, the dict 762 values are used as filling. 763 764 Example:: 765 766 <tr nevow:render="mapping" nevow:data="personDict"> 767 <td><nevow:slot name="name"/></td> 768 <td><nevow:slot name="email"/></td> 769 </tr> 770 """ 771 for k, v in data.items(): 772 context.fillSlots(k, v) 773 return context.tag 774 775 776def string(context, data): 777 return context.tag.clear()[str(data)] 778 779 780def data(context, data): 781 """Replace the tag's content with the current data. 782 """ 783 return context.tag.clear()[data] 784 785 786class FourOhFour: 787 """A simple 404 (not found) page. 788 """ 789 implements(inevow.IResource) 790 791 notFound = "<html><head><title>Page Not Found</title></head><body>Sorry, but I couldn't find the object you requested.</body></html>" 792 original = None 793 794 def locateChild(self, ctx, segments): 795 return NotFound 796 797 def renderHTTP(self, ctx): 798 inevow.IRequest(ctx).setResponseCode(404) 799 # Look for an application-remembered handler 800 try: 801 notFoundHandler = ctx.locate(inevow.ICanHandleNotFound) 802 except KeyError, e: 803 return self.notFound 804 # Call the application-remembered handler but if there are any errors 805 # then log it and fallback to the standard message. 806 try: 807 return notFoundHandler.renderHTTP_notFound(PageContext(parent=ctx, tag=notFoundHandler)) 808 except: 809 log.err() 810 return self.notFound 811 812 def __nonzero__(self): 813 return False 814 815 816# Not found singleton 817NotFound = None, () 818