Dynamically referencing a variable in jinja2? - jinja2

Is it possible to dynamically reference a variable in jinja2?
{%- set varA = 5 -%}
{%- set varB = 10 -%}
{%- set input = 'A' -%}
{%- set output = 'var' + input -%}
Output: {{ output }}
{# I get "Output: varA", but I would like to get "Output: 5" #}
I can do it with an if/else statement, but want to see if i can do it dynamically, in case i have inputs A-Z, for example.
{%- if input == 'A' -%}
{%- set output = varA -%}
{%- else -%}
{%- set output = varB -%}
{%- endif -%}
Output: {{ output }}
I thought a macro might help, but seems to return as a string:
{%- macro doit(input) -%}
var{{input}}
{%- endmacro -%}
{%- set output = doit(input) -%}
Output: {{ output }}
{# Returns "Output: varA" #}

try that:
template = """
{%- set varA = 5 -%}
{%- set input = 'A' -%}
{%- set output = 'var' ~ input -%}
Output: {{ self._TemplateReference__context.resolve(output) }}
"""
temp = jinja2.Template(template)
result = temp.render()
print(result)
result:
Output: 5

Related

Append string to array and join back into string

I'm using Home Assistant templates, which run on Jinja2 script.
I have a group of entities (states.group.doors) that have attribute battery_level. I want to build an array of entities with battery_level < min_battery level and display as a string separated by commas.
I can't figure out what's wrong with my syntax. Two questions:
Is there just a better way overall to create a list that is filtered for battery_level < min_battery_level rather than building an array like I am?
If not, then there must be something wrong with the way I am building this array. Can someone spot it?
Thanks for the help.
The following code does successfully detect battery_level < 98 and display true if anything meets that criteria, so I'm almost there.
{% set min_battery_level = 98 -%}
{% set ns = namespace(found=false, entities=[]) -%}
{% set entities = [] -%}
{% for entity_id in states.group.doors.attributes.entity_id -%}
{% set parts = entity_id.split('.') -%}
{% if (state_attr(entity_id, 'battery_level') | replace("%","") | int) < min_battery_level -%}
{% set ns.found = true -%}
{% set entities = entities + [entity_id] -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
{{ entities | join(' ') }}
Welp... kept playing with it and got it working as follows:
{% set min_battery_level = 98 -%}
{% set ns = namespace(found=false, entities = []) -%}
{% for entity_id in states.group.doors.attributes.entity_id -%}
{% set name = state_attr(entity_id, 'friendly_name') | string -%}
{% set battery = state_attr(entity_id, 'battery_level') | replace("%","") | int -%}
{% if (battery) < min_battery_level -%}
{% set ns.found = true -%}
{% set ns.entities = ns.entities + [name+' ('+battery|string+'%)'] -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
{{ ns.entities | join(', ') }}

(Shopify) How to update my inventory message on variant change using current theme JSON + AJAX setup

Background:
I am currently helping a company move their current website to Shopify and am trying to add a custom feature to the product page. I am a web designer, not a developer so I am having trouble implementing this.
Here is my situation:
At this point, our theme allows us to display an "In stock & ready to ship" or an "Out of stock" message on our product page if the variant.inventory_quantity is great than 0 or equal to 0, respectively. In my case, I have added another option that will display a message if the following is true:
section.settings.show_special_order_option and selected_variant.inventory_management and selected_variant.inventory_policy == 'continue' and selected_variant.inventory_quantity == 0
In this case, section.settings.show_special_order_option is a checkbox option I created that when checked will [ideally] display the message. I would like this message to only be displayed if the above conditions are met, and update as each new variation is selected in the event that the conditions are not met.
As of now, this is my code in the product-info.liquid section:
{%- if selected_variant.available -%}
{%- if selected_variant.inventory_management and selected_variant.inventory_policy == 'deny' and section.settings.low_inventory_threshold > 0 -%}
{%- if selected_variant.inventory_quantity <= section.settings.low_inventory_threshold -%}
<span class="product-form__inventory inventory inventory--low">{{ 'product.form.low_stock_with_quantity_count' | t: count: selected_variant.inventory_quantity }}</span>
{%- else -%}
<span class="product-form__inventory inventory inventory--high">{{ 'product.form.in_stock_with_quantity_count' | t: count: selected_variant.inventory_quantity }}</span>
{%- endif -%}
{%- elsif section.settings.show_special_order_option and selected_variant.inventory_management and selected_variant.inventory_policy == 'continue' -%}
{%- if selected_variant.inventory_quantity == 0 -%}
<span class="product-form__inventory inventory inventory--special-order">{{ 'product.form.no_stock_special_order' | t }}</span>
{%- else -%}
<span class="product-form__inventory inventory inventory--high">{{ 'product.form.in_stock' | t }}</span>
{%- endif -%}
{%- else -%}
<span class="product-form__inventory inventory inventory--high">{{ 'product.form.in_stock' | t }}</span>
{%- endif -%}
{%- else -%}
<span class="product-form__inventory inventory">{{ 'product.form.sold_out' | t }}</span>
{%- endif -%}
And this is my JSON Script:
<script type="application/json" data-product-json>
{
"product": {{ product | json }},
"options_with_values": {{ product.options_with_values | json }},
"selected_variant_id": {{ selected_variant.id }}
{%- if section.settings.show_inventory_quantity -%}
,"inventories": {
{%- for variant in product.variants -%}
{%- if variant.available -%}
{%- if variant.inventory_management and variant.inventory_policy == 'deny' and section.settings.low_inventory_threshold > 0 -%}
{%- if variant.inventory_quantity <= section.settings.low_inventory_threshold -%}
{%- capture inventory_message -%}{{ 'product.form.low_stock_with_quantity_count' | t: count: variant.inventory_quantity }}{%- endcapture -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.in_stock_with_quantity_count' | t: count: variant.inventory_quantity }}{%- endcapture -%}
{%- endif -%}
{%- elsif section.settings.show_special_order_option and selected_variant.inventory_management and selected_variant.inventory_policy == 'continue' -%}
{%- if selected_variant.inventory_quantity == 0 -%}
{%- capture inventory_message -%}{{ 'product.form.no_stock_special_order' | t }}{%- endcapture -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.in_stock' | t }}{%- endcapture -%}
{%- endif -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.in_stock' | t }}{%- endcapture -%}
{%- endif -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.sold_out' | t }}{%- endcapture -%}
{%- endif -%}
"{{ variant.id }}": {
"inventory_management": {{ variant.inventory_management | json }},
"inventory_policy": {{ variant.inventory_policy | json }},
"inventory_quantity": {{ variant.inventory_quantity | json }},
"inventory_message": {{ inventory_message | json }}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
}
{%- endif -%}
}
</script>
And finally this is the JS snippet that is executed when there is a variation change:
function _updateinven(newVariant) {
if (!this.options['showInventoryQuantity'] || !newVariant) {
return;
}
var productFormInventoryElement = this.element.querySelector('.product-form__inventory'),
variantInventoryManagement = this.variantsInventories[newVariant['id']]['inventory_management'],
variantInventoryPolicy = this.variantsInventories[newVariant['id']]['inventory_policy'],
variantInventoryQuantity = this.variantsInventories[newVariant['id']]['inventory_quantity'],
variantInventoryMessage = this.variantsInventories[newVariant['id']]['inventory_message'];
productFormInventoryElement.classList.remove('inventory--high');
productFormInventoryElement.classList.remove('inventory--low');
productFormInventoryElement.classList.remove('inventory--special-order');
if (newVariant['available']) {
if (null !== variantInventoryManagement && variantInventoryPolicy === 'deny' && this.options['lowInventoryThreshold'] > 0) {
if (variantInventoryQuantity <= this.options['lowInventoryThreshold']) {
productFormInventoryElement.classList.add('inventory--low');
} else {
productFormInventoryElement.classList.add('inventory--high');
}
} else if (variantInventoryQuantity === 0) {
productFormInventoryElement.classList.add('inventory--special-order');
} else {
productFormInventoryElement.classList.add('inventory--high');
}
}
// We also need to update the stock countdown if setup
var stockCountdown = this.element.querySelector('.inventory-bar');
if (stockCountdown) {
var stockCountdownProgress = Math.min(Math.max(variantInventoryQuantity / parseInt(stockCountdown.getAttribute('data-stock-countdown-max')) * 100.0, 0), 100);
stockCountdown.classList.toggle('inventory-bar--hidden', stockCountdownProgress === 0);
stockCountdown.firstElementChild.style.width = stockCountdownProgress + '%';
}
productFormInventoryElement.innerHTML = variantInventoryMessage;
}
How the function currently works:
As of now, when clicking on a product that is available and has inventory greater than 0, the "In stock & ready to ship" message appears in green (the class 'inventory--high' is what makes it green). When clicking on another variation that is out of stock but set to 'Continue selling when out of stock' the message stays the same, but the color changes to orange (the class 'inventory--special-order' is what makes it orange).
What appears to be the problem:
I have narrowed this problem down to the liquid function and/or JSON script. When viewing the page source, I look at the computed JSON script and it is displaying the same message for all variations when it should be displaying unique messages depending on the conditions set forth. (In other words, every variation in the JSON script has the same inventory message of "In stock & ready to ship" or "Special order: this product will take 7-10 days to arrive" even if there multiple variations in stock and multiple not in stock but able to continue selling).
My question:
Assuming I have provided all the necessary information, I am in need of some guidance as to correct my function to display the proper inventory message for each variation as the user selects different variations. Could someone please help me solve this problem? Please let me know if you need any additional information from me.

Jekyll: link next/previous sorted posts within a category

I need to generate two buttons linking to the next and the previous posts from the same category. Posts are sorted with the front-matter order and an integer value.
My solution is still not perfect. I need to exclude the previous button for the first post and the next button for the last post. However, it's not working and I can't understand why. This is my code:
{% capture the_cat %}{{page.categories | first}}{% endcapture %}
{%- assign sorted_posts = site.categories[the_cat] | sort: 'order' -%}
{%- for post in sorted_posts -%}
{% if post.url == page.url %}
{% assign post_index0 = forloop.index0 %}
{% assign post_index1 = forloop.index | plus: 1 %}
{% endif %}
{%- endfor -%}
{%- for post in sorted_posts -%}
{% if post_index0 == post.order %}
{% assign prev_post = post %}
{% endif %}
{% if post_index1 == post.order %}
{% assign next_post = post %}
{% endif %}
{%- endfor -%}
And finally...
{%- if prev_post != null -%} ... {%- endif -%}
{%- if next_post != null -%} ... {%- endif -%}
The main loop seems correct. In a category with 3 posts sorted, it returns 1, 2, 3. How can I fix it? Could be fixed with only one loop, making the code more efficient? Thanks!
PD: I used this plugin successfully, however this plugin sorts posts by date, not by order.
Finally, I got the solution:
{%- capture the_cat -%}{{page.categories | first}}{%- endcapture -%}
{%- assign sorted_posts = site.categories[the_cat] | sort: 'order' -%}
{%- for post in sorted_posts -%}
{%- if post.url == page.url -%}
{%- assign currIndex = forloop.index0 -%}
{%- assign prevIndex = currIndex | minus: 1 -%}
{%- assign nextIndex = currIndex | plus: 1 -%}
{%- assign articleIndexLength = forloop.length | minus: 1 -%}
{%- if currIndex == articleIndexLength -%}
{%- assign prev_post = sorted_posts[prevIndex] -%}
{%- endif -%}
{%- if currIndex < articleIndexLength and currIndex != 0 -%}
{%- assign prev_post = sorted_posts[prevIndex] -%}
{%- assign next_post = sorted_posts[nextIndex] -%}
{%- endif -%}
{%- if currIndex == 0 -%}
{%- assign next_post = sorted_posts[nextIndex] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
I only needed one loop with three conditionals.

Assign products to collection based on size

I created a new collection template and I want to assign to it all products that have 'XL' size in their size variant.
I started from create and array of sizes for every product but now I'm stuck.
{% assign sizes = '' %}
{% for variant in product.variants %}
{% if variant.available %}
{% assign sizes = sizes | append: variant.options[0] | append: '_' %}
{% endif %}
{% endfor %}
{% assign sizesArr = sizes | split: '_' | uniq %}
What should I do next for showing only products that contains 'XL'?
It depends what do you mean by all products.
By default collection can show up to 50 products but you can overwrite this with a paginate object.
So if you have under 50 products you can just do this.
{%- for product in collection.products -%}
{%- assign show_product = false -%}
{%- for option in product.options_with_values -%}
{%- for value in option.values -%}
{%- assign value_handle = value | handle -%}
{%- if value_handle == 'xl' -%}
{%- assign show_product = true -%}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
{%- if show_product -%}
Add your product here
{%- endif -%}
{%- endfor -%}
If you have more than 50 products you will need to wrap the above code in paginate object:
{% paginate collection.products by 9999 %}
{%- for product in collection.products -%}
{%- assign show_product = false -%}
{%- for option in product.options_with_values -%}
{%- for value in option.values -%}
{%- assign value_handle = value | handle -%}
{%- if value_handle == 'xl' -%}
{%- assign show_product = true -%}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
{%- if show_product -%}
Add your product here
{%- endif -%}
{%- endfor -%}
{% endpaginate %}
But this request will take a toll on the server and slow down your site. So please consider this issue as well.

Jinja template list and dict parsing

I'm trying to get this template to work to create an HAProxy config file. It's almost there, but I can't seem to get the lists in here correctly. I want to save the key and prepend that to the list of items, for example:
This:
acl:
- host_static hdr_beg(host) -i static
- url_static path_beg static
Should be:
acl host_static hdr_beg(host) -i static
acl url_static path_beg static
This would be any dict key that has a list for their value.
The carryover is there because there could be multiple frontends and backends and those would be a dict under frontend.
I was able to update it, probably a better way to do this, but seems to work ok:
Edit (Updated Working Code)
{%- set key = 0 -%}
{%- set value = 1 -%}
{%- set carryovers = ['frontend', 'backend','listen'] -%}
{%- macro haproxy_config(data, carryover='', listkey='', recurse=-1, indent=0) -%}
{%- set recurse = recurse + 1 -%}
{%- if data is none -%}
{{- '\n' -}}
{%- elif data is string or data is number -%}
{{- '%s %s'|format(listkey,data)|string|indent(indent, True) }}{{ '\n' -}}
{%- else -%}
{%- if recurse > 0 -%}
{{- '\n' -}}
{%- set indent = indent + 2 -%}
{%- endif -%}
{%- if data is mapping -%}
{%- for item in data|dictsort -%}
{%- if item[key] in carryovers -%}
{{- haproxy_config(item[value], carryover=item[key], indent=indent) -}}
{%- else -%}
{%- set carryIndent = indent -%}
{%- set forwardIndent = indent -%}
{%- if carryover -%}
{{- carryover|indent(indent, True) }}{{ ' ' -}}
{%- set carryIndent = 0 -%}
{%- endif -%}
{%- if item[value] is string or item[value] is not iterable -%}
{%- set forwardIndent = 0 -%}
{%- endif -%}
{%- if item[value] is not string and item[value] is iterable and item[value] is not mapping -%}
{%- set forwardIndent = 0 -%}
{{ haproxy_config(item[value], listkey=item[key], recurse=recurse, indent=forwardIndent) -}}
{%- else -%}
{{- item[key]|indent(carryIndent, True) }} {{ haproxy_config(item[value], recurse=recurse, indent=forwardIndent) -}}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- for item in data -%}
{{- haproxy_config(item, listkey=listkey, indent=indent) -}}
{%- endfor -%}
{%- endif -%}
{%- if recurse > 0 -%}
{{ '\n' }}
{%- endif -%}
{%- endif -%}
{%- endmacro -%}
=======================================================================
Template:
{%- set key = 0 -%}
{%- set value = 1 -%}
{%- set carryovers = ['frontend', 'backend',
'listen'] -%}
{%- macro haproxy_config(data, carryover='', recurse=-1, indent=0) -%}
{%- set recurse = recurse + 1 -%}
{%- if data is none -%}
{{- '\n' -}}
{%- elif data is string or data is number -%}
{{- data|string|indent(indent, True) }}{{ '\n' -}}
{%- else -%}
{%- if recurse > 0 -%}
{{- '\n' -}}
{%- set indent = indent + 2 -%}
{%- endif -%}
{%- if data is mapping -%}
{%- for item in data|dictsort -%}
{%- if item[key] in carryovers -%}
{{- haproxy_config(item[value], carryover=item[key], indent=indent) -}}
{%- else -%}
{%- set carryIndent = indent -%}
{%- set forwardIndent = indent -%}
{%- if carryover -%}
{{- carryover|indent(indent, True) }}{{ ' ' -}}
{%- set carryIndent = 0 -%}
{%- endif -%}
{%- if item[value] is string or item[value] is not iterable -%}
{%- set forwardIndent = 0 -%}
{%- endif -%}
{{- item[key]|indent(carryIndent, True) }} {{ haproxy_config(item[value], recurse=recurse, indent=forwardIndent) -}}
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- for item in data -%}
{{- haproxy_config(item, indent=indent) -}}
{%- endfor -%}
{%- endif -%}
{%- if recurse > 0 -%}
{{ '\n' }}
{%- endif -%}
{%- endif -%}
{%- endmacro -%}
Values
haproxy:
global:
stats: socket /var/lib/haproxy/stats mode 660 level admin
ssl-default-bind-ciphers: "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384"
ssl-default-bind-options: "no-sslv3 no-tlsv10 no-tlsv11"
user: haproxy
group: haproxy
chroot: /var/lib/haproxy
tune.something.else: 2048
frontend:
http:
bind: 0.0.0.0:80
option: http-server-close
acl:
- host_static hdr_beg(host) -i static
- url_static path_beg static
#use_backend: static if host_static
#use_backend: static if url_static
default_backend: www
backend:
www:
balance: roundrobin
#server: www1 www1 check port 80
#server: www2 www2 check port 80
# server: www3 www3 check port 80
# server: load1 localhost:8080 backup
# static:
# server: media1 media1 check port 80
# server: media2 media2 check port 80
# server: load1 localhost:8080 backup
Output
backend www
balance roundrobin
frontend http
host_static hdr_beg(host) -i static
url_static path_beg static
bind 0.0.0.0:80
default_backend www
option http-server-close
global
chroot /var/lib/haproxy
daemon True
group haproxy
log
/dev/log local0
/dev/log local1 notice
ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
stats socket /var/lib/haproxy/stats mode 660 level admin
tune.something.else 2048
user haproxy
What I want
backend www
balance roundrobin
frontend http
acl host_static hdr_beg(host) -i static
acl url_static path_beg static
bind 0.0.0.0:80
default_backend www
option http-server-close
global
chroot /var/lib/haproxy
daemon True
group haproxy
log /dev/log local0
log /dev/log local1 notice
ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
stats socket /var/lib/haproxy/stats mode 660 level admin
tune.something.else 2048
user haproxy
Thanks
I think you need another section in the if else block {%- if data is mapping -%} that checks if it is a list and uses the following.
{%- for key, value in data.items() %}
{{key}} {{value}}
{%- endfor %}
possibly this
{%- set key = 0 -%}
{%- set value = 1 -%}
{%- set carryovers = ['frontend', 'backend',
'listen'] -%}
{%- macro haproxy_config(data, carryover='', recurse=-1, indent=0) -%}
{%- set recurse = recurse + 1 -%}
{%- if data is none -%}
{{- '\n' -}}
{%- elif data is string or data is number -%}
{{- data|string|indent(indent, True) }}{{ '\n' -}}
{%- else -%}
{%- if recurse > 0 -%}
{{- '\n' -}}
{%- set indent = indent + 2 -%}
{%- endif -%}
{%- if data is mapping -%}
{%- for item in data|dictsort -%}
{%- if item[key] in carryovers -%}
{{- haproxy_config(item[value], carryover=item[key], indent=indent) -}}
{%- else -%}
{%- set carryIndent = indent -%}
{%- set forwardIndent = indent -%}
{%- if carryover -%}
{{- carryover|indent(indent, True) }}{{ ' ' -}}
{%- set carryIndent = 0 -%}
{%- endif -%}
{%- if item[value] is string or item[value] is not iterable -%}
{%- set forwardIndent = 0 -%}
{%- endif -%}
{{- item[key]|indent(carryIndent, True) }} {{ haproxy_config(item[value], recurse=recurse, indent=forwardIndent) -}}
{%- endif -%}
{%- endfor -%}
{%- elif data is iterable and data is not string %}
{%- for key, value in data.items() %}
{{- '%s %s'|format(key, value)|indent(indent, True) }}
{%- endfor %}
{%- else -%}
{%- for item in data -%}
{{- haproxy_config(item, indent=indent) -}}
{%- endfor -%}
{%- endif -%}
{%- if recurse > 0 -%}