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'])
Related
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
For seemingly no reason my angular $scope variable is not rendering in the browser. My whole project has been working just fine until I created this page today. All other pages still work as expected. I've deleted all my code down to just the variable itself with the following code and its still rendering as [undefined].
I'm using python/flask to serve up the application with the jinja templating engine and therefore have changed the interpolation characters for angular so they don't conflict with the same characters in jinja:
var zues = angular.module('zues', []);
zues.config(['$interpolateProvider', function($interpolateProvider) {
$interpolateProvider.startSymbol('{{[');
$interpolateProvider.endSymbol(']}}');
}]);
Controller:
zues.controller('test', ['$scope', '$http', function($scope, $http){
$scope.test123 = "testing123";
}]);
HTML:
<body ng-app="zues" ng-controller="test">
{{[ test123 ]}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script src="/static/assets/js/zues.js"></script>
<script src="/static/assets/js/Controllers/test.js"></script>
python/flask route:
from flask import Flask, render_template
import flask
application = Flask(__name__)
#application.route("/test")
def test():
return render_template('test.html')
if __name__ == "__main__":
application.run(host='127.0.0.1', reloader_type='stat', debug=True)
This renders in the browser simply as:
[Undefined]
I would have expected it to render:
testing123
Anything simple that I'm just overlooking?
I figured it out. My interpolation characters that I picked for angular were too closely related to the characters that Jinja uses by default. Jinja was taking {{[ test123 ]}} and interpreting the square brackets as plain text and then trying to find a variable test123 within my python script that didn't exist.
This didn't seem to break on the rest of the project because the angular variables I used in my html templates in the rest of the project were always bound to something else and therefore being watched by the digest loop whereas in this scenario it would only have been interpolated once.
I changed the interpolation characters to be {[{ test123 }]} so that Jinja never catches it and only angular does.
A few quick changes should help.
1) Get rid of the interpolate provider. You won't need this code if you complete 2 & 3 below.
2) Remove the close bracket ] when you close the controller definition
Yours: }]);
Mine: });
3) Add {{ ' before the set of curly braces and after ' }}
example/ {{ '{{someVariable + "" + anotherVariable}}' }}
I heard about the filter |safe, but if I understood correctly, that's unsafe and creates a backdoor for injections.
What are the alternatives to display full posts with formatted text?
I think when you not use the filter of |safe, then output should return as text only with html markup (not rendered as html output).
But, if you need to exclude some dangerous tags such as <script>location.reload()</script>, you need to handle it with custom templatetag filter..
I got good answer from: https://stackoverflow.com/a/699483/6396981, via BeautifulSoup.
from bs4 import BeautifulSoup
from django import template
from django.utils.html import escape
register = template.Library()
INVALID_TAGS = ['script',]
def clean_html(value):
soup = BeautifulSoup(value)
for tag in soup.findAll(True):
if tag.name in INVALID_TAGS:
# tag.hidden = True # you also can use this.
tag.replaceWith(escape(tag))
return soup.renderContents()
# clean_html('<h1>This is heading</h1> and this one is xss injection <script>location.reload()</script>')
# output:
# <html><body><h1>This is heading</h1> and this one is xss injection <script>location.reload()</script></body></html>
#register.filter
def safe_exclude(text):
# eg: {{ post.description|safe_exclude|safe }}
return clean_html(text)
Hope it usefull..
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)
Suppose I have a huge paragraph.
I just want the top 15 words to be shown. After than, the person clicks "more" to see the rest of the stuff.
Just whipped this up, seems to do what you want, and there's no dependency on any external JS libs.
DISCLAIMER: I haven't tried this in IE, but chrome and firefox work fine.
from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
register = template.Library()
import re
readmore_showscript = ''.join([
"this.parentNode.style.display='none';",
"this.parentNode.parentNode.getElementsByClassName('more')[0].style.display='inline';",
"return false;",
]);
#register.filter
def readmore(txt, showwords=15):
global readmore_showscript
words = re.split(r' ', escape(txt))
if len(words) <= showwords:
return txt
# wrap the more part
words.insert(showwords, '<span class="more" style="display:none;">')
words.append('</span>')
# insert the readmore part
words.insert(showwords, '<span class="readmore">... <a href="#" onclick="')
words.insert(showwords+1, readmore_showscript)
words.insert(showwords+2, '">read more</a>')
words.insert(showwords+3, '</span>')
# Wrap with <p>
words.insert(0, '<p>')
words.append('</p>')
return mark_safe(' '.join(words))
readmore.is_safe = True
To use it, just create a templatetags folder in your app, create the __init__.py file in there, and then drop this code into readmore.py.
Then at the top of any template where you want to use it, just add: {% load readmore %}
To use the filter itself:
{{ some_long_text_var|readmore:15 }}
The :15 tells how many words you want to show before the read more link.
If you want anything fancy like ajax loading of the full content, that's quite a bit different and would require a bit more infrastructure.
use truncatechars_html
refer to : https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#truncatechars-html
truncatechars_html
Similar to truncatechars, except that it is aware of HTML tags. Any tags that are opened in the string and not closed before the truncation point are closed immediately after the truncation.
For example:
{{ value|truncatechars_html:9 }}
If value is "<p>Joel is a slug</p>", the output will be "<p>Joel i...</p>".
Newlines in the HTML content will be preserved.
There is truncatewords filter, although you still need a JavaScript helper to do what you described.
from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
register = template.Library()
#register.filter
def readmore(text, cnt=250):
text, cnt = escape(text), int(cnt)
if len(text) > cnt:
first_part = text[:cnt]
link = u'%s' % _('read more')
second_part = u'%s<span class="hide">%s</span>' % (link, text[cnt:])
return mark_safe('... '.join([first_part, second_part]))
return text
readmore.is_safe = True
I rewrote an earlier answer to be cleaner and to handle string escaping properly:
#register.filter(needs_autoescape=True)
#stringfilter
def read_more(s, show_words, autoescape=True):
"""Split text after so many words, inserting a "more" link at the end.
Relies on JavaScript to react to the link being clicked and on classes
found in Bootstrap to hide elements.
"""
show_words = int(show_words)
if autoescape:
esc = conditional_escape
else:
esc = lambda x: x
words = esc(s).split()
if len(words) <= show_words:
return s
insertion = (
# The see more link...
'<span class="read-more">…'
' <a href="#">'
' <i class="fa fa-plus-square gray" title="Show All"></i>'
' </a>'
'</span>'
# The call to hide the rest...
'<span class="more hidden">'
)
# wrap the more part
words.insert(show_words, insertion)
words.append('</span>')
return mark_safe(' '.join(words))
The HTML in there assumes you're using Bootstrap and Fontawesome, but if that's not your flavor, it's easy to adapt.
For the JavaScript, assuming you're using jQuery (if you're using Bootstrap you probably are), you'll just need to add something like this:
$(".read-more").click(function(e) {
e.preventDefault();
var t = $(this);
t.parent().find('.more').removeClass('hidden');
t.addClass('hidden');
});