Custom templating using Jinja2 - python

I am trying to use jinja2 as follows.
Suppose,
Following are tags:
tags: {"world":"WORLD", "c language": "Dennis Ritchie", "apple":"JOBS" }
Input:
HELLO {{ world }}, C is written by **{{ c language }}**, **}}** while **{{** java is written by {{ java }}, hola.
Output:
HELLO WORLD, C is written by Dennis Ritchie, **}}** while **{{** java is written by, hola.
So in short there are following things I have to do.
delimiters - {{ & }}
If there is no tag predefined, it should put empty.
If there is only single delimiter {{ or }} ( I mean not pair) ,it should not consider tag else it should be printed as it it.
Tags should allow spaces.
Out of 4, for only 1 & 2 jinja2 is working fine.
from jinja2 import Template
t = Template(input_string)
t.render(context)
But for 3rd & 4th, it's not working.(or I am mistaking.)
I found only 1 template engine called "mustache" which supports above all 4 conditions. But I don't know how it works in case of performance.
As jinja2 is mature template engine, I think it's possible to customize default behaviour.
Can anybody know solution?
Thnx in advance.
My primary testing shows that Mustache(Pystache) is too faster than jinja2. If possible please give expert opinion.
http://mustache.github.io/
https://github.com/defunkt/pystache

Finally I continue with mustache. It's really awesome template engine.
http://mustache.github.io/
For mustache build for python
https://github.com/defunkt/pystache

I don't think this is possible. The documentation is quite clear on identifiers:
Jinja2 uses the regular Python 2.x naming rules. Valid identifiers
have to match [a-zA-Z_][a-zA-Z0-9_]*. As a matter of fact non ASCII
characters are currently not allowed. This limitation will probably go
away as soon as unicode identifiers are fully specified for Python 3.

Related

How to properly set variables in a latex template for Django

I want to create a pdf with latex through a django view. I created the view and set up a latex template and it works fine except for one thing. When I place a variable in the latex template I have to use spaces around the curly brackets like so:
\somevalue{ {{variable}} }
Otherwise django won't check that it is as a variable. The latex syntax checker already tells me "Unintendes whitespace around }?!". I can pass the variable into the template through my view and the pdf get created but then I have whitespaces around the inserted text.
Does anybody has an idea how to solve this?
Based on some google research, I'd recommend switching templating engines to Jinja, which is supported by Django and has configurable syntax.
Be warned, I haven't fully tested this.
Here's how your latex templates would look like:
\somevalue{((variable))}
The most important part is setting the variable_start_string and variable_end_string options:
>>> import jinja2
>>> env = jinja2.Environment(variable_start_string='((', variable_end_string='))')
>>> t = env.from_string("\somevalue{((variable))}")
>>> t.render(name='Attention!')
'\\somevalue{Attention!}'
Jinja's switching documentation outlines the (few) syntax differences. From the FAQ:
The default syntax of Jinja2 matches Django syntax in many ways. However this similarity doesn’t mean that you can use a Django template unmodified in Jinja2. For example filter arguments use a function call syntax rather than a colon to separate filter name and arguments. Additionally the extension interface in Jinja is fundamentally different from the Django one which means that your custom tags won’t work any longer.
Django 1.9 and 1.8 and maybe other versions have built-in support for Jinja.
I haven't found an example of configuring Jinja syntax in Django, and I can't test this at the moment but I believe you need to change the
TEMPLATES['OPTIONS'] dictionary as needed:
block_start_string='(#',
block_end_string='#)',
variable_start_string='((',
variable_end_string='))',
comment_start_string='((#',
comment_end_string='#))',
The solution I found is to remove extra spaces after template rendering:
template = select_template(self.get_template_names())
latex_content = template.render(context)
latex_content = re.sub(r'\{ ', '{', latex_content)
latex_content = re.sub(r' \}', '}', latex_content)
This has the benefit of not requiring extra template tags flooding the template. However this as the drawback to require template writers to be aware of this behavior and take it into account wherever curly braces are used.
In practice, it would probably be better to write a custom Template class that handles this.
Edit
Note that using this method, there is no reason to use a space as separator rather than any other character. In order to make it clearer for template writers/readers, you can use another character that would ring him a bell. For instance:
template.tex
\somevalue{§{{ variable }}§}
views.py
template = select_template(self.get_template_names())
latex_content = template.render(context)
latex_content = re.sub(r'\{§', '{', latex_content)
latex_content = re.sub(r'§\}', '}', latex_content)
Which makes it clear that if there is a space, it is intended. And I think it's already clear that § everywhere is not intended to be displayed.

Flask-Babel localized strings within js

I'm pretty new to both Python and Flask (with Jinja2 as template engine) and I am not sure I am doing it the right way. I am using Flask-Babel extension to add i18n support to my web application. I want to get localized strings from my js code, for instance:
var helloWorld = gettext('Hello, world');
console.log(helloWorld); //should log a localized hello world message
For this, I configured babel (babel.cfg):
[python: **/**.py]
[jinja2: **/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
[javascript: **/**.js]
encoding = utf-8
And its initialization is (imports omitted for simplicity):
#main Flask app
app = Flask(__name__)
#localization
babel = Babel(app)
LANGUAGES = {
'ca': 'Catalan',
'en': 'English',
'de': 'Deutsch',
'es': 'Español',
'fi': 'Finnish',
'it': 'Italian'
}
#babel.localeselector
def get_locale():
return request.accept_languages.best_match(LANGUAGES.keys())
#some more stuff...
Babel identifies that string when building the POT/PO language files, but it seems I can't access these localized strings from js code since gettext function is not defined. It seems like Jinja2 is ignoring this part.
Any hints?
I have finally found a solution, although I am not sure it is the way to go. The idea is to wrap the javascript code within an html template, which is interpretated by Jinja2 before it is rendered and apply a custom Jinja2 filter to get rid of some minor issues. I tried to keep js files separately but it did not work.
It seems that gettext function can be used like so:
var helloWorld = {{gettext('Hello, world')}};
But then, no quotes are inserted, and hence, js interpreter throws an error:
var helloWorld = Hello, world;
That's why I have finally applied a custom filter. A working example would be as follows.
hello_world.html:
<script type="text/javascript">
var x = {{gettext('Hello, world')|generate_string|safe}};
console.log(x); //logs the localized hello world message
</script>
app.py:
#Jinja2 filters
from jinja2 import evalcontextfilter, Markup
#Mind the hack! Babel does not work well within js code
#app.template_filter()
#evalcontextfilter
def generate_string(eval_ctx, localized_value):
if localized_value is None:
return ""
else:
return Markup("\"" + localized_value + "\"").unescape()
Hope this helps!
Providing translations in rendered JavaScript is a bit fragile. Also, I usually do not generate JavaScript using Jinja because it uses the same type of brackets and easily turns into mess when abused (it's always possible to have dynamic data and static JavaScript).
Alternative lightweight approach is to do the same JSON trick, but using data-attributes:
<div id="view-i18n" data-i18n='{{ view_i18n|tojson }}'> ... </div>
(NB: single quotes!)
But it's also good for a limited number of translations.
Perhaps, the most solid approach is to have the same translations in JavaScript as there are in the Flask app.
With a help of a utility called pojson it is possible to convert po-files to json (see https://github.com/ZeWaren/flask-i18n-example for an example) as part of the build process (for instance, right after making mo-files). Translations can be easily added to some unique enough global namespace variable by prepending the output of pojson with var some_unique_name = to have access to it. Or put the file under static into locale-specific file (eg static/view-es.json , static/view-fr.json, etc) and get it with ajax call.
Some things to consider though. You may need to break your translation domain into separate Python and Javascript by controlling babel extraction options if you really want to make JSON smaller. Also, having all translation strings in Javascript has security aspects. Maybe, you do not want to expose certain phrases only admins see to be open to other category of users. But then more translation domains are needed for different levels of access. Also header information may need to be removed to prevent leaking translator's emails and whatnot. This, of course, complicates the build process somewhat, but the more translation JavaScript side needs with time, the more automation pays itself off.

Add custom tokens in Jinja2 (e.g. %% somevar %%)

I'm making a Flask app for local development (on a Mac) of HTML templates that will eventually be served through ASP.NET.
For the purposes of local development, I want a way to replace the contents of .NET-style tokens with some data, meaning that Jinja2 would need to be able to recognize %% ... %% tokens in addition to the standard ones: {{ ... }}, <% ... %>, etc.
Everything I've found online pertains to the inclusion of some new functionality within the existing tags (e.g. {{ my_custom_function | arg1 arg2 }})
But what about defining a new pattern for tags altogether? Has anyone done this successfully? And will it require modification to the Jinja2 core?
As far as I know, you can use one set for block_start_string and block_end_string, as well as one set for variable_start_string and variable_end_string.
From jinja2/environment.py
`block_start_string`
The string marking the begin of a block. Defaults to ``'{%'``.
`block_end_string`
The string marking the end of a block. Defaults to ``'%}'``.
`variable_start_string`
The string marking the begin of a print statement.
Defaults to ``'{{'``.
`variable_end_string`
The string marking the end of a print statement. Defaults to
``'}}'``.
You can override these with environment variables. Though, I don't think there is a way to have multiple types recognized. For instance, you can't have {{ and <% both work, but with a little hackery you certainly could.
In addition to the correct answer maybe an example implementation to change the variable marker:
Add following header to the first line in the jinja2 template file.
#jinja2: variable_start_string: "#{" , variable_end_string: "}#"

Python Django Templates and testing if a variable is null or empty string

I am pretty new to django, but have many years experience coding in the java world, so I feel ridiculous asking this question - I am sure the answer is obvious and I am just missing it. I can't seem to find the right way to query this in google or something... I have searched through the django docs and it either isn't there or I am just not seeing it. All I want to do is in a template test if the var is not null OR an empty string OR just a bunch of spaces. I have an issue where spaces are getting introduced into my field - another issue I have to, and will, work out... but, I want my logic to work regardless. Right now, because my string contains just spaces simply doing this: {% if lesson.assignment %} always passes even though I don't want it to. I have looked for a trim type functionality that would work between {% %}, but I can't seem to find anything. I have tried strip, but it doesn't work between {% %}. Can someone please point me in the direction of the answer... some docs I might have missed... something?
Thanks a ton in advance!
{% if lesson.assignment and lesson.assignment.strip %}
The .strip calls str.strip() so you can handle whitespace-only strings as empty, while the preceding check makes sure we weed out None first (which would not have the .strip() method)
Proof that it works (in ./manage.py shell):
>>> import django
>>> from django.template import Template, Context
>>> t = Template("{% if x and x.strip %}OK{% else %}Empty{% endif %}")
>>> t.render(Context({"x": "ola"}))
u'OK'
>>> t.render(Context({"x": " "}))
u'Empty'
>>> t.render(Context({"x": ""}))
u'Empty'
>>> t.render(Context({"x": None}))
u'Empty'
This may not have been available when the question was posted but one option could be using the built in template filter default_if_none (Django Documentation Link).
For example:
{{ lesson.assignment|default_if_none:"Empty" }}
If lesson.assignment is a model field, you could define a helper function in your model and use it:
class Lesson(models.Model):
assignment = models.CharField(..., null=True, ...)
# ...
#property
def has_assignment(self):
return self.assignment is not None and self.assignment.strip() != ""
Then use {% if lesson.has_assignment %} in your template.
You can simulate it by using the cut filter to remove all spaces. But you should probably find out why it contains all spaces in the first place.
You can call any built-in methods anywhere in a Django template variable. For example, you can call the Python string method strip. So this will work:
{% if lesson.assignment.strip %}
Here is a simple way to do this, from Django version 3.2
{{ lesson.assignment|default:"nothing" }}
This works for both cases (empty string and None object).
ref link: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#std:templatefilter-default

Split tags in python

I have a file that contains this:
<html>
<head>
<title> Hello! - {{ today }}</title>
</head>
<body>
{{ runner_up }}
avasd
{{ blabla }}
sdvas
{{ oooo }}
</body>
</html>
What is the best or most Pythonic way to extract the {{today}}, {{runner_up}}, etc.?
I know it can be done with splits/regular expressions, but I wondered if there were another way.
PS: consider the data loaded in a variable called thedata.
Edit: I think that the HTML example was bad, because it directed some commenters to BeautifulSoup. So, here is a new input data:
Fix grammatical or {{spelling}} errors.
Clarify meaning without changing it.
Correct minor {{mistakes}}.
Add related resources or links.
Always respect the original {{author}}.
Output:
spelling
mistakes
author
Mmkay, well here's a generator solution that seems to work well for me. You can also provide different open and close tags if you like.
def get_tags(s, open_delim ='{{',
close_delim ='}}' ):
while True:
# Search for the next two delimiters in the source text
start = s.find(open_delim)
end = s.find(close_delim)
# We found a non-empty match
if -1 < start < end:
# Skip the length of the open delimiter
start += len(open_delim)
# Spit out the tag
yield s[start:end].strip()
# Truncate string to start from last match
s = s[end+len(close_delim):]
else:
return
Run against your target input like so:
# prints: today, runner_up, blabla, oooo
for tag in get_tags(html):
print tag
Edit: it also works against your new example :). In my obviously quick testing, it also seemed to handle malformed tags in a reasonable way, though I make no guarantees of its robustness!
try templatemaker, a reverse-template maker. it can actually learn them automatically from examples!
I know you said no regex/split, but I couldn't help but try for a one-liner solution:
import re
for s in re.findall("\{\{.*\}\}",thedata):
print s.replace("{","").replace("}","")
EDIT: JFS
Compare:
>>> re.findall('\{\{.*\}\}', '{{a}}b{{c}}')
['{{a}}b{{c}}']
>>> re.findall('{{(.+?)}}', '{{a}}b{{c}}')
['a', 'c']
If the data is that straightforward, a simple regex would do the trick.
J.F. Sebastian wrote this in a comment but I thought it was good enough to deserve its own answer:
re.findall(r'{{(.+?)}}', thestring)
I know the OP was asking for a way that didn't involve splits or regexes - so maybe this doesn't quite answer the question as stated. But this one line of code definitely gets my vote as the most Pythonic way to accomplish the task.

Categories