I'm working on configurable email template and need to validate if only available_variables are used in the content. So if user put {{ apple }}, the form should return ValidationError('This variable is unavailable').
models.py:
email_content = models.TextField()
forms.py:
def clean_email_content(self):
email_content = self.cleaned_data['email_content']
available_variables = ['{{ company_name }}', '{{ user_name }}']
required_variables = ['{{ company_name }}']
for required_variable in required_variables:
if not required_variable in email_content:
raise forms.ValidationError("{} is required".format(required_variable))
# how to check if there are only `available_variables` in the content?
TL;DR
email_content cannot contain other variables (strings that starts with {{ and ends with }}) than specified in available_variables
Should I use regex or can I validate this using some method from Django Template Engine?
You may want to use the template engine's lexer instead (nb: django 1.11 code, might need to be adapted if you use django 2.x):
from django.template import base
lexer = base.Lexer("{{ var_one }} is one {% if var_two %}{{ var_two|upper }} is two {% endif %}")
tokens = lexer.tokenize()
for t in tokens:
if t.token_type == base.TOKEN_VAR:
print("{}: {}".format(t, t.contents)
I leave it up to you to read the template.base code to find out other useful features...
Also in your validation method you definitly want to catch ALL errors at once (instead of raising as soon as you found an error) so the user can fix all errors at once (unless you really want to drive your users mad, that is).
And finally, as Gasanov suggests in his answer, you also want to use sets to find both missing required vars and illegal ones - this is much more efficient than doing sequential lookups.
We can use regex to find all tags from email_content. After that we convert them to set and substract all available_variables from it to find all incorrect ones. If any exists - we throw ValidationError.
Note that available_variables is set itself and contains just tags, without curly brackets.
Our regex checks for any numbers of spaces between brackets and tag, so your users shouldn't be able to cheat.
import re
def clean_email_content(self):
email_content = self.cleaned_data['email_content']
available_variables = {'company_name', 'user_name'}
finds = re.findall(r"{{[ ]*(.*?)[ ]*}}", email_content)
invalid_tags = set(finds) - available_variables
if invalid_tags:
raise forms.ValidationError(
"Should not contain invalid tags: " + str(invalid_tags)
)
return email_content
Related
For example:
str = "<b>Hello<b> this string also includes my {{myvariable}} {%if myvariable%} and its true {%endif%}"
I know that the |safe filter exists, but that doesn´t work for inline html, only for html. Same here for the autoescape off.
I would be happy if somebody could help me :D
You have to decidde if you are going to do this formatting in Python and pass it to the template, or in the template. Also, whether you want special characters in myvariable to be escaped, or not.
In a template:
<b>Hello<b> this string also includes my {{myvariable}} {%if myvariable%} and its true {%endif%}"
or to avoid escaping
<b>Hello<b> this string also includes my {{myvariable|safe}} {%if myvariable%} and its true {%endif%}"
In Python with escaping
annitt = "and its true " if myvariable else ""
safestr = format_html(
"<b>Hello<b> this string also includes my {} {}", myvariable, anitt)
Without escaping
annitt = "and its true " if myvariable else ""
unsafestr = f"<b>Hello<b> this string also includes my {myvariable} {anitt}"
safestr = mark_safe( unsafestr)
Pass it to the template, and therein, {{safestr}}
Use it like this
from django.utils.safestring import mark_safe
str = "<b>Hello<b> this string also includes my {{myvariable}} {%if myvariable%} and its true {%endif%}"
str = mark_safe(str)
For all that need to see that, here is a tutorial:
first you need your string:
str = '{{ value.passed }}{% if not value.passed %}. Thats not good, Look here for help Google.{% endif %}'
then use the render funktion and a context to effect the Template:
from django.template import Context, Template
t = Template(str)
dict = {'value':{'passed': False} ,'url': "www.google.com"}
newstr = (t.render(Context(dict)))
last but not least use a way to execute the html code
I use this methode now (thanks to #Dad)
from django.utils.safestring import mark_safe
str = mark_safe(newstr)
I have a one template say:
default_form.html
which receives in its context a variable, say variant.
I can of course render the contents of the variable with {{ variant }}
And here:
https://stackoverflow.com/a/28076380/4002633
I see a wonderful way to load a template if it exists. It works nicely if I have a template called variant as follows:
{% try_include variant %}
But I am hoping to go one step further. I would like to use a constructed template name, in the example above and include if it exists a template named default_form_variant.html.
I imagine something like this:
{% try_include "default_form_{{variant}}.html" %}
but even more general rather than encoding the current template's name, getting that, if possible, from the context itself or in a custom tag from the parser.
I am struggling to see how and where in the custom tag definition I'd have access to three things at once:
The name of the current template: default_form.html in this case.
The argument to the tag: variant in this case
The ability to construct a template name from the first two (easy enough with os.path) and inject it into the do_include handler.
Well, as no-one was rushing to answer this I experimented with the wonderful solution to another problem posted here:
https://stackoverflow.com/a/28076380/4002633
at some length and have implemented the following with success:
class IncludeVariant(template.Node):
'''
A Template Node that tries to include a template file named as a variant
of the file it's included in. That is if it's in a template named:
form_data.html
as:
{% include_variant context_var %}
it will try and include:
form_data_context_var.html
where context_var is a context variable.
For help on custom template tags:
https://docs.djangoproject.com/en/3.1/howto/custom-template-tags/#writing-the-compilation-function
'''
def __init__(self, parser, token):
self.parser = parser
self.token = token
def render(self, context):
try:
words = self.token.split_contents()
variant = context.get(self.token.contents.split()[1], self.token.contents.split()[1])
path = context.template_name
parts = os.path.splitext(path)
words[1] = f"'{parts[0]}_{variant}{parts[1]}'"
self.token.contents = " ".join(words)
include = do_include(self.parser, self.token)
return include.render(context)
except template.TemplateDoesNotExist:
return ''
#register.tag('include_variant')
def include_variant(parser, token):
'''
Include the specified variant on this template but only if it exists.
:param parser:
:param token:
'''
return IncludeVariant(parser, token)
It is a mildly risky approach in that it rests on premises that stood up to empirical testing but I can't find clear documentation for. Namely that we can rebuild the passed-in token as I have done (a re-join of a token.split_contents()) and that this is robust. It certainly seems to be and inspecting do_include() I am satisfied it's functional.
This permits me to include variants of the current template based on the contents of a context variable.
I am working on one of my colleague's Django (Django 2.1.4) projects. I spent a couple of days to try to figure out how to disable auto escape for Form field's value when rendering it manually in a template. {% autoescape off %} and {{form1.LastName | safe }} all don't work.
Here are some relative codes.
Form.py
class tblstudentinfomodelform_page1(forms.ModelForm):
LastName = forms.CharField(max_length=30, required=True)
views.py
def application(request,application_num)
form1 = tblstudentinfo.objects.get(ApplicationNumber=application_num)
...
form1_forms = tblstudentinfomodelform_page1(initial=form1.__dict__) if form1 else tblstudentinfomodelform_page1(initial=form1)
...
return render(request,'appinfo.html',{'form1':form1_forms})
appinfo.html
<th>{{form1.LastName}}<br>{{form1.LastName.errors}} {{form1.LastName.value}} </th>
Some tests here:
LastName's value is Şhaha
test1: add {% autoescape off %} at the top of the template and {% endautoescape %} at the bottom
result1: {{form1.LastName.value}} displays correctly -- Şhaha, but input textbox shows Şhaha
run result -- html page
test2: delete autoescape tag and add safe filter
<th>{{form1.LastName | safe}}<br>{{form1.LastName.errors}} {{form1.LastName.value |safe}} </th>
result2: get the same result, looks like that safe filter only worked on form.field.value
Any suggestion?
Thank you.
Add the answer to here in case someone gets the same problem.
Create a function to unescape all HTML entities.
from html import unescape
def html_unescape(data_model): # convert to unicode characters
# Convert all named and numeric character references (e.g. >, >, >) in the string s to the corresponding Unicode characters.
for f in data_model._meta.get_fields():
if ( f.get_internal_type() == "CharField" or f.get_internal_type() == "TextField") and getattr(data_model, f.name):
#some old records haved escaped many times
str = unescape(unescape(unescape(unescape(unescape(getattr(data_model, f.name))))))
setattr(data_model, f.name, str)
return data_model
and then
form1 = tblstudentinfo.objects.get(ApplicationNumber=application_num)
form1 = html_unescape(form1)
I use djangos template filter striptags. Example:
>>> from django.utils.html import strip_tags
>>> strip_tags("<p>This is a paragraph.</p><p>This is another paragraph.</p>")
'This is a paragraph.This is another paragraph.'
What is the best way to add a space character between the paragraphs, so that I get this string instead:
'This is a paragraph. This is another paragraph.'
Edit:
One idea I have is to write a custom template filter that replaces all </p> tags with [space]</p> before the striptags filter is used. But is that a clean and robust solution?
yes, it seems like a clean solution to me. I had a similar issue when trying to create an excerpt for some articles in a WagTail (built on Django) application. I was using this syntax in my template..
{{ page.body|striptags|truncatewords:50 }}
.. and getting the same issue you described. Here is an implementation I came up - hopefully useful for other Django / WagTail developers as well.
from django import template
from django.utils.html import strip_spaces_between_tags, strip_tags
from django.utils.text import Truncator
register = template.Library()
#register.filter(name='excerpt')
def excerpt_with_ptag_spacing(value, arg):
try:
limit = int(arg)
except ValueError:
return 'Invalid literal for int().'
# remove spaces between tags
value = strip_spaces_between_tags(value)
# add space before each P end tag (</p>)
value = value.replace("</p>"," </p>")
# strip HTML tags
value = strip_tags(value)
# other usage: return Truncator(value).words(length, html=True, truncate=' see more')
return Truncator(value).words(limit)
and you use it like this..
{{ page.body|excerpt:50 }}
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 ;)