[Grok-dev] first thoughts on "regebro-guido-templates"
Brandon Craig Rhodes
brandon at rhodesmill.org
Sun Oct 28 01:22:18 EDT 2007
I have many things to say about the "regebro-guido-templates" branch,
but since it's now after midnight, this email will only include two
thoughts - which, if they get addressed in the branch, might start
clearing up smaller issues that I won't mention for now.
The two subjects are:
1) Why self.__grok_module__ keeps getting involved.
2) Putting template logic together into one class.
My ideas:
1) The question was raised: Why does the new template code require
template plugin classes to do this in their __init__() functions:
self.__grok_module__ = martian.util.caller_module()
This should go away. What is it trying to do?
I have looked around, and noticed that the old-fashioned Grok
template logic does exactly the same thing. And the reason why
hinges on an interesting asymmetry: a Grok programmer creates
Models and Views through subclassing, but he creates Templates
through instantiation!
Since martian is built around the idea of grokking classes, it
only pays attention to objects whose __module__ is the same as
that of the module it's searching. This works wonderfully for
classes: it makes martian ignore classes you've brought into your
module through "import", since their __module__ names the one they
were imported from, not the one martian's currently scanning. But
it has the side-effect of making martian completely ignore class
instances defined in another module from the class itself, because
Python instances don't have their own __module__ attribute - they
inherit the value from their class instead. (For details, grep
through martian for the "locally_defined" function.)
And, again, all of this causes problems only because of the
asymmetry between how you create, say, a view:
class MyView(grok.View):
...
and how you create a template:
myTemplate = grok.PageTemplate("...")
which produces a mere instance. This all brings us back to that
ugly:
self.__grok_module__ = martian.util.caller_module()
call which, we now see, is an attempt to fool martain into looking
at instances by stating which module they "belong to".
I will suggest four alternative approaches, any of which I think
would make things far simpler, and it would be really nice if the
"regebro-guido-templates" branch was converted to one of them
before being merged. I'm willing to write code. :-)
Approach A: Make templates classes.
This would abandon the asymmetry above, and require users to
create a class for each inline template they wanted to create.
Instead of having class-instance pairs like:
class AView(grok.View):
...
aTemplate = grok.PageTemplate("...")
they would have pairs of classes, something like:
class AView(grok.View):
...
class ATemplate(grok.PageTemplate):
content = '...'
This might have other advantages involving the ability to mark
up templates with the same sorts of directives we use for
other things in Grok. For example, a Template could accept a
grok.context(...) (or grok.view(...)?) statement explicitly
stating the View it should work with, just like Views can
state the objects that they adapt. Or maybe that would never
be useful? :-)
Approach B: Teach martian about instances explicitly.
This would maintain the asymmetry between View classes and
inline Template instances, but rather than requiring the
implementor to practice witchcraft, would introduce a new
martian directive "instances()" that tells martian to pay
attention to instances of a class, not just the class itself.
So template classes would look like:
class GenshiTemplate(...):
grok.instances()
....
instead of having to set __grok_module__ on every single
dratted instance that it wants martian to pay attention to.
This would involve the difficult decision as to whether
grok.instances() should be inherited by subclasses of a class,
or whether each of them should have to repeat the directive in
order to make its own instances discoverable. The latter
seems the safer approach but I'm too tired at this point to
ponder all the issues.
Approach C: make Views link to their inline templates explicitly
Magically associating the View my.Foo with the contents of the
file "my_templates/foo.pt" is great. But how important is it,
really, to magically associate a variable named "foo" with the
View?
class Foo(grok.View):
...
foo = grok.PageTemplate(...)
Couldn't we instead have the View state that it wanted to use
an inline template like this?
foo = grok.PageTemplate(...)
class Foo(grok.View):
grok.template(foo)
...
Then we would not need magic to grok instances at all.
Approach D: search for templates without grokking them
Why grok instances of templates at all? Template files like
"foo.pt", after all, aren't grokked! They're discovered by a
little routine that scans the _templates directory and looks
for appropriate files. Instead of grokking template
instances, why not just explicitly scan each module for
template instances in a search that exactly parallels the
search for template files? The scans could be run and compare
their findings for conflicts first ("template 'foo' is defined
both in 'foo.pt' and in-line!"), then offer the resulting
collection of templates to the association routine. This
could, I think, be done straightforwardly by adding a
findModule() routine to templatereg.py that parallels the
structure of findFilesystem().
Those wiser, more experienced, and more awake ought to weigh in on
these approaches. I think I will have to read through them again
tomorrow before even starting to decide which one I myself like
best. :-)
2) I think the reason that "regebro-guido-templates" offers a clumsy
and verbose mechanism for plugging in new templates is that they
copied the two-class way that Grok already does it, which is
clumsy and verbose because Grok itself inherited it from
zope.pagetemplate. :-)
By "two-class", I mean that you have to create one class for
inline templates, and another class for templates that are stored
in files. I think these two situations should be collapsed into
one class, so that the person plugging in a new template language
just writes one class like:
class MyPageTemplateLanguage(grok.PageTemplateLanguage):
grok.name('mtl')
def setup(self, filename, content):
self._template = genshi.Template(content)
def render(self, view):
return self._template.render(**self.namespace(view))
Okay, I'm too tired to keep typing now. I'll outline more about a
single-class approach tomorrow, maybe. And then I can get started on
how the namespace() situation could perhaps be simplified and
streamlined a bit. But, tomorrow.
--
Brandon Craig Rhodes brandon at rhodesmill.org http://rhodesmill.org/brandon
More information about the Grok-dev
mailing list