How to interpolate already translated string in Django templates? - django

I have a generic delete view that includes a confirmation question as a translated string containing one placeholder. I would like to interpolate it like this:
<p class="text-error">
{% message % object %}
</p>
Variable message contains a string like: "Do you want to remove user %s?".
How can I use string interpolation in templates?

You can use the following dictionary with strings:
strings = { 'object': 'word' }
as follows:
{{ strings|stringformat:message }}
with the stringformat filter. Note that the leading % is dropped from the string (see the documentation for more details).

Finally I made a custom filter:
from django.template.base import Library
register = Library()
#register.filter
def interpolate(value, arg):
"""
Interpolates value with argument
"""
try:
return value % arg
except:
return ''

Related

How to translate numbers in django translation?

My head could not click how to translate numbers in django translation. It is not possible to translate by string id. I could print 2020 like:
{% translate '2' %}{% translate '0' %}{% translate '2' %}{% translate '0' %}
Obvioulsy, It is not the way. So, I am missing something. I would want something like:
{% translate "2020"|number %} # May be ?? It should be that easy right?
It should be that, translation from 0 to 9.
Django doesn't have this functionality (yet), but you can achieve the same by creating a custom template tag. You can read the whole documentation of creating the tag here, Custom template tags and filters.
By this way, you can translate Arabic Numerals (or anything) to any form, all you need is a mapper dict and a function that converts things by using the mapper dict.
We need to have a dict that has the source numerals as keys and target numeral as values. In this case, I assume you need to translate from Arabic numerals to nepali numerals
So, I have created a simple mapper using dict and converted the receiving value to something else using the translate_nepal_numeral(...) function.
from django import template
from django.template.defaultfilters import stringfilter
NUMBER_MAP = {
"0": "०",
"1": "१",
"2": "२",
# and so on
}
register = template.Library()
#register.filter()
#stringfilter
def translate_nepal_numeral(value):
try:
return "".join([NUMBER_MAP[char] for char in value])
except KeyError:
return value
Then in your template,
{% load custom_numerals %}
{{ "2010"|translate_nepal_numeral }}
Examples
In [3]: translate_nepal_numeral("2020")
Out[3]: '२०२०'
In [4]: translate_nepal_numeral("2120")
Out[4]: '२१२०'
In [5]: translate_nepal_numeral("2120a")
Out[5]: '2120a'
Notes
If you are passing a non-numeral, this function will return the input

Custom template filter: replace HTML element with regex filtered text (Django 2.1)

I am building a search engine, which needs a custom filter that displays the text surrounding a keyword, like the excerpts on Google results page. I am using regex to identify the surrounding words. Here is my code for the filter:
#register.filter(needs_autoescape=True)
#stringfilter
def show_excerpt (value, search_term, autoescape=True):
# make the keyword put into the search engine case insensitive #
keywords = re.compile(re.escape(search_term), re.IGNORECASE)
# make excerpt return 300 characters before and after keyword #
excerpt_text = '.{300}' + str(keywords) + '.{300}'
# replace the original text with excerpt #
excerpt = value.sub(excerpt_text, value)
return mark_safe(excerpt)
Code for the search engine in view.py:
def query_search(request):
articles = cross_currents.objects.all()
search_term = ''
if 'keyword' in request.GET:
search_term = request.GET['keyword']
articles = articles.annotate(similarity=Greatest(TrigramSimilarity('Title', search_term), TrigramSimilarity('Content', search_term))).filter(similarity__gte=0.03).order_by('-similarity')
context = {'articles': articles, 'search_term': search_term}
return render(request, 'query_search.html', context)
HTML template (it includes a custom highlight filter that highlights the keyword put into search engine):
<ul>
{% for article in articles %}
<li>{{ article|highlight:search_term }}</li>
<p> {{ article.Content|highlight:search_term|show_excerpt:search_term }} </p>
{% endfor %}
</ul>
Error message: 'SafeText' object has no attribute 'sub'
I think I am doing .sub wrong. I just need the excerpt to replace the entire original text (the text that I am putting a filter on). The original text starts from the beginning of the data but I just want to display the data surrounding the keyword, with my highlight custom filter highlighting the keyword (just like on Google). Any idea?
EDIT: when I do re.sub(excerpt_text, value), I get the error message sub() missing 1 required positional argument: 'string'.
You need to call re.sub(), not value.sub(). You are calling sub on a SafeText object, .sub() is a regex function.
I haven't tested your code but if the remaining code is correct you should just change that line to re.sub(excerpt_text, value)
I decided to ditch regex and just do good old string slicing. Working code for the filter:
#register.filter(needs_autoescape=True)
#stringfilter
def show_excerpt(value, search_term, autoescape=True):
#make data into string and lower#
original_text = str(value)
lower_original_text = original_text.lower()
#make keyword into string and lower#
keyword_string = str(search_term)
lower_keyword_string = keyword_string.lower()
#find the position of the keyword in the data#
keyword_index = lower_original_text.find(lower_keyword_string)
#Specify the begining and ending positions of the excerpt#
start_index = keyword_index - 10
end_index = keyword_index + 300
#Define the position range of excerpt#
excerpt = original_text[start_index:end_index]
return mark_safe(excerpt)

text slicing not working

template.html
{{list.report.description|default:"No description available"|slice:"45" }}{% if list.report.description|length > 45 %}...{% endif %}
1.This is slicing if character entered more than 45.
2.It produce problem while creating new report,if no description is given,it should display the default text as "No description available" but instead it is displaying the text along with 3 dots.
2.No problem if the field is saved with empty,it is displaying default as "No description available".
Thanks
Although I am not entirely sure why your code isn't working, it's the "wrong" thing to do anyhow.
Try the truncatechars method instead: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#truncatechars
{{ value|truncatechars:9 }}
If value is Joel is a slug, the output will be Joel i....
For Django 1.3 or older, use the following templatetag: http://djangosnippets.org/snippets/444/
from django import template
register = template.Library()
#register.filter
def truncatechars(s, num):
"""
Truncates a word after a given number of chars
Argument: Number of chars to truncate after
"""
length = int(num)
string = []
for word in s.split():
if len(word) > length:
string.append(word[:length]+'...')
else:
string.append(word)
return u' '.join(string)

Jinja keep indentation on include or macro

I am wondering if there is any way to keep the indentation with jinja when adding a include or macro inside a file. I would like to use jinja to generating a code file. An example would be
File: class.html
class MyClass:
def someOp():
pass
{% include "someOp.html" %}
File: someOp.html
def someOp2():
pass
The result of the template should be:
class MyClass:
def someOp():
pass
def someOp2():
pass
If there any way to make jinja prepend the indent before the include tag for each line in the included file? Or is there any way to customize jinja to do this?
One way is to wrap the include in a macro, then because the macro is a function, its output can be passed through the indent filter:
class MyClass:
def someOp():
pass
{% macro someop() %}{% include "someOp.html" %}{% endmacro %}
{{ someop()|indent }}
By default 'indent' indents 4 spaces and does not indent the first line, you can use e.g. 'indent(8)' to indent further, see http://jinja.pocoo.org/docs/templates/#list-of-builtin-filters for more details.
If what you're including is defined as a macro to begin with then the further wrapper macro is not needed, and you can jump straight to using the indent filter.
I was looking in Jinja2 to achieve the same and got to conclusion aligning multi-line block indentation with the originating Jinja statement is not possible currently.
I've posted a small PR to Jinja to add a new syntax {%* ... %} and {{* ... }} that does exactly this. See the PR for details:
https://github.com/pallets/jinja/pull/919
It would be easier if Jinja provided the facility. It looks like some work was done on this but the issue is currently closed (20 Nov 2019) and the pull request hasn't yet been merged. It could be because things get tricky quite quickly with indents (think of tabs and spaces, for example.)
The following is a simple solution I've found effective for generating Python code which, of course, needs to handle indenting well. It copes with files that use spaces for indentation.
auto_indent() detects the indent level of a variable in a host template, then applies that indent to a piece of text.
import os
import itertools
import jinja2
def indent_lines(text_lines: list, indent: int):
return [' ' * indent + line for line in text_lines]
def matching_line(s, substring):
lineno = s[:s.index(substring)].count('\n')
return s.splitlines()[lineno]
def is_space(c):
return c == ' '
def indentation(line: str) -> int:
initial_spaces = ''.join(itertools.takewhile(is_space, line))
return len(initial_spaces)
def auto_indent(template: str, placeholder: str, content_to_indent: str):
placeholder_line = matching_line(template, '{{ ' + placeholder + ' }}')
indent_width = indentation(placeholder_line)
lines = content_to_indent.splitlines()
first_line = [lines[0]] # first line uses placeholder indent-- no added indent
rest = indent_lines(lines[1:], indent_width)
return os.linesep.join(first_line + rest)
Example:
action_class = """\
class Actions:
def __init__(self):
pass
def prequel(self):
pass
{{ methods }}
def sequel(self):
pass
"""
inserted_methods = """\
def create_branch():
pass
def merge_branch():
pass
"""
if __name__ == '__main__':
indented_methods = auto_indent(action_class, 'methods', inserted_methods)
print(jinja2.Template(action_class).render(methods=indented_methods))
Example output:
>>> python indent.py
class Actions:
def __init__(self):
pass
def prequel(self):
pass
def create_branch():
pass
def merge_branch():
pass
def sequel(self):
pass
I've written a jinja2 extension to work around this long standing issue. It automates the previously proposed solution of using {% filter indent(...) %} by hooking into the preproccess api provided by jinja.ext.Extension.
If you add the extension in your jinja.Environment you can use the following syntax to include templates that get indented correctly into the rest of your template. Notice the indent content directive.
class MyClass:
def someOp():
pass
{% include "someOp.html" indent content %}
The result of rendering then becomes
class MyClass:
def someOp():
pass
def someOp2():
pass

Accessing a dict element who's key begins with an #

I have a dict passed to my template, some of the element names / keys begin with an # symbol, ie:
{ '#id': 123, '#name': 'Alex' }
Is there a good / clean way I can access these elements?
A custom template tag is the only way to access dictionary keys with special characters. The answer to this question provides a good example.
For the lazy:
from django import template
register = template.Library()
#register.filter
def dictKeyLookup(the_dict, key):
# Try to fetch from the dict, and if it's not found return an empty string.
return the_dict.get(key, '')
Which you would use like so:
{% dictKeyLookup your_dict "#blarg!#$^&*" %}
As homework, you could also convert this into a simple filter, which would give you a syntax like:
{{ your_dict|getkey:"#blarg!##$%" }}