How to load jinja template directly from filesystem - python

The jinja API document at pocoo.org states:
The simplest way to configure Jinja2 to load templates for your application looks roughly like this:
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('yourapplication', templates'))
This will create a template environment with the default settings and a loader that looks up the templates in the templates folder inside the yourapplication python package.
As it turns out, this isn't so simple because you have to make/install a python package with your templates in it, which introduces a lot of needless complexity, especially if you have no intention of distributing your code.
I found these related questions about doing so, but the answers are vague and unsatisfying:
need to package jinja2 template for python
How to make a python package containing only jinja templates
How can I load the template directly from the filesystem, not as a resource in a package?

Use a FileSystemLoader instead of a PackageLoader. Suppose there is a python file in the same directory as the template:
./index.py
./template.html
This index.py will find the template and render it:
#!/usr/bin/python
import jinja2
templateLoader = jinja2.FileSystemLoader(searchpath="./")
templateEnv = jinja2.Environment(loader=templateLoader)
TEMPLATE_FILE = "template.html"
template = templateEnv.get_template(TEMPLATE_FILE)
outputText = template.render() # this is where to put args to the template renderer
print(outputText)
In the introduction, the PackageLoader approach seems to be presented as the default, "simplest" method; however, there is also a section which discusses all the built-in loaders.

A simpler way is to directly call the jinja2.Template constructor and use open to load the file:
from jinja2 import Template
with open('template.html.jinja2') as file_:
template = Template(file_.read())
template.render(name='John')

Here is the one liner:
from jinja2 import Template
with open('template_file.j2') as f:
template = Template(f.read())
Then you can render the template on another line, or for all in one line:
with open('template_file.j2') as f:
rendered = Template(f.read()).render(var="TEXT")

If using Python 3.4+ and Jinja2 - v2.11+ -- we can combine python's pathlib and Filesystem to simplify the flow
from pathlib import Path
...
p = Path(__file__).parent.parent / 'templates' # sample relative path
env = Environment(
loader=FileSystemLoader(Path(p)))
template = env.get_template('your_file.jinja2')
I am not comfortable with using directly Template(file) since Jinja's template inheritance processing may not work well.
Pathlib support is only added in latest version of Jinja - v2.11+

from jinja2 import Environment, select_autoescape, FileSystemLoader
env = Environment(loader=FileSystemLoader(
searchpath=folder_contain_list_html), autoescape=select_autoescape(['html', 'xml']))
template = env.get_template('file_name_detail_template')
body_html = template.render(**args)
send_email(body_html)

Related

Problems using jinja2 for code generation when using "include" to subsection contents

I am using an initial python script to pull together a json file and a .j2 (jinja2) file which outputs an executable second python script on runtime. The first python script works in the following way:
import json
from jinja2 import Template
json_file = "dag-input.json"
interface_file = "dag-template2.j2"
with open(interface_file) as template_f:
interface_template = Template(template_f.read())
with open(json_file) as json_f:
reader = json.load(json_f)
interface_config = interface_template.render(
**reader
)
with open("output.py", "w") as python_f:
python_f.write(interface_config)
Since my jinja template is very long. I would like to break it down into subsections, much like I have done before when creating HTML pages, using jinja 2 such like:
{% include 'task-templates/import.j2'%}
On runtime I get the following error. My question is: what additional configuration do I need to be able to use the jinja2 include feature in my case?
**File "<template>", line 1, in top-level template code
TypeError: no loader for this environment specified**
As additional information, the error points to the following block of code as the initial source of the error:
interface_config = interface_template.render(
**reader
)
The solution to this is that unless you are using a tool like Flask, the configuration of the environment needs to be done manually.
Essentially you can only access the templates that you wish to use with jinja2 include tags {% include "my-template.j2" %} if you have explicitly told the script where to look on render.
To do this add the following to the script:
from jinja2 import Template, Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('dag-template.j2')
In such a way you are setting the root file as "." and the initial template to consider. From here you may use the include tags {% include "my-template.j2" %} within that initial template as you wish

How to use a Jinja2 extension direct from a Flask render_template

I'm working on a Flask app that is working just fine, until I try to add the following line to a template to be rendered:
{% do totals.update({tier: 0}) %}
The current code for rendering the template uses Flask's render_template():
from flask import Flask, Response, request, session
from flask import render_template
app = Flask(__name__)
..
return render_template(<template.htlm>,...)
This fails with with following error:
TemplateSyntaxError: Encountered unknown tag 'do'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
The obvious fix is to add the jinja2.ext.do extension to jinja. I have been able to do so successfully using Jinja2 directly, as per:
from jinja2 import Environment, PackageLoader
ENV = Environment(loader=PackageLoader('ticket_app', 'templates'), extensions=['jinja2.ext.do'])
...
TEMP = ENV.get_template('div_' + div_id + '.html')
return TEMP.render(sales_new=sales_new, event_config=event_config)
However, I would prefer to not use Jinja2 directly... The app was only using Flask and render_template() before, and as render_template() uses Jinja2 under the hood (as far as I understand) it seems that it should be possible to make render_template() understand the jinja2.ext.do extension (or any other extension for that matter).
So far, I've tried the following:
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['EXPLAIN_TEMPLATE_LOADING'] = True
env = app.jinja_env
env.add_extension('jinja2.ext.do')
While the above does not throw an error, it is also not causing it to make render_template() understand the jinja2.ext.do extension.
Any suggestions? Should this be possible? If so, how?
Update for Flask v.2.0 :
Since v.2.0, the below solution raise a KeyError: 'extensions' exception. Try this instead.
For Flask v.1.1, you can directly access to the Jinja extensions loaded by Flask with the Flask.jinja_options dictionary. In your case, adding just this line should do the trick:
app = Flask(__name__)
app.jinja_options['extensions'].append('jinja2.ext.do')
Make sure you update your Flask (using pip: pip install -U Flask).
I actually found that the option that Zatiranyk outlined broke sometimes (specifically when I tried to use Flask-SocketIO.
app.jinja_options['extensions'].append('jinja2.ext.do')
The solution that I found was to use the below option instead:
app.jinja_env.add_extension('jinja2.ext.do')

Using Python CGI for a big HTML file

I have a big html file named exercise.html, I need generate to one som stuff by Python CGI.
I want to ask you what is the best way to print this HTML.
I know that it is possible by print method with using format methods %s, %i etc.:
print '''<html>
<head><title>My first Python CGI app</title></head>
<body>
<p>Hello, 'world'!</p>
.
.
<div>%s</div>
.
.
</body>
</html>''' % generated_text
But this HTML is really big,so is this only one solution?
You should consider using a templating language like Jinja2.
Here is a simple example straight from the link above:
>>> from jinja2 import Template
>>> template = Template('Hello {{ name }}!')
>>> template.render(name='John Doe')
Generally, though you save templates in a file, and then load / process them:
from jinja2 import Environment, PackageLoader
# The env object below finds templates that are lcated in the `templates`
# directory of your `yourapplication` package.
env = Environment(loader=PackageLoader('yourapplication', 'templates'))
template = env.get_template('mytemplate.html')
print template.render(the='variables', go='here')
As demonstrated above, templates let you put variables into the template. Placing text inside {{ }} makes it a template variable. When you render the template, pass in the variable value with a keyword argument. For instance, the template below has a name variable that we pass via template.render
This is my {{name}}.
template.render(name='Jaime')
Also consider Python Bottle (SimpleTemplate Engine). It is worth noting that bottle.py supports mako, jinja2 and cheetah templates.
The % indicates python code and the {{var}} are the substitution variables
HTML:
<ul>
% for item in basket:
<li>{{item}}</li>
% end
</ul>
Python:
with open('index.html', 'r') as htmlFile:
return bottle.template(htmlFile.read(), basket=['item1', 'item2'])

Changing the root directory Python and mod_wsgi

I started using Mako templates engine on my localhost. However, everytime I want to select template, I need to enter full location of template (ex. c:/Users/username/Desktop/xampp/htdocs/blog/scripts/templates/index.html'). I want to change it, for example to enter just 'scripts/templates/index.html' or similar each time.
Any suggestions how to do that?
Use mako's TemplateLookup class. For your example, using the sample in the documentation:
from mako.template import Template
from mako.lookup import TemplateLookup
template_path = "c:/Users/username/Desktop/xampp/htdocs/blog"
mylookup = TemplateLookup(directories=[path])
def serve_template(templatename, **kwargs):
templatename = "scripts/templates/index.html"
mytemplate = mylookup.get_template(templatename)
print mytemplate.render(**kwargs)

Using sass with Flask and jinja2

I would like to include a sass compiler in my Flask application. Is there a generally accepted way of doing this?
Flask-Assets extension (which uses webassets library) can be used for that. Here's how to configure it to use pyScss compiler (implemented in Python) for SCSS:
from flask import Flask, render_template
from flask.ext.assets import Environment, Bundle
app = Flask(__name__)
assets = Environment(app)
assets.url = app.static_url_path
scss = Bundle('foo.scss', 'bar.scss', filters='pyscss', output='all.css')
assets.register('scss_all', scss)
And in the template include this:
{% assets "scss_all" %}
<link rel=stylesheet type=text/css href="{{ ASSET_URL }}">
{% endassets %}
SCSS files will be compiled in debug mode as well.
pyScss only supports SCSS syntax, but there are other filters (sass, scss and compass) which use the original Ruby implementation.
Some things have changed since the question was answered in 2013.
You can't have scss installed at the same time as pyscss and expect the pyscss filter to work like in the accepted answer.
scss = Bundle('foo.scss', 'bar.scss', filters='pyscss', output='all.css')
I was getting an error that ended in:
File "/home/sri/crap/example/flask/lib/python2.7/site-packages/webassets/filter/pyscss.py", line 110, in setup
scss.config.STATIC_ROOT = self.static_root or self.ctx.directory
You have to remove scss (i.e. pip uninstall scss) and be sure that pyscss is installed (i.e. pip install pyscss).
Also note that you will have to set some environment variables in order to get pyscss to work as well:
app = Flask(__name__)
assets = Environment(app)
assets.url = app.static_url_path
scss = Bundle('index.scss', filters='pyscss', output='all.css')
assets.config['SECRET_KEY'] = 'secret!'
assets.config['PYSCSS_LOAD_PATHS'] = assets.load_path
assets.config['PYSCSS_STATIC_URL'] = assets.url
assets.config['PYSCSS_STATIC_ROOT'] = assets.directory
assets.config['PYSCSS_ASSETS_URL'] = assets.url
assets.config['PYSCSS_ASSETS_ROOT'] = assets.directory
assets.register('scss_all', scss)
see the documentation on the pyscss filter for more info: http://webassets.readthedocs.io/en/latest/builtin_filters.html#pyscss
I hope that this save someone else a lot of time because I wasted a whole day on it.
I think the most pythonic approach is this one line solution using libsass. After you import sass simply use the compile method with the dirname keyword argument, like this:
sass.compile(dirname=('path/to/sass', 'path/to/css'))
You also have the option to set the output style, for example:
sass.compile(dirname=('path/to/sass', 'path/to/css'), output_style='compressed')
If you want to watch a file or directory for automatic compilation on every edit use boussole.
Currently, exist a better approach for this issue, the extion Flask-Scss.
You just have to install it: pip install Flask-Scss
And instanciate a Scss object after configuring the application (probably in your manage.py file):
from flask import Flask
from flask.ext.scss import Scss
app = Flask(__name__)
Scss(app)
By default, the extension will look for your .scss files in {app.root_dir}/assets/scss or {app.root_dir}/assets and will put the generate .css files in {default_static_dir}/css or {default_static_dir}.
Libsass is a good solution for this.
# Your __init__.py file
from flask import Flask
from sassutils.wsgi import SassMiddleware
app = Flask(__name__)
app.wsgi_app = SassMiddleware(app.wsgi_app, {
'myapp': ('static/sass', 'static/css', '/static/css')
})
# Your HTML (Jinja) file
<link href="{{ url_for('static', filename='css/style.scss.css') }}"
rel="stylesheet" type="text/css">
More info:
https://sass.github.io/libsass-python/index.html

Categories

Resources