jinja2: track previous value in for loop - python

in a jinja2 for loop, how can I keep track of what the previous value of a variable was (for purposes of displaying breaks between "groups")? The obvious and straight-forward answer of:
{% set last_val='unk' %}
{% for object in data %}
{% if object[0]!=last_val %}
<output whatever separation code>
{% set last_val=object[0] %}
{% endif %}
<other stuff>
{% endfor %}
...doesn't work due to jinja2's scoping rules - each new time through the loop sees the same 'unk'. How can I work around this limitation?
EDIT: I was poking around some older code of mine where I had done similar things, and apparently this DID work with jinja2 2.8, but broke sometime before 2.9.6. So I guess one solution would be to downgrade to 2.8 and just stay there.

Use "loop.previtem" .
{% for object in data %}
{% if loop.index0 ==0 %}
<output whatever separation code>
{% elif loop.index0 > 0 and loop.previtem[0] != object[0] %}
<output whatever separation code>
{% endif %}
<other stuff>
{% endfor %}

jinja is not able to change the value of variable ,but you can use dictionary to change value of key .
this will work .
{% set last_val={'key':'unk'} %}
{% for object in data %}
{% if object[0]!=last_val.key %}
<output whatever separation code>
{% set test = last_val.update({'unk':object[0]}) %}
{% endif %}
<other stuff>
{% endfor %}

Related

How do i specify a variable in side of django template file

So I am trying to declare a variable inside of my django templates file
{% with object = "{{object.id
}}" %}
{% for detail in details %}
{% if detail.post.id == {{object}} %}
{{detail.name}}
{% endif %}
{% endfor %}
{% endwith %}
I know that with is used to this job, but when i run this code it shows me this error: 'with' expected at least one variable assignment
Please help me. Thank You
The post_id is likely an int so you should specify object as {% with object=2 %}, you furthermore should not use double curly brackets in a template tag:
{% with object=2 %}
{% for detail in details %}
{% if detail.post_id == object %}
{{ detail.name }}
{% endif %}
{% endfor %}
{% endwith %}
It is however not a good idea to filter in the template, since this is not efficient, and requires more memory, normally you filter in the view.

How to check if instance of model exists in django template

I have a django app with a basic model (Job). Now in my template I would like to check if an instance exists of that model or not. I want to project a text if there is nothing yet to show, otherwise I'd like to show the model attributes.
Somehow like so (which obviously doesn't work):
{% if job.title != "" %}
{{ job.title }}
{% else %}
hola
{% endif %}
Also tried:
{% for job in jobs %}
{% if job.title %}
{{ job.title }}
{% else %}
Hola
{% endif %}
{% endfor %}
It makes sense it doesn't work because how can I loop through it or return something if it doesn't exist. Is there a simple way to even do that in a template? Or do I have to write my own function? Or what would be a way to do that?
Help is of course very much appreciated
You can use the {% if %} tag. As Django doc says:
The {% if %} tag evaluates a variable, and if that variable is “true” (i.e. exists, is not empty, and is not a false boolean value) the contents of the block are output.
So you can do something like this:
{% if job %}
{{ job.title }}
{% else %}
<p>Hi from Uruguay</p>
{% endif %}
If you need this inside a for, as #dirkgroten said, you need to use the {% empty %} tag. There is an example in the Django doc.

Setting variable in Jinja for loop doesn't persist between iterations

I want to loop over a list of objects and count how many objects meet a requirement. I based my code off other examples I'd found, but it doesn't work, the count is always 0 after the loop.
For each house, I want to loop over each room and count how many rooms have a bed. I want to output that then reset the count for the next house.
{% for house in city %}
{% set count = 0 %}
<div>{{ house.address }} has {{ count }} beds in it rooms.</div>
{% for room in house %}
{% if room.has_bed == True %}{% set count = count + 1 %}{% endif %}
{% endfor %}
{% endfor %}
Jinja 2.10 introduces the namespace object to handle assignment and comparison in loops.
{% set ns = namespace(beds=0) %}
{% for room in house %}
{% if room.has_bed %}
{% set ns.beds = ns.beds + 1 %}
{% endif %}
{% endfor %}
{{ house.address }} has {{ ns.beds }} beds.
Normally, set does not handle attributes, which is why the old answers mutate objects with methods instead. namespace has been special cased so setting attributes does work.
The reason the original counter didn't work is because of Jinja's scope rules. Unlike Python, most blocks are new scopes. set always defines a local variable, except in this new special case of namespace.attribute.
In this specific case, you can accomplish what you want with filters.
{% set beds = house.rooms|selectattr('has_bed')|length %}
{{ house.address }} has {{ beds }} beds.
However, there are cases where storing information across scopes is needed. For example, if you wanted to output some information in addition to incrementing the counter, it would make sense to use a namespace.
For Jinja 2.9, the scope behavior was fixed, invalidating code that worked in previous versions. The incremented value of count only lives within the scope of the loop. Their example involves setting variables, but the concept is the same:
Please keep in mind that it is not possible to set variables inside a block and have them show up outside of it. This also applies to loops. The only exception to that rule are if statements which do not introduce a scope. As a result the following template is not going to do what you might expect:
{% set iterated = false %}
{% for item in seq %}
{{ item }}
{% set iterated = true %}
{% endfor %}
{% if not iterated %} did not iterate {% endif %}
It is not possible with Jinja syntax to do this.
You will need to do a hacky-ish workaround in order to track count across iterations. Set a list, append to it, then count its length.
{% for house in city %}
{% set room_count = [] %}
{% for room in house %}
{% if room.has_bed %}
{% if room_count.append(1) %}{% endif %}
{% endif %}
{% endfor %}
<div>{{ house.address }} has {{ room_count|length }} beds.</div>
{% endfor %}
For Jinja <= 2.8, the code you've shown does work. However, this was due to incorrect behavior in Jinja's scope rules, which was fixed in 2.9.
{% for house in city %}
{% set count = 0 %}
{% for room in house %}
{% if room.has_bed %}
{% set count = count + 1 %}
{% endif %}
{% endfor %}
<div>{{ house.address }} has {{ count }} beds.</div>
{% endfor %}

Single print statement during loop iteration in Django

I am working on a Django template, and I need to print my statement once during the loop iteration. I've tried {% ifchanged %}, but it's not working inside two loops.
Using {% ifchanged %} works under a single loop, but I am trying this inside two loops.
For example:
{% for i in j %}
{% for k in j %}
{% ifchanged %}
//something here//
{% endifchanged %}
{% endfor %}
{% endfor %}
However, in this case it's not working.
There’s also forloop.first.
See the Django Built-in template tags and filters documentation for the variables associated with forloops.
{% if forloop.first %}
// something here //
{% endif %}
There is also a forloop.last, if it needs to show up at the end, and a forloop.counter.

Next field django forms [duplicate]

I want to put break and continue in my code, but it doesn't work in Django template. How can I use continue and break using Django template for loop. Here is an example:
{% for i in i_range %}
{% for frequency in patient_meds.frequency %}
{% ifequal frequency i %}
<td class="nopad"><input type="checkbox" name="frequency-1" value="{{ i }}" checked/> {{ i }} AM</td>
{{ forloop.parentloop|continue }} ////// It doesn't work
{ continue } ////// It also doesn't work
{% endifequal %}
{% endfor%}
<td class="nopad"><input type="checkbox" name="frequency-1" value="{{ i }}"/> {{ i }} AM</td>
{% endfor %}
Django doesn't support it naturally.
You can implement forloop|continue and forloop|break with custom filters.
http://djangosnippets.org/snippets/2093/
For-loops in Django templates are different from plain Python for-loops, so continue and break will not work in them. See for yourself in the Django docs, there are no break or continue template tags. Given the overall position of Keep-It-Simple-Stupid in Django template syntax, you will probably have to find another way to accomplish what you need.
For most of cases there is no need for custom templatetags, it's easy:
continue:
{% for each in iterable %}
{% if conditions_for_continue %}
<!-- continue -->
{% else %}
... code ..
{% endif %}
{% endfor %}
break use the same idea, but with the wider scope:
{% set stop_loop="" %}
{% for each in iterable %}
{% if stop_loop %}{% else %}
... code ..
under some condition {% set stop_loop="true" %}
... code ..
{% endif %}
{% endfor %}
if you accept iterating more than needed.
If you want a continue/break after certain conditions, I use the following Simple Tag as follows with "Vanilla" Django 3.2.5:
#register.simple_tag
def define(val=None):
return val
Then you can use it as any variable in the template
{% define True as continue %}
{% for u in queryset %}
{% if continue %}
{% if u.status.description == 'Passed' %}
<td>Passed</td>
{% define False as continue %}
{% endif %}
{% endif %}
{% endfor %}
Extremely useful for any type of variable you want to re-use on template without using with statements.

Categories