neRok
neRok

Reputation: 1005

Possible to override a sub-def so mako works like an inherited python class?

I'm trying to create some reusable 'widgets' in mako, using defs. I would like all the defs defined in 1 place, so I can import 1 file and call each widget as required.

If my mako template were python objects, I would have it sort of like;

class Div(object):
    element = 'div'
    def render(self):
        return '<{0}>Hello</{0}>'.format(self.element)

class Span(Div):
    element = 'span'

Here is what I have attempted in mako, but it doesn't work;

<%def name="div()">
    <%def name="element()">div</%def>
    <${element()}>Hello</${element()}
</%def>

<%def name="span()">
    <%def name="element()">span</%def>
    <${div()}>
</%def>

If I call span(), it renders the same as div(). I believe this is because the element sub-def isn't overridden, as there isn't any inheritance in play. I've also tried things like ${self.element()}.

Reading the mako docs, I'm not sure if it's possible to do this at all.

Where above, the title() def, because it’s a def within a def, is not part of the template’s exported namespace and will not be part of self. If the inherited template did define its own title def at the top level, it would be called, but the “default title” above is not present at all on self no matter what.

If this were a normal template, block tags and inheritance would work fine, but I note the following;

So while the <%block> tag lifts the restriction of nested blocks not being available externally, in order to achieve this it adds the restriction that all block names in a single template need to be globally unique within the template, and additionally that a <%block> can’t be defined inside of a <%def>. It’s a more restricted tag suited towards a more specific use case than <%def>.

So I don't think I can use blocks in any other way for this task either.

Perhaps there is a different way, or a different template system that will handle this better?

Upvotes: 0

Views: 595

Answers (3)

zzzeek
zzzeek

Reputation: 75117

for widget libraries you should be using def calls with embedded content. I don't understand how you've gotten into that "getattr" stuff, you can use the tags pretty much as documented:

from mako import lookup

l = lookup.TemplateLookup()

l.put_string(
    "functions",
    """

<%def name="widget(tag='div')">\
    <${tag}>\
        ${caller.body()}\
    </${tag}>\
</%def>

<%def name="div()">\
    <%self:widget tag='div'>\
        ${caller.body()}\
    </%self:widget>\
</%def>

<%def name="span()">\
    <%self:widget tag='span'>\
        ${caller.body()}\
    </%self:widget>\
</%def>

""")

l.put_string(
    "caller",
    """
<%namespace name="functions" file="functions"/>

<%functions:span>
    im a span
</%functions:span>

<%functions:div>
    im a div
</%functions:div>


<%functions:widget tag='foob'>
    im a foob
</%functions:widget>

""")


print l.get_template("caller").render()

output:

    <span>                
im a span
    </span>

    <div>                
im a div
    </div>


<foob>        
im a foob
</foob>

Upvotes: 0

stringtheory
stringtheory

Reputation: 11

If you pass the html tag as an argument you would eliminate the need to have span inherit from div.

    <%def name="tag(tag='div', word='Hello')">
      <${tag}>
        ${word}
      </${tag}>
    </%def>

Upvotes: 1

neRok
neRok

Reputation: 1005

I found a solution, but I really hope this isn't the one and only way with mako.

<%def name="div(word='Hello')">
    <%def name="element()">div</%def>
    <${getattr(caller, 'element', element)()}>
        ${word}
    </${getattr(caller, 'element', element)()}>
</%def>

<%def name="span(**kwargs)">
    <%self:div kwargs='${kwargs}'/>
        <%def name="element()">span</%def>
    </%self:form_group>
</%def>

**kawrgs support was something I wanted (for similar reasons to how you would use them with a class and init and super() in python), so I included it in the example. Basically, I need to def the span function, then inherit the div function with the <%namespace:function /> block, and then override the element function within it.

In the div function, I couldn't run ${caller.element()}, because in the case of ${div()}, there is no caller and thus no element function. I couldn't run ${element()}, because in the case of ${span()}, it always uses the 'local' element function (ie it prints "div"). Thus I had to settle on ${getattr(caller, 'element', element)()}, which tries to get the element function from the caller, and defaults to the local element function otherwise.

This seems like a janky solution though.

Upvotes: 0

Related Questions