How can I concatenate a string using Jinja for loop? - jinja2

I am trying to iteratively concatenate a string to build url params with a 'for' loop, but I believe I am having scoping issues.
The output should be: url_param = "&query_param=hello&query_param=world"
array_of_objects = [{'id':'hello'},{'id':'world'}]
{% set url_param = "" %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%} // correctly returns [1,2]
{% for id in array_of_ids %}
{% set param = '&query_param='~id %}
{% set url_param = url_param~param %}
{% endfor %}
//url_param is still an empty string
I also tried namespace(), but to no avail:
{% set ns = namespace() %}
{% set ns.output = '' %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%} // correctly returns [1,2]
{% for id in array_of_ids %}
{% set param = '&industries='~id%}
{% set ns.output = ns.output~param %}
{% endfor %}
//ns.output returns namespace

That is indeed a scope issue. One "hacky" way of dealing with this is using a list that you append to like so:
{% set array_of_objects = [{'id':'hello'},{'id':'world'}] %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%}
{{ array_of_ids|pprint }} {# output: ['hello', 'world'] #}
{% set ids = [] %} {# Temporary list #}
{% for id in array_of_ids %}
{% set param = '&query_param='~id %}
{% set url_param = url_param~param %}
{{ ids.append(url_param) }}
{% endfor %}
{{ ids|pprint }} {# output: [u'&query_param=hello', u'&query_param=world'] #}
{{ ids|join|pprint }} {# output: "&query_param=hello&query_param=world" #}
The above gets you what you need, but for this specific example I would take a look at using jinja's join filter. It's more declarative and feels a little less hacky.
{% set array_of_objects = [{'id':'hello'},{'id':'world'}] %}
{# set to a variable #}
{% set query_string = "&query_param=" ~ array_of_objects|join("&query_param=", attribute="id") %}
{{ query_string|pprint }}
{# output: u'&query_param=hello&query_param=world' #}
{# or just use it inline #}
{{ "&query_param=" ~ array_of_objects|join("&query_param=", attribute="id") }}

You should change the initialization of your namespace.
Here is an example from the docs that will help you out:
{% set ns = namespace(found=false) %}
{% for item in items %}
{% if item.check_something() %}
{% set ns.found = true %}
{% endif %}
* {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}

Related

Store result of a query in a variable ( jinja)

I am trying to put the result of a query in a variable but it doesn't work.
I am not sure what to do so it returns 0 as expected. Any ideas? I am using dbt and jinja.
With the below code the results_list variable is (Decimal('0'),))
MACRO
{% macro source_freshness(model, column_name) %}
{% set freshness_query %}
SELECT COUNT 0 AS count
{% endset %}
{% set results = run_query(freshness_query) %}
{% if execute %}
{% set results_list = results.columns[0].values() %}
{% else %}
{% set results_list = [] %}
{% endif %}
{{ return(results_list) }}
{% endmacro %}
call in a model:
{% set freshness_query_test = source_freshness(ref('model'),'date') %}
{% if count in freshness_query_test == 0 %}
do this
{% else %}
do that
{% endif %}
Thank you!
thanks for your help with this. I have not been able to find a direct answer but what I have done is to add the macro in a separate model, and then use the call statement logic in the shared answer Hi, how do we define select statement as a variable in dbt?

Unable to use concat on `nil`

I am trying to build a related post include file for my Jekyll site. The site is based around the concept of members, attractions and parks (each as collections). Each post has a many to many relationships. I am trying to up a combined array of each of the page attributes (members, attractions and parks) loop through the array and find posts with a common number of tags.
It's quite simple but I am getting stuck with one section, not all the posts have members, attractions and parks fields so they are returning nil but the concat filter requires an array. I am trying to default the variable to an [] but it always gets set to nil. Any ideas?
Here's the code:
<ul class="row">
{% assign pageTags = [] %}{% if page.tags.first %}{% assign pageTags = page.tags %}{% endif %}
{% assign pageAttractions = [] %}{% if page.attractions.first %}{% assign pageAttractions = page.attractions %}{% endif %}
{% assign pageMembers = [] %}{% if page.members.first %}{% assign pageMembers = page.members %}{% endif %}
{% assign pageParks = [] %}{% if page.parks.first %}{% assign pageParks = page.parks %}{% endif %}
{% assign pageTagList = pageTags | concat: pageAttractions | concat: pageMembers | concat: pageParks %}
{% for post in site.documents %}
{% assign sameTagCount = 0 %}
{% assign commonTags = '' %}
{% assign postTags = [] %}{% if post.tags %}{% assign postTags = post.tags %}{% endif %}
{% assign postAttractions = [] %}{% if post.attractions %}{% assign postAttractions = post.attractions %}{% endif %}
{% assign postMembers = [] %}{% if post.members %}{% assign postMembers = post.members %}{% endif %}
{% assign postParks = [] %}{% if post.parks %}{% assign postParks = post.parks %}{% endif %}
{% assign postTageList = postTags | concat: postAttractions | concat: postMembers | concat postParks %}
{% if post.hidden == true %}
{% break %}
{% endif %}
{% for tag in postTageList %}
{% if post.url != page.url %}
{% if pageTagList contains tag %}
{% assign sameTagCount = sameTagCount | plus: 1 %}
{% capture tagmarkup %} <span class="label label-default">{{ tag }}</span> {% endcapture %}
{% assign commonTags = commonTags | append: tagmarkup %}
{% endif %}
{% endif %}
{% endfor %}
{% if sameTagCount >= minCommonTags %}
<li class="col-lg-4 col-md-12">
<div class="main-image">
</div>
<h5>{{ post.categories | first }}</h5>
<h3>{{ post.title | replace: 'Review', '' }}</h3>
<p>
{% if post.description %}
{{ post.description }}
{% else %}
{{ post.content | markdownify | strip_html | truncatewords: 20 }}
{% endif %}
</p>
<p>
Read Article →
</p>
</li>
{% assign maxRelatedCounter = maxRelatedCounter | plus: 1 %}
{% if maxRelatedCounter >= maxRelated %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
</ul>
You can see the repo here: https://github.com/dtsn/jungleskipper/blob/feature/members/_includes/related-posts.html
From the Liquid documentation:
You cannot initialize arrays using only Liquid.
You can, however, use the split filter to break a string into an array of substrings.
You should look at compact which removes any nil values from an array.
Here is a link to the doc on shopify.
Example from Liquid documentation
Input:
{% assign site_categories = site.pages | map: "category" %}
{% for category in site_categories %}
- {{ category }}
{% endfor %}
Output:
- business
- celebrities
-
- lifestyle
- sports
-
- technology
With Compact
Input:
{% assign site_categories = site.pages | map: "category" | compact %}
{% for category in site_categories %}
- {{ category }}
{% endfor %}
Output:
- business
- celebrities
- lifestyle
- sports
- technology

One variable for different collections in Jekyll to use in forloop

I have several collections on my Jekyll site. I've added post navigation to one of the collections displaying a counter on each post page:
{% assign testimonials = site.testimonials %}
{% assign page_order = 1 %}
{% for node in testimonials reversed %}
{% if node.url == page.url %}
{{ page_order }} from {{ forloop.length }}
{% else %}
{% assign page_order = page_order | plus: 1 %}
{% endif %}
{% endfor %}
I would like to make this code work not only for site.testimonials, but for other collections as well. I tried to pass a variable for collections like this:
{% capture label %}{{ page.collection }}{% endcapture %}
{% assign collection = site.collections | where: "label",label | first %}
{% for node in collection reversed %}
{% if node.url == page.url %}
{{ page_order }} from {{ forloop.length }}
{% else %}
{% assign page_order = page_order | plus: 1 %}
{% endif %}
{% endfor %}
But it doesn't work. Is there any way to pass a variable for all collections in Jekyll to use in forloop in post navigation?
When you access collection with site.testimonials, you get collection's documents array.
{{ site.testimonials | inspect }}
# output >> [#<Jekyll::Document ...>, #<Jekyll::Document ...>, ...]
When you access a collection while looping over site.collection, you receive the collection's object :
{% assign collection = site.collections | where: "label",page.collection | first %}
{{ collection | inspect }}
# output >> { "output": true, "label": "collectionLabel",
"docs": [ doc1, docs2, ... ], "files": [],
"directory": "/path/to/collection",
"relative_directory": "_colectionDirectory" }
In your case, you just have to replace :
{% for node in collection reversed %}
By :
{% for node in collection.docs reversed %}

Output Markup Inside of Tag Markup

My question is how can I rewrite this output markup (which does work)
{{ forloop.index0 | modulo:4 }}
so that it can be used inside of a tag markup (which doesn't work).
{% if forloop.index0 | modulo:4 == 0 %}
This is what ended up working (David helped me get started).
{% assign mod = forloop.index0 | modulo:4 %}
{% if mod == 0 %}
<!-- Do stuff -->
{% endif %}
Edited answer :
For this example I use a custom array, but the array can be site.pages or site.posts or site.data.somedatas.
{% assign words = "zero,one,two,three,four,five" | split: ',' %}
Then process the words array :
<ol>
{% for word in words %}
{% comment %} Here we assign the filtered array to myTest {% endcomment %}
{% assign myTest = forloop.index0 | modulo:4 %}
{% comment %} then we process the filtered array {% endcomment %}
{% if myTest == 0 %}
<li>
<h4>Test passing (index = {{ forloop.index0 }} >> modulo = {{ myTest }})</h4>
</li>
{% else %}
<li>
<h4>test NOT passing (index = {{ forloop.index0 }} >> modulo = {{ myTest }})</h4>
</li>
{% endif %}
{% endfor %}
</ol>
This works on Jekyll 2.2.0, the current Github pages version.

Pass variables from child template to parent in Jinja2

I want to have one parent template and many children templates with their own variables that they pass to the parent, like so:
parent.html:
{% block variables %}
{% endblock %}
{% if bool_var %}
{{ option_a }}
{% else %}
{{ option_b }}
{% endif %}
child.html:
{% extends "parent.html" %}
{% block variables %}
{% set bool_var = True %}
{% set option_a = 'Text specific to this child template' %}
{% set option_b = 'More text specific to this child template' %}
{% endblock %}
But the variables end up undefined in the parent.
Ah. Apparently they won't be defined when they are passed through blocks. The solution is to just remove the block tags and set it up like so:
parent.html:
{% if bool_var %}
{{ option_a }}
{% else %}
{{ option_b }}
{% endif %}
child.html:
{% extends "parent.html" %}
{% set bool_var = True %}
{% set option_a = 'Text specific to this child template' %}
{% set option_b = 'More text specific to this child template' %}
If Nathron's solution does not fix your problem, you can use a function in combination with a global python variable to pass a variable value.
Advantage: The variable's value will available in all templates. You can set the variable inside a block.
Disadvantage: More overhead.
This is what I did:
child.j2:
{{ set_my_var('new var value') }}
base.j2
{% set my_var = get_my_var() %}
python code
my_var = ''
def set_my_var(value):
global my_var
my_var = value
return '' # a function returning nothing will print a "none"
def get_my_var():
global my_var
return my_var
# make functions available inside jinja2
config = { 'set_my_var': set_my_var,
'get_my_var': get_my_var,
...
}
template = env.get_template('base.j2')
generated_code = template.render(config)
In some cases, you can avoid this 'parameter-passing' by creating another variant of the parent that adds/removes some block and extends it instead.
{% extends [condition]|yesno:'parent_1.html,parent_2.html' %}