Jinja2 - pass variable to render - python

I'm trying to define a variable named "body" and then pass it to the render statement for a Jinja2 template. If I define the variable on a separate line and then use the render statement with just the variable name, I get an error.
Example code:
from jinja2 import Environment, FileSystemLoader
import os
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(THIS_DIR),
trim_blocks=True)
template = env.get_template('test_template.html')
body = "test"
html_str = template.render(body)
Error for the above code:
ValueError: dictionary update sequence element #0 has length 1; 2 is required
It works if I define the variable within the parenthesis of the render statement.
This works but it isn't what I want to do:
from jinja2 import Environment, FileSystemLoader
import os
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(THIS_DIR),
trim_blocks=True)
template = env.get_template('test_template.html')
html_str = template.render(body="test")

You should use
body = {"body":"test"}
html_str = template.render(body)

You can use:
html_str = template(body=body)
The bodyleft to the equal sign is the name of the variable as it will be called inside the template. The bodyon the right of the equal sign is the name of the variable inside the python code

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

Flask: How to use url_for() outside the app context?

I'm writing a script to collect the emails of those users that didn't receive an email confirmation email and resend it to them. The script works obviously outside of flask app context. I would like to use url_for() but can't get it right.
def resend(self, csv_path):
self.ctx.push()
with open(csv_path) as csv_file:
csv_reader = csv.reader(csv_file)
for row in csv_reader:
email = row[0]
url_token = AccountAdmin.generate_confirmation_token(email)
confirm_url = url_for('confirm_email', token=url_token, _external=True)
...
self.ctx.pop()
The first thing I had to do was to set SERVER_NAME in config. But then I get this error message:
werkzeug.routing.BuildError: Could not build url for endpoint
'confirm_email' with values ['token']. Did you mean 'static' instead?
This is how it's defined, but I don't think it can even find this, because it's not registered when ran as script:
app.add_url_rule('/v5/confirm_email/<token>', view_func=ConfirmEmailV5.as_view('confirm_email'))
Is there a way to salvage url_for() or do I have to build my own url?
Thanks
It is much easier and proper to get the URL from the application context.
You can either import the application and manually push context with app_context
https://flask.palletsprojects.com/en/2.0.x/appcontext/#manually-push-a-context
from flask import url_for
from whereyoudefineapp import application
application.config['SERVER_NAME'] = 'example.org'
with application.app_context():
url_for('yourblueprint.yourpage')
Or you can redefine your application and register the wanted blueprint.
from flask import Flask, url_for
from whereyoudefineyourblueprint import myblueprint
application = Flask(__name__)
application.config['SERVER_NAME'] = 'example.org'
application.register_blueprint(myblueprint)
with application.app_context():
url_for('myblueprint.mypage')
We can also imagine different ways to do it without the application, but I don't see any adequate / proper solution.
Despite everything, I will still suggest this dirty solution.
Let's say you have the following blueprint with the following routes inside routes.py.
from flask import Blueprint
frontend = Blueprint('frontend', __name__)
#frontend.route('/mypage')
def mypage():
return 'Hello'
#frontend.route('/some/other/page')
def someotherpage():
return 'Hi'
#frontend.route('/wow/<a>')
def wow(a):
return f'Hi {a}'
You could use the library inspect to get the source code and then parse it in order to build the URL.
import inspect
import re
BASE_URL = "https://example.org"
class FailToGetUrlException(Exception):
pass
def get_url(function, complete_url=True):
source = inspect.getsource(function)
lines = source.split("\n")
for line in lines:
r = re.match(r'^\#[a-zA-Z]+\.route\((["\'])([^\'"]+)\1', line)
if r:
if complete_url:
return BASE_URL + r.group(2)
else:
return r.group(2)
raise FailToGetUrlException
from routes import *
print(get_url(mypage))
print(get_url(someotherpage))
print(get_url(wow).replace('<a>', '456'))
Output:
https://example.org/mypage
https://example.org/some/other/page
https://example.org/wow/456

How to load jinja template directly from filesystem

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)

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)

How do you use FCKEditor's image upload and browser with mod-wsgi?

I am using FCKEditor within a Django app served by Apache/mod-wsgi. I don't want to install php just for FCKEditor andI see FCKEditor offers image uploading and image browsing through Python. I just haven't found good instructions on how to set this all up.
So currently Django is running through a wsgi interface using this setup:
import os, sys
DIRNAME = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-3])
sys.path.append(DIRNAME)
os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
In fckeditor in the editor->filemanager->connectors->py directory there is a file called wsgi.py:
from connector import FCKeditorConnector
from upload import FCKeditorQuickUpload
import cgitb
from cStringIO import StringIO
# Running from WSGI capable server (recomended)
def App(environ, start_response):
"WSGI entry point. Run the connector"
if environ['SCRIPT_NAME'].endswith("connector.py"):
conn = FCKeditorConnector(environ)
elif environ['SCRIPT_NAME'].endswith("upload.py"):
conn = FCKeditorQuickUpload(environ)
else:
start_response ("200 Ok", [('Content-Type','text/html')])
yield "Unknown page requested: "
yield environ['SCRIPT_NAME']
return
try:
# run the connector
data = conn.doResponse()
# Start WSGI response:
start_response ("200 Ok", conn.headers)
# Send response text
yield data
except:
start_response("500 Internal Server Error",[("Content-type","text/html")])
file = StringIO()
cgitb.Hook(file = file).handle()
yield file.getvalue()
I need these two things two work together by means of modifying my django wsgi file to serve the fckeditor parts correctly or make apache serve both django and fckeditor correctly on a single domain.
This describes how to embed the FCK editor and enable image uploading.
First you need to edit fckconfig.js to change the image upload
URL to point to some URL inside your server.
FCKConfig.ImageUploadURL = "/myapp/root/imageUploader";
This will point to the server relative URL to receive the upload.
FCK will send the uploaded file to that handler using the CGI variable
name "NewFile" encoded using multipart/form-data. Unfortunately you
will have to implement /myapp/root/imageUploader, because I don't think
the FCK distribution stuff can be easily adapted to other frameworks.
The imageUploader should extract the NewFile and store it
somewhere on the server.
The response generated by /myapp/root/imageUploader should emulate
the HTML constructed in /editor/.../fckoutput.py.
Something like this (whiff template format)
{{env
whiff.content_type: "text/html",
whiff.headers: [
["Expires","Mon, 26 Jul 1997 05:00:00 GMT"],
["Cache-Control","no-store, no-cache, must-revalidate"],
["Cache-Control","post-check=0, pre-check=0"],
["Pragma","no-cache"]
]
/}}
<script>
//alert("!! RESPONSE RECIEVED");
errorNumber = 0;
fileUrl = "fileurl.png";
fileName = "filename.png";
customMsg = "";
window.parent.OnUploadCompleted(errorNumber, fileUrl, fileName, customMsg);
</script>
The {{env ...}} stuff at the top indicate the content type and
recommended HTTP headers to send. The fileUrl should be the Url to
use to find the image on the server.
Here are the basic steps to get the html fragment which
generates the FCK editor widget. The only tricky part is you have to put the
right client indentification into the os.environ -- it's ugly
but that's the way the FCK library works right now (I filed a bug
report).
import fckeditor # you must have the fck editor python support installed to use this module
import os
inputName = "myInputName" # the name to use for the input element in the form
basePath = "/server/relative/path/to/fck/installation/" # the location of FCK static files
if basePath[-1:]!="/":
basePath+="/" # basepath must end in slash
oFCKeditor = fckeditor.FCKeditor(inputName)
oFCKeditor.BasePath = basePath
oFCKeditor.Height = 300 # the height in pixels of the editor
oFCKeditor.Value = "<h1>initial html to be editted</h1>"
os.environ["HTTP_USER_AGENT"] = "Mozilla/5.0 (Macintosh; U;..." # or whatever
# there must be some way to figure out the user agent in Django right?
htmlOut = oFCKeditor.Create()
# insert htmlOut into your page where you want the editor to appear
return htmlOut
The above is untested, but it's based on the below which is tested.
Here is how to use FCK editor using mod-wsgi:
Technically it uses a couple features of WHIFF (see
WHIFF.sourceforge.net),
-- in fact it is part of the WHIFF distribution --
but
the WHIFF features are easily removed.
I don't know how to install it in Django, but if
Django allows wsgi apps to be installed easily, you
should be able to do it.
NOTE: FCK allows the client to inject pretty much anything
into HTML pages -- you will want to filter the returned value for evil
attacks.
(eg: see whiff.middleware.TestSafeHTML middleware for
an example of how to do this).
"""
Introduce an FCK editor input element. (requires FCKeditor http://www.fckeditor.net/).
Note: this implementation can generate values containing code injection attacks if you
don't filter the output generated for evil tags and values.
"""
import fckeditor # you must have the fck editor python support installed to use this module
from whiff.middleware import misc
import os
class FCKInput(misc.utility):
def __init__(self,
inputName, # name for input element
basePath, # server relative URL root for FCK HTTP install
value = ""): # initial value for input
self.inputName = inputName
self.basePath = basePath
self.value = value
def __call__(self, env, start_response):
inputName = self.param_value(self.inputName, env).strip()
basePath = self.param_value(self.basePath, env).strip()
if basePath[-1:]!="/":
basePath+="/"
value = self.param_value(self.value, env)
oFCKeditor = fckeditor.FCKeditor(inputName)
oFCKeditor.BasePath = basePath
oFCKeditor.Height = 300 # this should be a require!
oFCKeditor.Value = value
# hack around a bug in fck python library: need to put the user agent in os.environ
# XXX this hack is not safe for multi threaded servers (theoretically)... need to lock on os.env
os_environ = os.environ
new_os_env = os_environ.copy()
new_os_env.update(env)
try:
os.environ = new_os_env
htmlOut = oFCKeditor.Create()
finally:
# restore the old os.environ
os.environ = os_environ
start_response("200 OK", [('Content-Type', 'text/html')])
return [htmlOut]
__middleware__ = FCKInput
def test():
env = {
"HTTP_USER_AGENT":
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14"
}
f = FCKInput("INPUTNAME", "/MY/BASE/PATH", "THE HTML VALUE TO START WITH")
r = f(env, misc.ignore)
print "test result"
print "".join(list(r))
if __name__=="__main__":
test()
See this working, for example, at
http://aaron.oirt.rutgers.edu/myapp/docs/W1500.whyIsWhiffCool.
btw: thanks. I needed to look into this anyway.
Edit: Ultimately I was unhappy with this solution also so I made a Django app that takes care of the file uploads and browsing.
This is the solution I finally hacked together after reading the fckeditor code:
import os, sys
def fck_handler(environ, start_response):
path = environ['PATH_INFO']
if path.endswith(('upload.py', 'connector.py')):
sys.path.append('/#correct_path_to#/fckeditor/editor/filemanager/connectors/py/')
if path.endswith('upload.py'):
from upload import FCKeditorQuickUpload
conn = FCKeditorQuickUpload(environ)
else:
from connector import FCKeditorConnector
conn = FCKeditorConnector(environ)
try:
data = conn.doResponse()
start_response('200 Ok', conn.headers)
return data
except:
start_response("500 Internal Server Error",[("Content-type","text/html")])
return "There was an error"
else:
sys.path.append('/path_to_your_django_site/')
os.environ['DJANGO_SETTINGS_MODULE'] = 'your_django_site.settings'
import django.core.handlers.wsgi
handler = django.core.handlers.wsgi.WSGIHandler()
return handler(environ, start_response)
application = fck_handler

Categories

Resources