Commits

Andriy Kornatskyy committed 45ee85f

Introduced TenjinTemplate support; updated template demo with tengin.

Comments (0)

Files changed (46)

demos/template/Makefile

 	find src/ -type d -name __pycache__ | xargs rm -rf
 	find src/ -name '*.py[co]' -delete
 	find src/ -name '*.mo' -delete
+	find src/ -name '*.cache' -delete
 	rm -rf /tmp/mako_modules/
 
 release:
 	xgettext --join-existing --sort-by-file --omit-header \
 		--language=Python \
 		-o i18n/membership.po src/membership/**/*.py \
-		content/templates/membership/*.html
+		content/templates-mako/membership/*.html \
+		content/templates-tenjin/membership/*.html
 	cp i18n/membership.po i18n/en/LC_MESSAGES
 	cp i18n/validation.po i18n/en/LC_MESSAGES
 	for l in `ls --hide *.po i18n`; do \
 		$(ENV)/bin/gunicorn -b 0.0.0.0:8080 -w 1 app:main
 
 test:
+	echo mako ; export TEMPLATE_ENGINE=mako ; \
+	$(PYTEST) -q -x --pep8 --doctest-modules src/ ; \
+	echo tenjin ; export TEMPLATE_ENGINE=tenjin ; \
 	$(PYTEST) -q -x --pep8 --doctest-modules src/
 
 test-cover:
 		src/
 
 benchmark:
+	echo mako ; export TEMPLATE_ENGINE=mako ; \
+	$(NOSE) -qs -m benchmark src/ ; \
+	echo tenjin ; export TEMPLATE_ENGINE=tenjin ; \
 	$(NOSE) -qs -m benchmark src/
 
 profile:

demos/template/content/templates-mako/membership/signin.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">Sign In</%block>
+<div id="signin">
+    <h2>
+        Sign In</h2>
+    <p>
+    Please enter your username and password.
+    <a href="${path_for('signup')}">Sign up</a>
+    if you don't have an account.
+    </p>
+    ${model.error()}
+    <form action="${path_for('signin')}" method="post">
+        ${xsrf()}
+        <div>
+            <fieldset>
+                <legend>Account Credentials</legend>
+                <p>
+                ${credential.username.label('Username:')}
+                ${credential.username.textbox(autocomplete='off')}
+                ${credential.username.error()}
+                </p>
+                <p>
+                ${credential.password.label('Password:')}
+                ${credential.password.password(autocomplete='off', maxlength='12')}
+                ${credential.password.error()}
+                </p>
+                <p>
+                ${model.remember_me.checkbox()}
+                ${model.remember_me.label('Remember me?', class_='inline')}
+                ${model.remember_me.error()}
+                </p>
+                <p>
+                <input type="submit" value="Sign In" />
+                </p>
+            </fieldset>
+            <i><b>demo</b> user password is P@ssw0rd. You can press 'Ctrl'
+                + '1' to auto complete information.</i>
+        </div>
+    </form>
+</div>
+<%block name="script">
+<script type="text/javascript">
+    $(document).ready(function() {
+        ajaxForm();
+    })
+</script>
+</%block>

demos/template/content/templates-mako/membership/signup.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">Sign Up</%block>
+<div id="signup">
+    <h2>
+        Create a New Account</h2>
+    <p>
+    Use the form below to create a new account.
+    </p>
+    <p>
+    Passwords are required to be a minimum of 8 characters
+    in length.
+    </p>
+    ${model.error()}
+    <form action="${path_for('signup')}" method="post">
+        ${resubmission()}
+        <div>
+            <fieldset>
+                <legend>Account Information</legend>
+                <p>
+                ${credential.username.label('Username:')}
+                ${credential.username.textbox(autocomplete='off')}
+                ${credential.username.error()}
+                </p>
+                <p>
+                ${account.display_name.label('Display name:')}
+                ${account.display_name.textbox(autocomplete='off')}
+                ${account.display_name.error()}
+                </p>
+                <p>
+                ${account.email.label('Email:')}
+                ${account.email.textbox(autocomplete='off')}
+                ${account.email.error()}
+                </p>
+                <p>
+                ${account.account_type.label('Account Type:')}
+                ${account.account_type.radio(choices=account_types)}
+                ${account.account_type.error()}
+                </p>
+                <p>
+                ${registration.date_of_birth.label(_('Date of birth (YYYY/MM/DD):'))}
+                ${registration.date_of_birth.format(_('YYYY/MM/DD')).textbox(autocomplete='off')}
+                ${registration.date_of_birth.error()}
+                </p>
+                <p>
+                ${credential.password.label('Password:')}
+                ${credential.password.password(autocomplete='off', maxlength='12')}
+                ${credential.password.error()}
+                </p>
+                <p>
+                ${model.confirm_password.label('Confirm password:')}
+                ${model.confirm_password.password(autocomplete='off', maxlength='12')}
+                ${model.confirm_password.error()}
+                </p>
+                <p>
+                ${model.questionid.label('Security question:')}
+                ${model.questionid.dropdown(choices=questions)}
+                ${model.questionid.error()}
+                </p>
+                <p>
+                ${registration.answer.label('Answer:')}
+                ${registration.answer.textbox(autocomplete='off', maxlength='30')}
+                ${registration.answer.error()}
+                </p>
+                <p>
+                <input type="submit" value="Register" />
+                </p>
+            </fieldset>
+        </div>
+    </form>
+</div>
+<%block name="script">
+<script type="text/javascript">
+    $(document).ready(function() {
+        ajaxForm();
+    })
+</script>
+</%block>

demos/template/content/templates-mako/public/about.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">About</%block>
+<div id="about">
+    <h2>About</h2>
+    <p>Proin at lorem vel dolor pharetra aliquam. Phasellus auctor,
+    nunc vitae lobortis pretium, mauris ligula sagittis quam, nec
+    volutpat est arcu a diam. Nunc nisi augue, sollicitudin eu
+    adipiscing ac, commodo at urna. Nullam sit amet odio sit amet
+    elit dictum ultrices.</p>
+    <p>Try out the following responses:
+    </p>
+    <ul>
+        <%block cached="True" cache_key="${route_args.locale}">
+        <li><a href="${path_for('http400')}">Bad Request</a></li>
+        <li><a href="${path_for('http403')}">Forbidden</a></li>
+        <li><a href="${path_for('http404')}">Not Found</a></li>
+        <li><a href="${path_for('http500')}">Internal Error</a></li>
+        </%block>
+    </ul>
+</div>

demos/template/content/templates-mako/public/home.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">Home</%block>
+<div id="welcome">
+    <h2>Welcome!</h2>
+    <p>
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Donec quis tempus tellus. Curabitur faucibus fermentum enim,
+    quis aliquet.
+    </p>
+</div>

demos/template/content/templates-mako/public/http400.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">Bad Request</%block>
+<div id="error">
+    <h2>Oops! Code 400. Sorry, we can't process your request.</h2>
+    <p>
+    The 400 Bad Request error is an HTTP status code that
+    means that the request you sent to the website server (i.e.
+    a request to load a web page) was somehow malformed therefore
+    the server was unable to understand or process the request.
+    </p>
+</div>

demos/template/content/templates-mako/public/http403.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">Access Denied</%block>
+<div id="error">
+    <h2>Oops! Code 403. Access is denied.</h2>
+    <p>
+    You do not have permission to view this directory or page
+    using the credentials that you supplied.
+    </p>
+</div>

demos/template/content/templates-mako/public/http404.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">We are sorry, the page you requested cannot
+be found.</%block>
+<div id="error">
+    <h2>Oops! Code 404. Sorry, we can't find that page.</h2>
+    <p>
+    Unfortunately the page you are looking for may have been
+    removed, had its name changed, under construction or is
+    temporarily unavailable. Try checking the web address for
+    typos, please. We apologize for the inconvenience.
+    </p>
+</div>

demos/template/content/templates-mako/public/http500.html

+<%inherit file="/shared/master.html"/>
+<%block name="title">We are sorry, we can not process your request.
+</%block>
+<div id="error">
+    <h2>Oops! Code 500. Sorry, we can not process your request.</h2>
+    <p>
+    The web server encountered an unexpected condition that
+    prevented it from fulfilling the request by the client for
+    access to the requested URL.
+    </p>
+</div>

demos/template/content/templates-mako/shared/master.html

+<%!
+from public import __version__
+%>\
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <title>My Site - <%block name="title"/></title>
+        <link href="${path_for('static', path='css/site.css')}"
+        type="text/css" rel="stylesheet" />
+    </head>
+    <body>
+        <div class="page">
+            <div id="header">
+                <div id="title">
+                    <h1><a href="${path_for('default')}">My Site</a></h1>
+                </div>
+                <div id="logindisplay">
+                    <%include file="snippet/menu-signin.html"/>
+                    <%include file="snippet/menu-locale.html"/>
+                </div>
+                <div id="menucontainer">
+                    <%include file="snippet/menu-header.html"/>
+                </div>
+            </div>
+            <div id="main">
+                <div id="placeholder">${self.body()}</div>
+                <div id="footer">Version ${__version__}</div>
+            </div>
+        </div>
+        <%include file="snippet/script.html"/>
+        <%block name="script"/>
+    </body>
+</html>

demos/template/content/templates-mako/shared/snippet/menu-header.html

+<%page cached="True" cache_key="${(route_args.route_name in \
+('home', 'about') and route_args.route_name or '') + route_args.locale}" />
+
+<%def name="menu_item(route_name, title)">
+% if route_name == route_args.route_name:
+    <li class="active">\
+% else:
+    <li>\
+% endif
+<a href="${path_for(route_name)}">${title}</a></li>\
+</%def>
+
+<ul id="menu">
+    ${menu_item('home', 'Home')}
+    ${menu_item('about', 'About')}
+</ul>

demos/template/content/templates-mako/shared/snippet/menu-locale.html

+<%page cached="True" cache_time="900"
+    cache_key="${route_args.route_name}" />
+<i><a href="${path_for(route_args.route_name, locale='en')}"
+        >English</a> | <a
+        href="${path_for(route_args.route_name, locale='ru')}"
+        >Russian</a></i>

demos/template/content/templates-mako/shared/snippet/menu-signin.html

+<%page cached="True" cache_time="900"
+    cache_key="${(principal and principal.alias or '') + route_args.locale}" />
+% if principal:
+Welcome <b>${principal.alias | h}</b>!
+[<a href="${path_for('signout')}">Sign out</a>]
+% else:
+[<a href="${path_for('signin')}">Sign in</a>]
+% endif

demos/template/content/templates-mako/shared/snippet/script.html

+<%page cached="True" />
+<script src="${path_for('static', path='js/jquery-1.7.1.min.js')}"
+    type="text/javascript"></script>
+<script src="${path_for('static', path='js/core.js')}"
+    type="text/javascript"></script>
+<script src="${path_for('static', path='js/autocomplete.js')}"
+    type="text/javascript"></script>

demos/template/content/templates-tenjin/membership/signin.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for t in capture_as("title"): ?>Sign In<?py #endfor ?>
+<div id="signin">
+    <h2>
+        Sign In</h2>
+    <p>
+    Please enter your username and password.
+    <a href="#{path_for('signup')}">Sign up</a>
+    if you don't have an account.
+    </p>
+    #{model.error()}
+    <form action="#{path_for('signin')}" method="post">
+        #{xsrf()}
+        <div>
+            <fieldset>
+                <legend>Account Credentials</legend>
+                <p>
+                #{credential.username.label('Username:')}
+                ${credential.username.textbox(autocomplete='off')}
+                #{credential.username.error()}
+                </p>
+                <p>
+                #{credential.password.label('Password:')}
+                ${credential.password.password(autocomplete='off', maxlength='12')}
+                #{credential.password.error()}
+                </p>
+                <p>
+                #{model.remember_me.checkbox()}
+                #{model.remember_me.label('Remember me?', class_='inline')}
+                #{model.remember_me.error()}
+                </p>
+                <p>
+                <input type="submit" value="Sign In" />
+                </p>
+            </fieldset>
+            <i><b>demo</b> user password is P@ssw0rd. You can press 'Ctrl'
+                + '1' to auto complete information.</i>
+        </div>
+    </form>
+</div>
+<?py for s in capture_as("script"): ?>
+<script type="text/javascript">
+    $(document).ready(function() {
+        ajaxForm();
+    })
+</script>
+<?py #endfor ?>

demos/template/content/templates-tenjin/membership/signup.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for t in capture_as("title"): ?>Sign Up<?py #endfor ?>
+<div id="signup">
+    <h2>
+        Create a New Account</h2>
+    <p>
+    Use the form below to create a new account.
+    </p>
+    <p>
+    Passwords are required to be a minimum of 8 characters
+    in length.
+    </p>
+    #{model.error()}
+    <form action="#{path_for('signup')}" method="post">
+        #{resubmission()}
+        <div>
+            <fieldset>
+                <legend>Account Information</legend>
+                <p>
+                #{credential.username.label('Username:')}
+                ${credential.username.textbox(autocomplete='off')}
+                #{credential.username.error()}
+                </p>
+                <p>
+                #{account.display_name.label('Display name:')}
+                ${account.display_name.textbox(autocomplete='off')}
+                #{account.display_name.error()}
+                </p>
+                <p>
+                #{account.email.label('Email:')}
+                ${account.email.textbox(autocomplete='off')}
+                #{account.email.error()}
+                </p>
+                <p>
+                #{account.account_type.label('Account Type:')}
+                #{account.account_type.radio(choices=account_types)}
+                #{account.account_type.error()}
+                </p>
+                <p>
+                #{registration.date_of_birth.label(_('Date of birth (YYYY/MM/DD):'))}
+                ${registration.date_of_birth.format(_('YYYY/MM/DD')).textbox(autocomplete='off')}
+                #{registration.date_of_birth.error()}
+                </p>
+                <p>
+                #{credential.password.label('Password:')}
+                ${credential.password.password(autocomplete='off', maxlength='12')}
+                #{credential.password.error()}
+                </p>
+                <p>
+                #{model.confirm_password.label('Confirm password:')}
+                ${model.confirm_password.password(autocomplete='off', maxlength='12')}
+                #{model.confirm_password.error()}
+                </p>
+                <p>
+                #{model.questionid.label('Security question:')}
+                #{model.questionid.dropdown(choices=questions)}
+                #{model.questionid.error()}
+                </p>
+                <p>
+                #{registration.answer.label('Answer:')}
+                ${registration.answer.textbox(autocomplete='off', maxlength='30')}
+                #{registration.answer.error()}
+                </p>
+                <p>
+                <input type="submit" value="Register" />
+                </p>
+            </fieldset>
+        </div>
+    </form>
+</div>
+<?py for s in capture_as("script"): ?>
+<script type="text/javascript">
+    $(document).ready(function() {
+        ajaxForm();
+    })
+</script>
+<?py #endfor ?>

demos/template/content/templates-tenjin/public/about.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for t in capture_as("title"): ?>About<?py #endfor ?>
+<div id="about">
+    <h2>About</h2>
+    <p>Proin at lorem vel dolor pharetra aliquam. Phasellus auctor,
+    nunc vitae lobortis pretium, mauris ligula sagittis quam, nec
+    volutpat est arcu a diam. Nunc nisi augue, sollicitudin eu
+    adipiscing ac, commodo at urna. Nullam sit amet odio sit amet
+    elit dictum ultrices.</p>
+    <p>Try out the following responses:
+    </p>
+    <ul>
+        <%block cached="True" cache_key="${route_args.locale}">
+        <li><a href="${path_for('http400')}">Bad Request</a></li>
+        <li><a href="${path_for('http403')}">Forbidden</a></li>
+        <li><a href="${path_for('http404')}">Not Found</a></li>
+        <li><a href="${path_for('http500')}">Internal Error</a></li>
+        </%block>
+    </ul>
+</div>

demos/template/content/templates-tenjin/public/home.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for t in capture_as("title"): ?>Home<?py #endfor ?>
+<div id="welcome">
+    <h2>Welcome!</h2>
+    <p>
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Donec quis tempus tellus. Curabitur faucibus fermentum enim,
+    quis aliquet.
+    </p>
+</div>

demos/template/content/templates-tenjin/public/http400.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for _ in capture_as("title"): ?>Bad Request<?py #endfor ?>
+<div id="error">
+    <h2>Oops! Code 400. Sorry, we can't process your request.</h2>
+    <p>
+    The 400 Bad Request error is an HTTP status code that
+    means that the request you sent to the website server (i.e.
+    a request to load a web page) was somehow malformed therefore
+    the server was unable to understand or process the request.
+    </p>
+</div>

demos/template/content/templates-tenjin/public/http403.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for _ in capture_as("title"): ?>Access Denied<?py #endfor ?>
+<div id="error">
+    <h2>Oops! Code 403. Access is denied.</h2>
+    <p>
+    You do not have permission to view this directory or page
+    using the credentials that you supplied.
+    </p>
+</div>

demos/template/content/templates-tenjin/public/http404.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for _ in capture_as("title"): ?>We are sorry, the page you
+requested cannot be found.<?py #endfor ?>
+<div id="error">
+    <h2>Oops! Code 404. Sorry, we can't find that page.</h2>
+    <p>
+    Unfortunately the page you are looking for may have been
+    removed, had its name changed, under construction or is
+    temporarily unavailable. Try checking the web address for
+    typos, please. We apologize for the inconvenience.
+    </p>
+</div>

demos/template/content/templates-tenjin/public/http500.html

+<?py _context["_layout"] = "shared/master.html" ?>
+<?py for _ in capture_as("title"): ?>We are sorry, we can not process your request.<?py #endfor ?>
+<div id="error">
+    <h2>Oops! Code 500. Sorry, we can not process your request.</h2>
+    <p>
+    The web server encountered an unexpected condition that
+    prevented it from fulfilling the request by the client for
+    access to the requested URL.
+    </p>
+</div>

demos/template/content/templates-tenjin/shared/master.html

+<?py from public import __version__ ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <title>My Site - <?py #pass ?>
+        <?py if not captured_as("title"): ?>
+        <?py pass ?>
+        <?py #endif ?></title>
+        <link href="#{path_for('static', path='css/site.css')}"
+        type="text/css" rel="stylesheet" />
+    </head>
+    <body>
+        <div class="page">
+            <div id="header">
+                <div id="title">
+                    <h1><a href="#{path_for('default')}">My Site</a></h1>
+                </div>
+                <div id="logindisplay">
+                    <?py include("shared/snippet/menu-signin.html") ?>
+                    <?py include("shared/snippet/menu-locale.html") ?>
+                </div>
+                <div id="menucontainer">
+                    <ul id="menu">
+                    <?py include('shared/snippet/menu-header.html', route_name='home', title='Home') ?>
+                    <?py include('shared/snippet/menu-header.html', route_name='about', title='About') ?>
+                    </ul>
+                </div>
+            </div>
+            <div id="main">
+                <div id="placeholder">#{_content}</div>
+                <div id="footer">Version #{__version__}</div>
+            </div>
+        </div>
+        <?py include("shared/snippet/script.html") ?>
+        <?py if not captured_as("script"): ?>
+        <?py pass ?>
+        <?py #endif ?>
+    </body>
+</html>

demos/template/content/templates-tenjin/shared/snippet/menu-header.html

+<?py #@ARGS route_name, title, route_args, path_for ?>
+<?py if route_name == route_args.route_name: ?>
+<li class="active"><a href="#{path_for(route_name)}">#{title}</a></li>
+<?py else: ?>
+<li><a href="#{path_for(route_name)}">#{title}</a></li>
+<?py #endif ?>

demos/template/content/templates-tenjin/shared/snippet/menu-locale.html

+<i><a href="#{path_for(route_args.route_name, locale='en')}"
+        >English</a> | <a
+        href="#{path_for(route_args.route_name, locale='ru')}"
+        >Russian</a></i>

demos/template/content/templates-tenjin/shared/snippet/menu-signin.html

+<?py if principal: ?>
+Welcome <b>${principal.alias}</b>!
+[<a href="#{path_for('signout')}">Sign out</a>]
+<?py else: ?>
+[<a href="#{path_for('signin')}">Sign in</a>]
+<?py #endif ?>

demos/template/content/templates-tenjin/shared/snippet/script.html

+<script src="#{path_for('static', path='js/jquery-1.7.1.min.js')}"
+    type="text/javascript"></script>
+<script src="#{path_for('static', path='js/core.js')}"
+    type="text/javascript"></script>
+<script src="#{path_for('static', path='js/autocomplete.js')}"
+    type="text/javascript"></script>

demos/template/content/templates/membership/signin.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">Sign In</%block>
-<div id="signin">
-    <h2>
-        Sign In</h2>
-    <p>
-    Please enter your username and password.
-    <a href="${path_for('signup')}">Sign up</a>
-    if you don't have an account.
-    </p>
-    ${model.error()}
-    <form action="${path_for('signin')}" method="post">
-        ${xsrf()}
-        <div>
-            <fieldset>
-                <legend>Account Credentials</legend>
-                <p>
-                ${credential.username.label('Username:')}
-                ${credential.username.textbox(autocomplete='off')}
-                ${credential.username.error()}
-                </p>
-                <p>
-                ${credential.password.label('Password:')}
-                ${credential.password.password(autocomplete='off', maxlength='12')}
-                ${credential.password.error()}
-                </p>
-                <p>
-                ${model.remember_me.checkbox()}
-                ${model.remember_me.label('Remember me?', class_='inline')}
-                ${model.remember_me.error()}
-                </p>
-                <p>
-                <input type="submit" value="Sign In" />
-                </p>
-            </fieldset>
-            <i><b>demo</b> user password is P@ssw0rd. You can press 'Ctrl'
-                + '1' to auto complete information.</i>
-        </div>
-    </form>
-</div>
-<%block name="script">
-<script type="text/javascript">
-    $(document).ready(function() {
-        ajaxForm();
-    })
-</script>
-</%block>

demos/template/content/templates/membership/signup.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">Sign Up</%block>
-<div id="signup">
-    <h2>
-        Create a New Account</h2>
-    <p>
-    Use the form below to create a new account.
-    </p>
-    <p>
-    Passwords are required to be a minimum of 8 characters
-    in length.
-    </p>
-    ${model.error()}
-    <form action="${path_for('signup')}" method="post">
-        ${resubmission()}
-        <div>
-            <fieldset>
-                <legend>Account Information</legend>
-                <p>
-                ${credential.username.label('Username:')}
-                ${credential.username.textbox(autocomplete='off')}
-                ${credential.username.error()}
-                </p>
-                <p>
-                ${account.display_name.label('Display name:')}
-                ${account.display_name.textbox(autocomplete='off')}
-                ${account.display_name.error()}
-                </p>
-                <p>
-                ${account.email.label('Email:')}
-                ${account.email.textbox(autocomplete='off')}
-                ${account.email.error()}
-                </p>
-                <p>
-                ${account.account_type.label('Account Type:')}
-                ${account.account_type.radio(choices=account_types)}
-                ${account.account_type.error()}
-                </p>
-                <p>
-                ${registration.date_of_birth.label(_('Date of birth (YYYY/MM/DD):'))}
-                ${registration.date_of_birth.format(_('YYYY/MM/DD')).textbox(autocomplete='off')}
-                ${registration.date_of_birth.error()}
-                </p>
-                <p>
-                ${credential.password.label('Password:')}
-                ${credential.password.password(autocomplete='off', maxlength='12')}
-                ${credential.password.error()}
-                </p>
-                <p>
-                ${model.confirm_password.label('Confirm password:')}
-                ${model.confirm_password.password(autocomplete='off', maxlength='12')}
-                ${model.confirm_password.error()}
-                </p>
-                <p>
-                ${model.questionid.label('Security question:')}
-                ${model.questionid.dropdown(choices=questions)}
-                ${model.questionid.error()}
-                </p>
-                <p>
-                ${registration.answer.label('Answer:')}
-                ${registration.answer.textbox(autocomplete='off', maxlength='30')}
-                ${registration.answer.error()}
-                </p>
-                <p>
-                <input type="submit" value="Register" />
-                </p>
-            </fieldset>
-        </div>
-    </form>
-</div>
-<%block name="script">
-<script type="text/javascript">
-    $(document).ready(function() {
-        ajaxForm();
-    })
-</script>
-</%block>

demos/template/content/templates/public/about.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">About</%block>
-<div id="about">
-    <h2>About</h2>
-    <p>Proin at lorem vel dolor pharetra aliquam. Phasellus auctor,
-    nunc vitae lobortis pretium, mauris ligula sagittis quam, nec
-    volutpat est arcu a diam. Nunc nisi augue, sollicitudin eu
-    adipiscing ac, commodo at urna. Nullam sit amet odio sit amet
-    elit dictum ultrices.</p>
-    <p>Try out the following responses:
-    </p>
-    <ul>
-        <%block cached="True" cache_key="${route_args.locale}">
-        <li><a href="${path_for('http400')}">Bad Request</a></li>
-        <li><a href="${path_for('http403')}">Forbidden</a></li>
-        <li><a href="${path_for('http404')}">Not Found</a></li>
-        <li><a href="${path_for('http500')}">Internal Error</a></li>
-        </%block>
-    </ul>
-</div>

demos/template/content/templates/public/home.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">Home</%block>
-<div id="welcome">
-    <h2>Welcome!</h2>
-    <p>
-    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-    Donec quis tempus tellus. Curabitur faucibus fermentum enim,
-    quis aliquet.
-    </p>
-</div>

demos/template/content/templates/public/http400.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">Bad Request</%block>
-<div id="error">
-    <h2>Oops! Code 400. Sorry, we can't process your request.</h2>
-    <p>
-    The 400 Bad Request error is an HTTP status code that
-    means that the request you sent to the website server (i.e.
-    a request to load a web page) was somehow malformed therefore
-    the server was unable to understand or process the request.
-    </p>
-</div>

demos/template/content/templates/public/http403.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">Access Denied</%block>
-<div id="error">
-    <h2>Oops! Code 403. Access is denied.</h2>
-    <p>
-    You do not have permission to view this directory or page
-    using the credentials that you supplied.
-    </p>
-</div>

demos/template/content/templates/public/http404.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">We are sorry, the page you requested cannot
-be found.</%block>
-<div id="error">
-    <h2>Oops! Code 404. Sorry, we can't find that page.</h2>
-    <p>
-    Unfortunately the page you are looking for may have been
-    removed, had its name changed, under construction or is
-    temporarily unavailable. Try checking the web address for
-    typos, please. We apologize for the inconvenience.
-    </p>
-</div>

demos/template/content/templates/public/http500.html

-<%inherit file="/shared/master.html"/>
-<%block name="title">We are sorry, we can not process your request.
-</%block>
-<div id="error">
-    <h2>Oops! Code 500. Sorry, we can not process your request.</h2>
-    <p>
-    The web server encountered an unexpected condition that
-    prevented it from fulfilling the request by the client for
-    access to the requested URL.
-    </p>
-</div>

demos/template/content/templates/shared/master.html

-<%!
-from public import __version__
-%>\
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-    <head>
-        <title>My Site - <%block name="title"/></title>
-        <link href="${path_for('static', path='css/site.css')}"
-        type="text/css" rel="stylesheet" />
-    </head>
-    <body>
-        <div class="page">
-            <div id="header">
-                <div id="title">
-                    <h1><a href="${path_for('default')}">My Site</a></h1>
-                </div>
-                <div id="logindisplay">
-                    <%include file="snippet/menu-signin.html"/>
-                    <%include file="snippet/menu-locale.html"/>
-                </div>
-                <div id="menucontainer">
-                    <%include file="snippet/menu-header.html"/>
-                </div>
-            </div>
-            <div id="main">
-                <div id="placeholder">${self.body()}</div>
-                <div id="footer">Version ${__version__}</div>
-            </div>
-        </div>
-        <%include file="snippet/script.html"/>
-        <%block name="script"/>
-    </body>
-</html>

demos/template/content/templates/shared/snippet/menu-header.html

-<%page cached="True" cache_key="${(route_args.route_name in \
-('home', 'about') and route_args.route_name or '') + route_args.locale}" />
-
-<%def name="menu_item(route_name, title)">
-% if route_name == route_args.route_name:
-    <li class="active">\
-% else:
-    <li>\
-% endif
-<a href="${path_for(route_name)}">${title}</a></li>\
-</%def>
-
-<ul id="menu">
-    ${menu_item('home', 'Home')}
-    ${menu_item('about', 'About')}
-</ul>

demos/template/content/templates/shared/snippet/menu-locale.html

-<%page cached="True" cache_time="900"
-    cache_key="${route_args.route_name}" />
-<i><a href="${path_for(route_args.route_name, locale='en')}"
-        >English</a> | <a
-        href="${path_for(route_args.route_name, locale='ru')}"
-        >Russian</a></i>

demos/template/content/templates/shared/snippet/menu-signin.html

-<%page cached="True" cache_time="900"
-    cache_key="${(principal and principal.alias or '') + route_args.locale}" />
-% if principal:
-Welcome <b>${principal.alias | h}</b>!
-[<a href="${path_for('signout')}">Sign out</a>]
-% else:
-[<a href="${path_for('signin')}">Sign in</a>]
-% endif

demos/template/content/templates/shared/snippet/script.html

-<%page cached="True" />
-<script src="${path_for('static', path='js/jquery-1.7.1.min.js')}"
-    type="text/javascript"></script>
-<script src="${path_for('static', path='js/core.js')}"
-    type="text/javascript"></script>
-<script src="${path_for('static', path='js/autocomplete.js')}"
-    type="text/javascript"></script>

demos/template/development.ini

 virtualenv = ../../env
 pythonpath = src
 disable-logging = True
-logto = /dev/null
+#logto = /dev/null
 no-default-app = True
 auto-procname = True
 procname-prefix = MySite-
 limit-post = 1024
 idle = 300
 #python-auto-reload = 2
-lazy = True
+#lazy = True
 static-map = /static=content/static
 static-map = /favicon.ico=content/static/img/favicon.ico

demos/template/i18n/en/LC_MESSAGES/membership.po

 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+#: content/templates-mako/membership/signup.html:40
 #: content/templates/membership/signup.html:40
 msgid "Date of birth (YYYY/MM/DD):"
 msgstr "Date of birth (YYYY/MM/DD):"
 
+#: content/templates-mako/membership/signup.html:41
+#: content/templates-tenjin/membership/signup.html:41
 #: content/templates/membership/signup.html:41
 msgid "YYYY/MM/DD"
 msgstr "%Y/%m/%d"

demos/template/i18n/membership.po

 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+#: content/templates-mako/membership/signup.html:40
 #: content/templates/membership/signup.html:40
 msgid "Date of birth (YYYY/MM/DD):"
 msgstr "Date of birth (YYYY/MM/DD):"
 
+#: content/templates-mako/membership/signup.html:41
+#: content/templates-tenjin/membership/signup.html:41
 #: content/templates/membership/signup.html:41
 msgid "YYYY/MM/DD"
 msgstr "%Y/%m/%d"

demos/template/setup.py

 install_requires = [
     'wheezy.core>=0.1.70',
     'wheezy.caching>=0.1.54',
-    'wheezy.html>=0.1.68',
+    'wheezy.html>=0.1.86',
     'wheezy.http>=0.1.223',
     'wheezy.routing>=0.1.124',
     'wheezy.security>=0.1.35',
     'wheezy.validation>=0.1.72',
     'wheezy.web>=0.1.207',
-    'mako>=0.6.2'
 ]
 
 try:
             'pytest',
             'pytest-pep8',
             'pytest-cov'
+        ],
+        'mako': [
+            'mako>=0.7.0'
+        ],
+        'tenjin': [
+            'tenjin>=1.1.0'
         ]
     },
 

demos/template/src/config.py

 """
 """
 
+import os
+
 from ConfigParser import ConfigParser
 from datetime import timedelta
 
 from wheezy.caching import MemoryCache
 from wheezy.core.collections import defaultdict
 from wheezy.core.i18n import TranslationsManager
-from wheezy.html.ext.mako import widget_preprocessor
-from wheezy.html.ext.mako import whitespace_preprocessor
 from wheezy.http import CacheProfile
 from wheezy.security.crypto import Ticket
 from wheezy.security.crypto.comp import aes128
 from wheezy.security.crypto.comp import ripemd160
 from wheezy.security.crypto.comp import sha1
 from wheezy.security.crypto.comp import sha256
-from wheezy.web.templates import MakoTemplate
 
 from membership.repository.mock import MembershipRepository
 
         'CRYPTO_VALIDATION_KEY': config.get('crypto', 'validation-key')
 })
 
+#template_engine = os.getenv('TEMPLATE_ENGINE', 'mako')
+template_engine = os.getenv('TEMPLATE_ENGINE', 'tenjin')
+if template_engine == 'mako':
+    from wheezy.html.ext.mako import whitespace_preprocessor
+    from wheezy.html.ext.mako import widget_preprocessor
+    from wheezy.web.templates import MakoTemplate
+
+    render_template = MakoTemplate(
+            module_directory=config.get('mako', 'module-directory'),
+            filesystem_checks=config.getboolean('mako', 'filesystem-checks'),
+            directories=['content/templates-mako'],
+            cache_factory=cache_factory,
+            preprocessor=[
+                widget_preprocessor,
+                whitespace_preprocessor,
+            ])
+else:
+    from wheezy.html.ext.tenjin import whitespace_preprocessor
+    from wheezy.html.ext.tenjin import widget_preprocessor
+    from wheezy.html.utils import format_value
+    from wheezy.web.templates import TenjinTemplate
+
+    render_template = TenjinTemplate(
+            path=['content/templates-tenjin'],
+            pp=[
+                widget_preprocessor,
+                whitespace_preprocessor,
+            ],
+            helpers={
+                'format_value': format_value
+            })
+
 # BaseHandler
 options.update({
         'translations_manager': TranslationsManager(
             directories=['i18n'],
             default_lang='en'),
 
-        'render_template': MakoTemplate(
-            module_directory=config.get('mako', 'module-directory'),
-            filesystem_checks=config.getboolean('mako', 'filesystem-checks'),
-            directories=['content/templates'],
-            cache_factory=cache_factory,
-            preprocessor=[
-                widget_preprocessor,
-                whitespace_preprocessor,
-            ]),
+        'render_template': render_template,
 
         'ticket': Ticket(
             max_age=config.getint('crypto', 'ticket-max-age'),

src/wheezy/web/templates.py

 
     def invalidate(self, key, **kw):
         raise NotImplementedError()
+
+
+class TenjinTemplate(object):
+    __slots__ = ('engine', 'helpers')
+
+    def __init__(self, path=None, pp=None, helpers=None,
+            encoding='UTF-8', postfix='.html', cache=None,
+            **kwargs):
+        import tenjin
+        tenjin.set_template_encoding(encoding)
+        from tenjin.helpers import capture_as, escape,\
+                captured_as, cache_as
+        self.helpers = {
+                'to_str': unicode,
+                'escape': escape,
+                'capture_as': capture_as,
+                'captured_as': captured_as,
+                'cache_as': cache_as,
+        }
+        if helpers:
+            self.helpers.update(helpers)
+        self.engine = tenjin.Engine(
+                path=path or ['content/templates'],
+                postfix=postfix,
+                cache=cache or tenjin.MemoryCacheStorage(),
+                pp=pp,
+                **kwargs)
+
+    def __call__(self, template_name, **kwargs):
+        return self.engine.render(template_name, kwargs, self.helpers)