[Twisted-web] fragments with child lookup

Markus Schiltknecht markus at bluegap.ch
Mon Sep 25 12:24:43 CDT 2006


Hi,

Valentino Volonghi aka Dialtone wrote:
> There's no magic in def _()... it's just a closure. Yes, the designer 
> should pass the level to the renderer.

IMHO, the designer should not have to care about level of nested-ness of 
his templates.

> macros are a much better way to integrate a pre-existing template into a 
> basic template. They work at TEMPLATE level not at Page or Fragment level.

..and that's the problem: working at the template level is not 
sufficient for my application.

> This template will be used for the Root class ONLY. Any other subclass 
> will re-pre-compile the macro to get its own template.
> 
> After having worked in this way... How do you embed applications then?
> SIMPLE!! You Change the macro template and instead of providing that 
> one, you simply provide a new one that fits your web application 
> defaults while keeping the macro slots defined in there so that the 
> application you want to embed won't be surprised by the new template. 
> How is that simple? It's so simple I shouldn't even tell you... 

Yeah, it's simple and works well for a lot of things.

> templateDir is a configuration option, change it in the configuration 
> and give it a new location where the new templates are available.
> 
> There is no other way and there's no cleaner way to do this in my opinion.
> Why? Because you cannot embed applications randomly without having first 
> planned for this option in the application that needs to be embedded 
> because it has certain requirements about the API and simply moving 
> around class cannot work anyway.

Why should that not work? What about WSGI as a simple API? There are 
systems, that use WSGI to pass 'requests' around, processing parts of it 
(i.e. segments, in nevow terms).

> Is this good or bad? I don't know, I 
> think it's good. Is it possible to just put applications running in 
> another one like they are completely separate? Of course... it's even 
> easier, just return one's root page from the appropriate link.

That's fine as long as you don't want to do any processing between the 
parent and the child application. As you say, you have to 'prepare' the 
application which is to be embedded. And it's hard to do that dynamically.


Anyway, I've partly succeeded to do what I want. And sometimes a patch 
says more than 1000 words:


Index: pastebin.tac
===================================================================
--- pastebin.tac	(revision 9132)
+++ pastebin.tac	(working copy)
@@ -5,18 +5,58 @@

  from nevow import appserver
  from nevow import vhost
+from nevow import loaders
+from nevow import tags as T
+from nevow import rend

  from pastebin import interfaces
  from pastebin.service import FSPasteBinService
  from pastebin.web import pages

+from container import ContainerPage

  application = service.Application('pastebin')

  pastebin = FSPasteBinService('data')
  pastebin.setServiceParent(application)

-appResource = pages.RootPage(pastebin)
+class SubContainer(ContainerPage):
+    addSlash = True
+
+    docFactory = loaders.stan(
+        T.div[
+            T.h2['hello component based world'],
+            T.invisible(render=T.directive('child_content'))[
+                T.slot('child_content')]
+        ])
+
+    defaultChildPage = loaders.stan([
+        'another sub pastebin: ',
+        T.a(href='pastebin')['pastebin']])
+
+    child_pastebin = pages.RootPage(pastebin)
+
+class NewSuperRootPage(ContainerPage):
+    addSlash = True
+
+    docFactory = loaders.stan(
+        T.html[
+            T.head(),
+            T.body[
+                T.h1['hallo welt'],
+                T.invisible(render=T.directive('child_content'))[
+                    T.slot('child_content')],
+            ]])
+
+    defaultChildPage = loaders.stan([
+        'a sub component: ',
+        T.a(href='pastebin')['pastebin'], T.br(),
+        T.a(href='sub_component')['a component']])
+
+    child_pastebin = pages.RootPage(pastebin)
+    child_sub_component = SubContainer()
+
+appResource = NewSuperRootPage()
  appResource.putChild('robots.txt', static.File('static/robots.txt'))
  vResource = vhost.VHostMonsterResource()
  appResource.putChild('vhost', vResource)
Index: container.py
===================================================================
--- container.py	(revision 0)
+++ container.py	(revision 0)
@@ -0,0 +1,63 @@
+from zope.interface import implements, Interface
+
+from twisted.application import strports
+from twisted.application import service
+
+from twisted.web import static
+
+from nevow import appserver
+from nevow import vhost
+from nevow import inevow, rend, stan, loaders, tags as T, context
+from nevow.util import qual
+
+class IChildPages(Interface):
+    pass
+
+class ContainerPage(rend.Page):
+
+    def render_child_content(self, ctx, data):
+        print "render_child_content: context: %s" % str(ctx)
+
+        try:
+            child_pages = ctx.locate(IChildPages)
+            child = child_pages[0]
+            print "child: %s" % str(child)
+            ctx.remember(child_pages[1:], IChildPages)
+            ctx.fillSlots('child_content', child)
+        except KeyError:
+            ctx.fillSlots('child_content', self.defaultChildPage)
+        except IndexError:
+            ctx.fillSlots('child_content', self.defaultChildPage)
+
+        return ctx
+
+    def locateChild(self, ctx, segments):
+        if segments[0] == '':
+            return self, ()
+
+        try:
+            child_pages = ctx.locate(IChildPages)
+        except KeyError:
+            print "no IChildPages, yet"
+            child_pages = []
+            child = self
+
+        while len(segments) > 0 and segments[0] != '':
+            if isinstance(child, ContainerPage):
+                print "   querying self for segments %s" % str(segments)
+                child, segments = super(rend.Page, 
self).locateChild(ctx, segments)
+            else:
+                print "   querying child %s for segments %s" % 
(str(child), str(segments))
+                child, segments = child.locateChild(ctx, segments)
+            print "       got child %s" % str(child)
+            print "       segments remaining %s" % str(segments)
+
+            if isinstance(child, ContainerPage):
+                child_pages.append(child)
+
+        if not isinstance(child, ContainerPage):
+            child_pages.append(child)
+
+        ctx.remember(child_pages, IChildPages)
+        print "child_pages: %s" % str(child_pages)
+        return self, segments


Of course this is not perfect, yet. But it's a good start. It serves all 
the URLs I want with the correct templates. The pastebin got embedded twice.

The links are not correct (at least in the pastebin). I have also 
written a preprocessor which takes care of links in the templates. I'm 
not sure how to make the pastebin return the correct links, though.

Imagine all the possibilities this opens: you could for example write a 
Container which holds multiple pastebins for different categories. 
Having a template surrounding the original pastebin and offering to jump 
between the categories. All with just a simple ContainerPage with all 
the comfort of nevow (docFactories, children, renderers, etc...). If you 
don't hard code the boring pastebin example, but make it dynamically 
configurable, you can wrap whatever component you want. You have a 
category chooser which can be applied to any other component. That's 
what I call 'component based'!

And writing a ContainerPage is as simple as giving it a docFactory which 
renders 'child_content', adding some children and adding a defaultChildPage!

Anybody else out here seeing the benefits of that?

Regards

Markus



More information about the Twisted-web mailing list