How to access variables in python blocks(<% %>) in named block(<%block name="myblock">)?

2,905 views
Skip to first unread message

patto

unread,
Sep 12, 2012, 3:23:27 AM9/12/12
to mako-d...@googlegroups.com
Hi,

    Here is my code:

    ## base.html
    <%block name="title">
        <title>Common Title</title>
    </%block>


   ## detail.html (inheriting template)
   <%inherit file="base.html" />
   <%
       detail_title = detail_obj.title   ## TemplateLookup.get_template('detail.html').renader( detail_obj=detail_obj )
   %>
    <%block name="title">
        <title>${ detail_title }</title>
    </%block>


    Using above code, I am told that "TypeError: sequence item 35: expected string or Unicode, Undefined found". 
    It seems that it cannot access variables declared in python blocks,  so what should I do?

Christopher the Magnificent

unread,
Sep 12, 2012, 12:00:32 PM9/12/12
to mako-d...@googlegroups.com
Dear someone,

I'm not the top guru on this list, but I think I can answer this question.

If you're going to use the detail_title variable in various places throughout the template, try putting that assignment in <%! ... %>  (note the exclamation point following the percent sign) instead of <% ... %> which will make it a global variable instead of local to the template's body() function.

Otherwise, you may also move the the Python-block assignment directly inside of the block statement like below.  

<%block name="title">
<% detail_title = detail_obj.title %>
<title>${detail_title}</title>
</%block>

Of course, then the variable detail_title is scoped only to that block, and may not be used outside the block or from any sub-block contained therein.

Either of these should fix the error.

I believe the reason it has to work like this is that the top-level stuff in the template goes into the resulting Python module's body() function and everything in a named block goes into its own top-level function so that things you assign to with Python blocks <%  %> in one function (whether explicit or implicit, like body) are not defined in another function (blocks are implemented as top-level Python functions).  To make Python code be executed at the global level, so all resulting assignments and definitions are guaranteed to be accessible anywhere in the template, then use <%! to start the Python block.

Two other ideas are these:

1. to just always write ${detail_obj.title} instead of defining detail_title and using ${detail_title}.
2. Access the title attribute of detail_obj in your Python code and pass it directly to the Mako template as detail_title.

Hope this helps!

—Christopher


--
You received this message because you are subscribed to the Google Groups "Mako Templates for Python" group.
To view this discussion on the web visit https://groups.google.com/d/msg/mako-discuss/-/H6ELN6WfXQYJ.
To post to this group, send email to mako-d...@googlegroups.com.
To unsubscribe from this group, send email to mako-discuss...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/mako-discuss?hl=en.

Patto

unread,
Sep 12, 2012, 11:15:39 PM9/12/12
to mako-d...@googlegroups.com
Christopher:

      Thanks for your reply :)
      
      I can not put the assignment into a <!% %> block, because this type of block is executed when template is compiled, so it is not able to 
      access the different data per request per user, and such. As I know there should be only some static definition or logic in a <!% %> block.

      I can not put the python-block into the named  blocked where need it, because there are multiple named blocks in the template needing the variables in the python-block.  I do not want to repeat it.

      to just always write ${detail_obj.title} instead of defining detail_title and using ${detail_title}.
      For performance, I want to cache the detail_obj.title in a variable instead of calling it each time. Actually, I have done a property cache in the CLASS, however there is something I cannot handle.

2. Access the title attribute of detail_obj in your Python code and pass it directly to the Mako template as detail_title.

 Yes,  I am doing in this way now. Hope for a better solution.

Jason McKellar

unread,
Sep 13, 2012, 9:18:08 AM9/13/12
to mako-d...@googlegroups.com


On Wednesday, September 12, 2012 11:15:40 PM UTC-4, patto wrote:
Christopher:

      Thanks for your reply :)
      
      I can not put the assignment into a <!% %> block, because this type of block is executed when template is compiled, so it is not able to 
      access the different data per request per user, and such. As I know there should be only some static definition or logic in a <!% %> block.

   
I think you can get passed variables in the <!% %> block by using context.get('detail_obj').title. 

-- Jason

Christopher Johnson

unread,
Sep 13, 2012, 10:51:31 AM9/13/12
to mako-d...@googlegroups.com
Patto and Jason,

You probably meant to write it the other way, and I'm not trying to be a nitpicker, but just to be clear, especially for the sake of anyone else reading this, I believe the opening tag for module-level blocks is 

<%! 

with the exclamation point FOLLOWING the percent sign, yes?  Both of you, Jason and Patto, wrote it with the percent sign preceding the percent, which is likely to give errors and not do what you want.

My tests show that context.get("detail_obj").title will NOT work inside a module Python block <%!  %>, because the context variable is a Python function parameter, and not available at module scope.

Here's another approach, which is the best that I can recommend, but I think that you will like it better than computing detail_title in your separate Python file and passing it in as a pre-built parameter.  Try this:

## detail.html
<%inherit file="base.html" />

<%def name="detail_title()" buffered=True>
    ${detail_obj.title}
</%def>

<%block name="title">
    <title>${detail_title()}</title>
</%block>


This gives you the maximum flexibility so that you can do normal variable things like

${detail_title() + " (the title is " + unicode(len(detail_title())) + " characters long)"
or
${detail_title().upper()}

This is the way I recommend.  

Remember that normally, a def function writes its output directly to the output stream with context.write() rather than returning the output as the string result of the function call, and after writing to the output stream, it returns an empty string (see http://docs.makotemplates.org/en/latest/filtering.html#buffering).  The buffered=True attribute stops Mako from doing this and returns the output as the result of the string, as our (or at least my) intuition wants to believe.  This seems closest to the behavior of a variable, so this is probably what you want.

Keep in mind, that the object's title attribute is computed EVERY TIME the function is called, not just once and stashed in a variable.  Clearly it's more efficient to do it way I first suggested of computing the title once and passing that in as a template argument.

If you want to preserve the type of the variable (not automatically convert it to a string) here's how I would do it.  Suppose there is the template parameter named object1.  Suppose you need to do some operations on it which give you another object that can't be losslessly represented as a string.  Let's say object1 is the parameter to the template and detail_object is the thing to be computed. Then do this instead:

<%def name="detail_object()">  ## no buffered attribute
<% return some_function(object1.operation1(2498).operation2({"keep": "going"})) %>
</%def>

Instead of converting the expression to a string as ${} always does, it returns the value directly with its original type and such.

Then you can do 

${detail_object().title}

or some other final operation on the object after the call.  Remember again, that all the computation is repeated for each call to detail_object().

I believe it's better form it save heavy computations for the Python files and just do mostly cherry-picking stuff in the Mako templates.

I think this should give you some ideas to work with.

Happy Mako-ing!

—Christopher

--
You received this message because you are subscribed to the Google Groups "Mako Templates for Python" group.
To view this discussion on the web visit https://groups.google.com/d/msg/mako-discuss/-/3WPYjvmR5goJ.

Jason McKellar

unread,
Sep 13, 2012, 3:30:35 PM9/13/12
to mako-d...@googlegroups.com


On Thursday, September 13, 2012 10:51:38 AM UTC-4, Christopher the Magnificent wrote:
Patto and Jason,

You probably meant to write it the other way, and I'm not trying to be a nitpicker, but just to be clear, especially for the sake of anyone else reading this, I believe the opening tag for module-level blocks is 

<%! 

with the exclamation point FOLLOWING the percent sign, yes?  Both of you, Jason and Patto, wrote it with the percent sign preceding the percent, which is likely to give errors and not do what you want.

My tests show that context.get("detail_obj").title will NOT work inside a module Python block <%!  %>, because the context variable is a Python function parameter, and not available at module scope.


Ah ok, I use "<%inherit file="${context['base_template']}"/>" and I thought the inherit tag was also module-level, but I didn't actually try it out.

-- Jason

Arnaud Hebert

unread,
Apr 13, 2017, 4:40:37 PM4/13/17
to Mako Templates for Python, ultimatem...@gmail.com
Thanks Chris!
You helped me resolve my issue.
Reply all
Reply to author
Forward
0 new messages