Jinja2 template variable proper usage syntax - flask

I am trying to iterate a list to populate a variable to be used to set the value of a hidden field. See my code example below. I am able to iterate the list and concatenate the variable however, when I go to assign the contents of the variable to the hidden input value, there is nothing there. What is the proper method of doing this?
{% set hdnfiles = '' %}
{% if tr.files is not none and tr.files|length > 0 %}
{% for file in tr.files %}
{% if hdnfiles|length > 0 %}
{% set hdnfiles = hdnfiles ~ ";" ~ file %}
{% else %}
{% set hdnfiles = file %}
{% endif %}
{% endfor %}
{% endif %}
<input type="hidden" id="filesHidden" name="filesHidden" value="{{ hdnfiles }}"/>

set will not override the value on an outer scope. Scoping in Jinja works differently than in Python; control structures (such as if and for) have a different scope than their surrounding code (where the initial set was done). Way back in 2011, Jinja's author stated:
I will keep it in mind for the new compiler backend, but in the current one that is not possible to achieve for performance reasons.
Consider building this string in Python before rendering the template, rather that building it in the template.

Related

Detect if a forloop has zero loops

I'm trying to handle a case where a forloop with a where clause results in zero loops.
I've tried using set and map in various ways unsuccessfully, possibly one of those is the solution but I just couldn't get it right.
{% for variable in type.allVariables where variable.type.implements["SomeProtocol"]["name"] == "SomeProtocol" %}
// Add code for each variable
{% endfor %}
// Add backup code if forloop didn't do a single loop
Found the answer in the stencil documentation of all places - who knew!
The for tag can take an optional {% empty %} block that will be displayed if the given list is empty or could not be found.
{% for user in users %}
<li>{{ user }}</li>
{% empty %}
<li>There are no users.</li>
{% endfor %}

using a loop result as a variable for child loop

I think the best way to describe is problem is with an example.
{% for content in contents %}
{% for stuff in {{content}} %}
{{stuff}}
{% endfor %}
{% endfor %}
I am using google app engine webapp templates. I can't seem to use a result from the parent forloop {{content}} as a variable for its child forloop. TemplateSyntaxError: Could not parse the remainder: '{{content}}' from '{{content}}' Is it possible to do this? Thanks!!
You can use only content without braces around:
{% for content in contents %}
{% for stuff in content %}
{{ stuff }}
{% endfor %}
{% endfor %}
When you are inside the first for-loop, content exists in the context, as any other variable. Same thing for stuff in the inner loop. Plus, blocks are generally using argument as variables, except in it is surrounded by quotes.
The {{ }} notation can be use to only display the variable in the document.

truncate characters in a HTML element - Django Templates

In my django templates i have a list of objects which is rendered as follows:
<li class="keys">
{% for key in job.key_list|slice:":2" %}
{% if not forloop.last %}
{{ key }},
{% else %}
{{ key }}
{% endif %}
{% endfor %}
</li>
This outputs the list as:
some_key, some_key_two
I want to truncate the number of characters to 20 but on the whole list. So that it renders something like:
some_key, some_key_t...
I am aware about the truncatechars and truncatewords filters available in django but they work on a string variable within the template.
How do i go about implementing this functionality ?
Wow, that's a strange requirement. You would have to implement this as a template tag or custom filter that takes a list and transforms it.
#register.filter(name='truncatinator')
def truncatinator(value, arg):
strings = ",".join(value)
if len(strings) >= arg:
part = strings[0:19] + "..."
return part.split(',')
and use it like this {% for object in job.key_list|truncatinator:"20" %}
What you will notice though is that you will loop over a string list here instead of your objects which will give you this disadvantage: You won't have access to your .id.
This could be modified though as well.
I would do this with Javascript instead but I don't think that Django should be responsible for this overall.

django template: how to enclose a variable

I would like to make a sequence of variables within a for loop such as name0, name1, .... How do I do that? Thanks.
{% for i in '1234567890' %}
{% if name{{forloop.counter0}} %}
...
{% endif %}
...
{{name{{forloop.counter0}}}}
...
{% endfor %}
is as simple as
{{ name }}{{ forloop.counter0 }}
for the if, you should use the "with" statement:
{% with name|add:forloop.counter0 as if_test %}
{% if if_test %}
... <!-- do whatever you need to do here -->
all this must be inside your for loop
As you can see, the Django templating language tries hard to keep you from doing what you're trying to do, encouraging you to do your data processing in your view code, instead of your templates. For your example, in your view code, you might try doing:
context['names'] = [name for name in names[:10]]
...instead of creating individual variables for each name.
Then in your template:
{% for name in names %}
{% if name %}
...
{% endif %}
...
{{name}}
...
{% endfor %}
As far as I can tell, that would have the same effect as your code, but you would be doing your aggregation of the names in the view, instead of the template. If I'm reading the intent of your code wrongly, please provide more context, but it doesn't seem like you're doing anything that requires template logic.

Django template for loop. Member before

I want to create such loop:
{% for object in objects %}
{% if object.before != object %}
{{ object }} this is different
{% else %}
{{ object }} this is the same
{% endfor %}
Based on https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#for I can't. Is there really no simple way to do this? Or I just need to use counter and check for objects[counter-1]?
P.S. .before is theoretical and objects is simple query list. I want to take and do something with the loop member that encountered before current loop member.
Check ifchanged template tag
There is a "simple way" to do this: write a custom template tag. They're really not hard. This would probably do the trick (untested):
#register.simple_tag
def compare_objects(object_list):
comparisons = []
for i in range(1, len(object_list)):
if object_list[i] > object_list[i-1]:
comparisons.append('bigger')
else:
comparisons.append('smaller')
return comparisons
The built-in template tags and filters don't make it easy (as of Django 1.4), but it is possible by using the with tag to cache variables and the add, slugify, and slice filters to generate a new list with only one member.
The following example creates a new list whose sole member is the previous member of the forloop:
{% for item in list %}
{% if not forloop.first %}
{% with forloop.counter0|add:"-1" as previous %}
{% with previous|slugify|add:":"|add:previous as subset %}
{% with list|slice:subset as sublist %}
<p>Current item: {{ item }}</p>
<p>Previous item: {{ sublist.0 }}</p>
{% endwith %}
{% endwith %}
{% endwith %}
{% endif %}
{% endfor %}
This isn't an elegant solution, but the django template system has two faults that make this hack unavoidable for those who don't what to write custom tags:
Django template syntax does not allow nested curly parenthesis. Otherwise, we could do this:
{{ list.{{ forloop.counter|add:-1 }} }}
The lookup operator does not accept values stored using with (and perhaps for good reason)
{% with forloop.counter|add:-1 as index %}
{{ list.index }}
{% endwith %}
This code should work just fine as a django template, as long as object has a property or no-argument method called before, and objects is iterable (and '<' is defined).
{% for object in objects %}
{% if object.before < object %}
this is bigger
{% else %}
this is smaller
{% endfor %}