[Twisted-web] Re: Nevow and template like files

Nuutti Kotivuori naked at iki.fi
Wed Sep 6 03:03:28 CDT 2006


Valentino Volonghi aka Dialtone wrote:
> On Tue, 05 Sep 2006 22:48:08 +0300, Nuutti Kotivuori <naked at iki.fi> wrote:
>> Thanks! I've looked already at that and it seems nice... except for
>> two points.
>>
>> First of all, the content is looked up like this:
>>
>> ,----
>> | def macro_content(self, ctx):
>> | return loaders.xmlfile(self.template+'.html', ignoreDocType=True,
>> |                            templateDir=conf.TEMPLATE_PATH)
>> `----
>>
>> Since I have several patterns in a single file, I would need to append
>> pattern='content' keyword into that. But if I do that, then the file
>
> I disagree. If you use a template as a pattern container than load
> it completely and use inevow.IQ() to get patterns from it. Patterns
> are not rendered anyway so just style the real content of the
> template and grab patterns togheter with it. Later on you'll be able
> to merge the patterns inside the rendering result.

Yes, I would want to load it completely - but where should I do that
and how? I don't know if I should call the .load() method on the
docfactory myself if I want to keep the preprocessors machinery intact
and everything.

Or hmm, are you proposing on returning the entire docfactory from
somewhere? I don't want to do that as the file contains <html><head>
etc. blocks outside the patterns so that it will be nicer to edit in
HTML editors.

Let's assume I have a content file like this (pseudohtml)

,----[ content.xhtml ]
| <html>
|   <head>
|     <title><n:invisible n:pattern="title">Page title</n:invisible></title>
|     <n:invisible n:pattern="head">
|       <link rel="stylesheet" type="text/css" href="additional.css" />
|     </n:invisible>
|   </head
|   <body>
|     <div class="content">
|       <n:invisible n:pattern="content">
|         <p>Page content.</p>
|       </n:invisible>
|     </div>
|     <div class="help">
|       <n:invisible n:pattern="help">
|         <p>Page help.</p>
|       </n:invisible>
|     </div>
|   </body>
| </html>
`----

Now, from this, I'd want to use the patterns defined by embedding them
in a layout page.

>> will get read once by every macro that uses a pattern from it? That
>
> Once by every macro yes, but only once per macro, not once per
> request per macro. It's a very light cost.

I meant once by every macro - this is no longer really relevant if I
load the whole page completely first.

But if I didn't, loading the whole page once per macro would still
amount to quite a lot of extra loadings of the file if I read the
entire file for each pattern.

>> I'd like to avoid - I'd like to keep the loaders in a class
>> variable nicely, or a Fragment, or something.
>> Also, if I say pattern='content', then that loader will throw a hissy
>> fit if the pattern isn't present. So I'd need a loader that would
>> return some default value when the .load() method is called and that
>> sounds like an icky solution again. No worries though, it's easy to
>> hack.
>
> You should not hack it. Nope, what if the default value is not there
> too?  Use a Fragment (or rather an Element instead). Besides that I
> don't really think that templates containing many patterns are a
> good way to deal with the problem, many templates each with a
> Fragment associated is a much better way to solve it.

Okay, consider the content page shown above. Now let's assume that
most pages don't need the "head" pattern at all. I wouldn't want to
write the empty pattern on all pages just because it has to be there,
instead I would just want to say that put T.invisible() there if the
pattern doesn't exist. (With just the "head" pattern it wouldn't be so
much of a problem, but I might later on have to add some more, and I
don't want to change every content page just because one content page
requires something special.)

So this is what I'd like the default for. But ofcourse, if I load the
entire page first, and then just use the inevow.IQ machinery, making
the default patterns is easy.

>> So, any ideas how I could have just one loader that would load the
>> file once, and then just use normal nevow machinery to fetch the
>> patterns from it (where it is easier to get a default value if the
>> pattern does not exist)?
>
> I told you:
>
> loaded = loaders.xmlfile('file')
> some_pattern = inevow.IQ(loaded).onePattern('whatever')

That seems to work at first - but if there are any macros or render
methods inside the file I load, then I get Could not adapt error to
IMacroFactory.

>> Actually, I have a bit of a problem there - how do I get the whole
>> document loaded in the methods I have? I can call
>> self.docFactory.load() in every method ofcourse, but then the context
>> is a bit screwy - however, I hesitate to pass in the context that is
>> available at the time of the first call, since that might come from a
>> different page each time and I'm not sure if the render/macro methods
>> are going to be invoked from the correct object and such.
>
> Don't pass the context, what's the problem here?

Macros and render methods. In the case of the snippet pages, I would
like them to be interpreted in the context of the Fragment class that
is loading the entire snippet page.

>> I thought that the rendered/parsed version of the template resided in
>> the loaders.xmlfile instance that is created - and if I return a new
>> loaders.xmlfile instance (with a different pattern clause) from
>> several different macros, then the page would get loaded as many
>> times. Or if I returned a loaders.xmlfile instance from a render
>
> No because the macro is not reloaded. Just try to modify a file that
> was compiled in macro. You'll see that unless you modify the file
> containing the macro nothing changes.

I understand that the macro is not reloaded - but each macro will load
the page once. Which would be a problem on a page with hundreds of
snippets if every snippet would cause the page to be loaded once.

>> method, then the file would be parsed on each rendition of the page -
>> am I wrong?
>
> Yes a bit.
>
>> Patterns are optional yes, but if I call
>>
>> loaders.xmlfile(filename, pattern='foobar')
>>
>> that will throw an exception if pattern 'foobar' is not present in the
>
> Of course... You are telling it to search exactly for a particular pattern.

...and this was exactly the problem I was having - if I would use a
construct like that

>> file. So will onePattern(), but that is easily wrapped in a try/catch
>> block (or I can use patternGenerator which does take a default
>> argument in some cases) - where as a loader being wrapped doesn't do
>> anything since the actual loading happens later (when the load()
>> method is called).
>
> correct.
>
> It's not very clear what's your problem though. Maybe you should
> either show some code or try to solve it in the way you explained
> which seemed reasonable anyway.

Okay, I will try to be a bit clearer. For now, I have two problems -
how to load the "content" page all at once and then use patterns with
optional defaults from it. And then the snippet problem. I will
outline the snippet problem later.

I wrote a really tiny testsite outlining my problem. I will put the
three files here:

,----[ testpage.xhtml ]
| <?xml version="1.0"?>
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| <html xmlns="http://www.w3.org/1999/xhtml"
|   xmlns:n="http://nevow.com/ns/nevow/0.1">
|   <head>
|     <title>
|       <n:invisible n:macro="title">Page Title</n:invisible>
|     </title>
|     <n:invisible n:macro="head" />
|   </head>
| 
|   <body>
|     <h1><n:invisible n:macro="title">Page Title</n:invisible></h1>
| 
|     <div class="plain-content">
|       <n:invisible n:macro="content" />
|     </div>
|   </body>
| </html>
`----

,----[ datapage.xhtml ]
| <?xml version="1.0"?>
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| <html xmlns="http://www.w3.org/1999/xhtml"
|   xmlns:n="http://nevow.com/ns/nevow/0.1">
|   <head>
|     <title><n:invisible n:pattern="title">Data Page</n:invisible></title>
|     <n:invisible n:pattern="head" />
|   </head>
|   <body>
|     <n:invisible n:pattern="content">
|       <p>
|       <n:invisible n:macro="test">
|           Replaced by test data.
|         </n:invisible>
|       </p>
|     </n:invisible>
|   </body>
| </html>
`----

,----
| from nevow import inevow, loaders, rend, guard, url, stan, tags as T
| 
| class TestPage(rend.Page):
|     docFactory = loaders.xmlfile('testpage.xhtml')
|     datapage = None
| 
|     def macro_title(self, ctx):
|         return loaders.xmlfile(self.datapage, pattern='title')
| 
|     def macro_head(self, ctx):
|         return loaders.xmlfile(self.datapage, pattern='head')
| 
|     def macro_content(self, ctx):
|         return loaders.xmlfile(self.datapage, pattern='content')
| 
| class DataPage(TestPage):
|     datapage = 'datapage.xhtml'
| 
|     def macro_test(self, ctx):
|         return 'Test successful.'
`----

Now, how would I modify this example so that "datapage.xhtml" is
loaded only once, even though there are three different macros using
it. And also so that the "head" pattern could be optional and is
replaced by T.invisible() if not present. All the while not breaking
the macro_test part, splitting the pages into several files or
breaking the normal html rendability/editability of the datapage.

I will come up with something outlining the snippet problem later.

Thanks in advance,
-- Naked




More information about the Twisted-web mailing list