concat string and for iteration using jinja2 - jinja2

I would like to create a variable name using a concatenation of a string and for iteration:
{% for iteration in my_array %}
{% set my_var = 'my_string_' + loop.index0|string %}
{{ my_var }}
{% endfor %}
in my python file with data:
templateVars={
'my_string_0': 'test with 0',
'my_string_1': 'test with 1'
}
outputText = template.render(templateVars)
but I don't get 'test with 0' I get 'my_string_0'

'my_string_' is just a string, not a variable. Since your "variable" names are just named with incremental numbers, you should make it a list instead:
outputText = template.render(my_array=['test with 0', 'test with 1'])
so you can iterate through the list in the template:
{% for item in my_array %}
{{ item }}
{% endfor %}

Related

String manipulation in DBT - Jinja

I want to develop a macro that will loop rows from a seeding query's result and create a dynamic query for another task.
Let's assume my row would be similar to
<agate.Row: ('product_available', Decimal('0.6'), 'Positive')>
<agate.Row: ('product_quality', Decimal('0.5'), 'Negative')>
I intend to generate an array selection for downstream queries which have my_udf that will take arguments from my seeding query's result. For example,
my_udf("product_available", 0.6, 'Positive')
my_udf("product_quality", 0.5, 'Negative')
The problem that I have is some arguments are actual column names, while others are values. Hence, column names should have double quotes, while value must have single quote. For instance, "product_available" vs 'Positive'
My code is
{% macro generate_list_select_from(seeding_query) %}
{# ... other code to execute my seeding query ... #}
{# loop query goes here #}
{% for i in results_list %}
{% set item = "my_udf( {{ i[0] }} , {{ i[1] }}, '{{ i[2] }}' )" %}
{{items.append(item)}}
{% endfor %}
{{ return(items) }}
{% endmacro %}
Below is output when I use my macro
select foo_column,
my_udf( {{ i[0] }} , {{ i[1] }}, '{{ i[2] }}' ),
my_udf( {{ i[0] }} , {{ i[1] }}, '{{ i[2] }}' )
from foo_table
My question is how to create a such string?
Update:
Tried other way, {% set item = "my_udf(" + {{i[0]}} + ")" %}, I end up with a compilation error expected token ':', got '}'
Don't nest your curlies.
~ is the string concatenation operator in jinja. You could use that to build up your function args, including the single quotes:
{% set item = "my_udf(" ~ i[0] ~ ", " ~ i[1] ~ ", '" ~ i[2] ~ "' )" %}
This is pretty hard to read, though. I'd probably add a macro called quoted:
{% macro quoted(s) %}
'{{ s }}'
{% endmacro %}
And then use the join filter to concatenate the items of a list:
{% set args = [i[0], i[1], quoted(i[2])] %}
{% set item = "my_udf(" ~ args | join(", ") ~ ")" %}
Lastly, if the purpose of this macro is to template SQL, then you don't need to use return(items). You should just have the body of the macro template the string you want (see the quoted macro above). That dramatically simplifies things:
{% macro quoted(s) %}
'{{ s }}'
{% endmacro %}
{% macro generate_list_select_from(seeding_query) %}
{# ... other code to execute my seeding query ... #}
{# loop query goes here #}
{% for i in results_list %}
{% set args = [i[0], i[1], quoted(i[2])] %}
my_udf({{ args | join(", "}}){% if not loop.last %},{% endif %}
{% endfor %}
{% endmacro %}

Find object by id in an array of JSON objects with Twig

I am being passed an array that unfortunately I cannot restructure:
"options": [
{"name":"namea","text":"valuea"},
{"name":"nameb","text":"valueb"},
{"name":"namec","text":"valuec"},
{"name":"named","text":"valued"}
]
I need to be able to find the object with the name equal to namea, nameb, namec, etc. and then produce the appropriate text. I tried the following and few other variations of that but could not get it to work:
{% for item in event.options %}
{% if item.name == "nameb" %}
{{ item.text|capitalize }}
{% endif %}
{% endfor %}
Thanks in advance for any help.
Edit: I basically only have access to a text box to input HTML and Twig. I do not have access to framework, custom extensions, etc.
Edit: JSON with exact formatting:
{
"source": "TEST.COM",
"trigger": "test",
"options": [{
"name":"test_date",
"value":"31-05-2017"
},
{
"name":"test_number",
"value":"9081003"
},
{
"name":"test_test",
"value":"9asd003"
},
{
"name":"Name",
"value":"Todd"
},
{
"name":"test_other",
"value":"kslkjsfd"
},
{
"name":"test_help",
"value":"908sdf3"
}]
}
This is going to be very hacky either way (I feel the javascript way as mentioned in the comments would still be less hacky).
However as its multilined we could write a basic parser like thing (this is by no means bullet proof and fails if the content has a ", or the structure changes or whatever changes to be honest):
{# create an array of lines #}
{% set event = event|split('\n') %}
{# an array to hold our options #}
{% set options = [] %}
{# using a bool to track if we are inside the options part #}
{% set inOptions = false %}
{# this holds the name of the last name value we have passed #}
{% set currentKey = '' %}
{# go through each line #}
{% for line in event %}
{# if we are inside the options tag do our magic #}
{% if inOptions %}
{# check if the line starts with "name": and track the current key name #}
{# or check if it starts with "value": and set the current key to that value #}
{% if line matches '/"name":/' %}
{# split line on the double quotes and get the last bit #}
{% set currentKey = line|split('"') %}
{# cant get the array index piped in one go idk.. #}
{% set currentKey = currentKey[3] %}
{% elseif line matches '/"value":/i' %}
{% set currentValue = line|split('"') %}
{% set options = options|merge({(currentKey): currentValue[3]}) %}
{% elseif line matches '/\s*}]/' %}
{% set inOptions = false %}
{% endif %}
{% endif %}
{# check for the options sectioning start #}
{% if line matches '/^"options/' %}
{% set inOptions = true %}
{% endif %}
{% endfor %}
{{ dump(options) }}
This returns in my dump (which you didn't have so you cant see):
array:6 [▼
"test_date" => "31-05-2017"
"test_number" => "9081003"
"test_test" => "9asd003"
"Name" => "Todd"
"test_other" => "kslkjsfd"
"test_help" => "908sdf3"
]
As an actual array where you can then call options.test_date but all this is super hacky, it still can be improved but twig is not made for it and the syntax gets so clunky its hard to maintain.

jekyll assign concat in a loop?

I would like to organize a page based on the number of pages that pass a filter.
I have tried to append truthy pages to a collection but it doesn't work.
{% assign homepage_posts = [] %}
{% for my_page in site.pages %}
{% if my_page.homepage %}
{% assign homepage_posts = homepage_posts | concat: [my_page] %}
{% endif %}
{% endfor %}
<h1>size{{homepage_posts.size}}</h1>
<h1>{{homepage_posts}}</h1>
This is not working. Does concat only work with strings?
Jekyll will use Liquid 4 soon. But, for now, no concat.
In your case you can :
Create an empty array (bracket notation doesn't work in liquid) : {% assign homepage_posts = "" | split:"/" %}
{{ homepage_posts | inspect }} --> output : []
And push elements in it :
{% for my_page in site.pages %}
{% if my_page.homepage %}
{% assign homepage_posts = homepage_posts | push: mypage %}
{% endif %}
{% endfor %}
{{ homepage_posts | inspect }}
concat filter only works with arrays and will be available in Jekyll when it upgrades to Liquid 4.*:
concat
Concatenates (combines) an array with another array. The resulting
array contains all the elements of the original arrays. concat will
not remove duplicate entries from the concatenated array unless you
also use the uniq filter.
To filter pages containing a specific attribute (in this case homepage: true) you can use a where filter.
Having a page with front matter:
---
homepage: true
---
Then you can have the pages with the homepage: true attribute like:
{% assign homepages = site.pages | where:"homepage","true" %}

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' %}

How to access sub level JSON trough Django

So, I get this JSON from Django:
{'something' :
{'value':'somethingName','editable':'false'}
},
{'somethingElse':
{'value':'somethingElseName','editable':'true'}
}
And show it like this:
{% for key, value in obj.items %}
{{ key }} : {{ value }}
{% endfor %}
The problem is {{ value }} returns {'value':'somethingName','editable':'false'}, and I can't access value or editable trough {{ value.value }} or {{ value.editable }}.
I'd like to show {{ value.value }} as somethingName instead of the entire JSON.
Is there a way to access 'sub-level' JSON trough Django itself?
You cannot use template variable name as a dictionary key using the . notation. The second value in value.value is not interpreted as a string value because you have a variable name value in the loop.
Just rename key and value to obj_key and obj_value respectively:
{% for obj_key, obj_value in obj.items %}
{{ obj_key }} : {{ obj_value.value }}
{% endfor %}
Demo:
>>> from django.template import Context, Template
>>> t = Template("""
... {% for obj_key, obj_value in obj.items %}
... {{ obj_key }} : {{ obj_value.value }}
... {% endfor %}""")
>>> obj = {'something' : {'value':'somethingName','editable':'false'}}
>>> t.render(Context({'obj': obj}))
u'something : somethingName'
Hope that helps.