How to pluralize a name in a template with jinja2? - python

If I have a template variable called num_countries, to pluralize with Django I could just write something like this:
countr{{ num_countries|pluralize:"y,ies" }}
Is there a way to do something like this with jinja2? (I do know this doesn't work in jinja2) What's the jinja2 alternative to this?
Thanks for any tip!

Guy Adini's reply is definitely the way to go, though I think (or maybe I misused it) it is not exactly the same as pluralize filter in Django.
Hence this was my implementation (using decorator to register)
#app.template_filter('pluralize')
def pluralize(number, singular = '', plural = 's'):
if number == 1:
return singular
else:
return plural
This way, it is used exactly the same way (well, with parameters being passed in a slightly different way):
countr{{ num_countries|pluralize:("y","ies") }}

Current Jinja versions have the i18n extension which adds decent translation and pluralization tags:
{% trans count=list|length %}
There is {{ count }} {{ name }} object.
{% pluralize %}
There are {{ count }} {{ name }} objects.
{% endtrans %}
You can use this even if you don't actually have multiple language versions - and if you ever add other languages you'll have a decent base which requires no changes (not all languages pluralize by adding an 's' and some even have multiple plural forms).

According to Jinja's documentation, there is no built in filter which does what you want. You can easily design a custom filter to do that, however:
def my_plural(str, end_ptr = None, rep_ptr = ""):
if end_ptr and str.endswith(end_ptr):
return str[:-1*len(end_ptr)]+rep_ptr
else:
return str+'s'
and then register it in your environment:
environment.filters['myplural'] = my_plural
You can now use my_plural as a Jinja template.

You also want to check if the word is already plural. Here is my solution:
def pluralize(text):
if text[-1:] !='s':
return text+'s'
else:
return text
Then register the tag to your environment (this can be applied to the Django templating engine too).

Related

Single View with Dynamic template

I've got modules that if enabled/disabled would require a different arrangement of tables and iframes on the frontend webpage. In order to do this I set up a function to check which modules are enabled/disabled and assigned a number to each 'set' and will be created separate templates for each "set1.html", "set2.html", "set3.html", etc...
I'd like to utilize one single view where I can pass the set number from from function I created but I can't seem to figure out how.
def homeset(request):
return render(request, 'app/set1.html', {})
Looking to figure out some way to make the "1" the return of the function I created to determine which set# to load as template and would prefer to not have to create a view for every single template needed.
Just taking a stab at this, do you have a problem with setting the template name before rendering?
def homeset(request):
# define 'my_set'
# define num
...
my_template_name = None
if num in my_set:
my_template_name = 'app/set{}.html'.format(num)
return render(request, my_template_name)
Looks like Scott Skiles answered your question, but an alternative way is to use a single base template and the "include" templatetag.
You can pass the set number to the template and use the "if" templatetag" to include the content from appropriate template(s) for each set number.
{% if set_num == 1 %}
{% include "foo/bar.html" %}
{% elif set_num == 2 %}
{% include "foo/two.html" %}
{% endif %}

How can I set a (non-global) variable in a jinja context function?

I want to set variable values, but only when they are not already assigned and within a local context.
So there is a solution to this:
{% with x=( x | default(1)) %}
{{ x }}
{% endwith %}
{% with x=2 %}
{% with x=( x | default(1)) %}
{{ x }}
{% endwith %}
{% endwith %}
This works nicely but it is a lot of text. I have many situations where I don't have just one but up to 20 variables that are being set and then a macro is called, or a template is included with those values.
Writing all those default conditions is just a mess and provokes mistakes. So I would love to be able to set a value on the current context in e.g. in a context function. But if I try the following:
#contextfunction
def defaults(ctx, **vals):
for k,v in vals.iteritems():
if k not in ctx:
ctx[k] = v
I get the an exception:
TypeError: 'Context' object does not support item assignment
And trying to set a value on ctx.vars would not help either:
vars
The template local variables. This list contains environment and context functions from the parent scope as well as local
modifications and exported variables from the template. The template
will modify this dict during template evaluation but filters and
context functions are not allowed to modify it.
http://jinja.pocoo.org/docs/2.9/api/#jinja2.Context.vars
I tried with
#contextfunction
def defaults(ctx, **vals):
for k,v in vals.iteritems():
if k not in ctx.vars:
ctx.vars[k] = v
And it gives no exception but just seems to not assign the value to the context.
I know I could write to the global context but that's not what I would like to do as it would produce side effects.
Is there a possibility to get just the current context and set a value on it? I didn't find any instructions on it and how this could be done and I did not really grasp that from reading the jinja source.
I found a solution, somehow I keep solving my own problems. It is not exactly an answer to the question of "How can I set a (non-global) variable in a jinja context function?", but it solves the problem.
I have written a jinja extension, that allows for a simple "default" tag:
from jinja2 import nodes
from jinja2.ext import Extension
"""
DefaultExtension
~~~~~~~~~~~~~~~~
Very simple jinja extension that allows for the following
{% set x=(x | default(1)) %}
{% set y=(y | default(2)) %}
to be written as
{% default x=1, y=2 %}
:copyright: (c) 2017 by the Roman Seidl
:license: BSD
"""
class DefaultExtension(Extension):
# a set of names that trigger the extension.
tags = set(['default'])
def parse(self, parser):
#just dump the tag
lineno = next(parser.stream).lineno
#parse through assignments (similar to parser.parse_with)
assignments = []
while parser.stream.current.type != 'block_end':
lineno = parser.stream.current.lineno
if assignments:
parser.stream.expect('comma')
target = parser.parse_assign_target()
parser.stream.expect('assign')
expr = (parser.parse_expression())
#consruct a 'default' filter
filter = nodes.Filter(nodes.Name(target.name, 'load'), 'default', [expr], [], None, None, lineno=lineno)
#produce an assignment with this filter as value
assignment = nodes.Assign(target, filter, lineno=lineno)
assignments.append(assignment)
return assignments
I just had to add it to my app:
app.jinja_env.add_extension(DefaultExtension)
and it works quite nicely, though I must confess I haven't yet tested it very thoroughly.
Anyone think I should submit this to jinja?

Keep undefined variables

I am interested in rendering a template in multiple steps or keeping the tags for the undefined variables in Jinja2. I believe this would mean not only creating the 'UndefinedSilent" class (so the template won't crash on missing data) but also keeping the tags with the appropriate variable names if they are missing.
Example:
Let's say we have the name = "Test" included in the context, but quantity is missing.
Givent the following template:
<p>{{name}} has {{quantity}}</p>
After rendering, I need the template to become:
<p>test has {{quantity}}</p>
Does anyone know if this is achievable?
Providing DebugUndefined to named parameter undefined in the env, apparently does the trick. The rendered template preserves the {{<undefined variable}}.
Like here:
from jinja2 import Environment, BaseLoader, DebugUndefined
rtemplate = Environment(loader=BaseLoader,undefined=DebugUndefined).from_string("{{ a }} is defined, but {{ b}} is undefined")
print(rtemplate.render({"a":"a"}))
The result is:
a is defined, but {{ b }} is undefined
It is achievable using the default built-in filter.
<p>{{name|default('{{name}}')}} has {{quantity|default('{{quantity}}')}}</p>
The drawback is that the code becomes uglier and the variable names are duplicated, thus reducing maintainability.
Here is another approach that preserves undefined double curly expressions after rendering, including those that contain "multilevel" (dot-notated) references as well as any others.
The answer provided by Willy Pregliasco does not support preservation of undefined list types, eg {{ some_list[4] }} which is something I required. The below solution addresses this, as well as all possible schema types.
The idea is to parse the input template and try to resolve each expression with the provided context. Any that can not be resolved, we replace with a double curly expression that simply resolves to the original expression as a string.
Pass your template and context through the below preserve_undefineds_in_template function before calling render:
from jinja2 import Template, StrictUndefined, UndefinedError
import re
def preserve_undefineds_in_template(template, context):
patt = re.compile(r'(\{\{[^\{]*\}\})')
j2_expressions = patt.findall(template)
for j2_expression in set(j2_expressions):
try:
Template(j2_expression, undefined=StrictUndefined).render(context)
except UndefinedError:
template = template.replace(j2_expression, f"{{% raw %}}{j2_expression}{{% endraw %}}")
return template
Example:
template = """hello {{ name }}, {{ preserve_me }} {{ preserve.me[2] }}"""
context = { "name": "Alice" }
# pass it through the preserver function
template = preserve_undefineds_in_template(template, context)
# template is now:
# hello {{ name }}, {% raw %}{{ preserve.me }}{% endraw %} {% raw %}{{ preserve.me.too[0] }}{% endraw %}
# render the new template as normal
result = Template(template).render(context)
print(result)
The output is:
hello Alice, {{ preserve_me }} {{ preserve.me[2] }}
I also wated the same behaviour. The library jinja2schema provides the schema of variables needed for your template.
The steps for my solution are:
have a template
obtain the schema structure
give some data to fill
complete de data filling the missing items with the original string
from jinja2 import Template
import jinja2schema
def assign(schema, data, root=''):
'''Returns a corrected data with untouched missing fields
'''
out = {}
for key in schema.keys():
if isinstance(schema[key], (str, jinja2schema.model.Scalar)):
try:
out[key] = data[key]
except:
out[key] = f'{{{{ {root+key} }}}}'
elif isinstance(schema[key], (dict, jinja2schema.model.Dictionary)):
out[key]={}
try:
data[key]
except:
data[key] = {}
out[key] = assign(schema[key], data[key], root+key+'.')
return out
# original template
template_str = '<p>{{name}} has {{quantity}}</p>'
# read schema
schema = jinja2schema.infer(template_str)
# available data
data = {'name':'test'}
# data autocompleted
data_corrected = assign(schema, data)
# render
template = Template(template_str)
print(template.render(data_corrected))
The output is
<p>test has {{ quantity }}</p>
That was the intended result.
Hope it will help. This solution doesn't work with lists, but I think it is possible to extend the solution. You can also obtain the list of missing fields, if you need them.
This is the version with Template rather than Environment:
from jinja2 import Template, DebugUndefined
template = Template("<p>{{name}} has {{quantity}}</p>", undefined=DebugUndefined)
new_template = Template(template.render(name="Test"))
Thanks #giwyni
Another little hack if you just have few variables:
<p>{{name}} has {{ "{{quantity}}" }}</p>
The second replacement will replace to {{quantity}} so all is good ;)

Django remove leading zero

In a Django template how do I remove the first Zero from all the this mobile numbers I'm displaying. In Python I have tried this but with no success....
{% for object in data %}
{{ object.mobile.lstrip('0') }}
{% endfor %}
views.py
def sms(request, incentive):
objectQuerySet = Recipient.objects.filter(incentiveid=incentive)
context = {'data':objectQuerySet}
return render_to_response('smssend.html', context, context_instance=RequestContext(request))
There are many ways to do that.
write custom template filter
filter user input and store phones with leading zeros already stripped
create a custom object #property returning mobile with zeroes stripped (this one a little bit dirty)
do cleanup before rendering template
I assume you will need to use similar logic in other locations of your application so why not just add a method to your model to clean the data? The code below assumes your mobile field is a string.
class Recipient(models.Model):
...
def mobile_no_zero(self):
return self.mobile[1:] if self.mobile.startswith('0') else self.mobile
And then you can call the method from your template:
{% for object in data %}
{{ object.mobile_no_zero }}
{% endfor %}
You should pass your data to your template in the correct format. Django templates are not "Python interspersed with HTML" like PHP is.
For example, when you first pass your data in your view, you should make sure that it is an iterable with the zeroes already stripped.
There is no lstrip in the Django template mini-language.
Edit: If you know that the first digit will always be a 0, you can also do this:
{{ object|slice:"1:" }}
There is one method that does work (but is more of a kludge that anything and removes 0 or more 0s) - and only if the number can be converted to a float to start with (ie, no spaces)
In [23]: t = Template('{{ value|floatformat:0 }}')
In [24]: t.render(Context({'value': '00123'}))
Out[24]: u'123'
You can use cut ( it will cut all zeros from number )
or you can use
phone2numeric
Imo its beter opption

Django: How to nicely format an address in a template without knowing how much information is available?

I have a table with address information. It has these fields.
address_1
address_2
city
state
zip
No field is required. I want to display this in a template and format it nicely. I want line breaks between address_1, address_2, and the city/state/zip line for any of those that exists. I also want a comma between city and state if both exist. What is the best way to accomplish this? I started writing an if statement in the template, but it seemed to be getting a bit unwieldy. The big problem is that the user could enter only a city and state, only a zip code, a full-formed address, or anything between.
Many ways of achieving this, none are likely to be that elegant. I'll throw this into the ring... Do this in your handler, and pass address_parts to the template context.
## Gather the address components in groups, removing any that are None
address_parts = filter(None, [
model_obj.address_1,
model_obj.address_2,
u', '.join(filter(None, [model_obj.address_city, model_obj.state])),
model_obj.zip,
])
## In the template, join the address groups
{{ address_parts|join:"<br>" }}
If you add a simple custom filter to append text to variables if they exist:
#register.filter
def append(arg, suffix):
return arg + suffix if arg else ''
then you can do something like
{{ address_1|append:"<br/>" }}
{{ address_2|append:"<br/>" }}
{{ city|append:", " }}{{ state|append:" " }}{{ zip }}
If you want to do this in a <table> or whatever, you can also use a similar prepend filter.

Categories

Resources