Modifying the Convert Timezone macro in DBT to accept the timezone parameter from a column - jinja2

I’m trying to modify the convert_timezone macro from the dbt_date package so that I can supply the target timezone from a column in my dataset, but I can’t seem to figure out how to get it working. I’m relatively new to DBT and Jinja so hoping one of you brilliant people can help me figure it out!
The macro in its current form can be seen below. Thanks!
{%- macro convert_timezone(column, target_tz=None, source_tz=None) -%}
{%- set source_tz = "UTC" if not source_tz else source_tz -%}
{%- set target_tz = var("dbt_date:time_zone") if not target_tz else target_tz -%}
{{ adapter.dispatch('convert_timezone', 'dbt_date') (column, target_tz, source_tz) }}
{%- endmacro -%}
{% macro default__convert_timezone(column, target_tz, source_tz) -%}
{%- if not source_tz -%}
cast(convert_timezone('{{ target_tz }}', {{ column }}) as {{ dbt_utils.type_timestamp() }})
{%- else -%}
cast(convert_timezone('{{ source_tz }}', '{{ target_tz }}', {{ column }}) as {{ dbt_utils.type_timestamp() }})
{%- endif -%}
{%- endmacro -%}
{%- macro bigquery__convert_timezone(column, target_tz, source_tz=None) -%}
timestamp(datetime({{ column }}, '{{ target_tz}}'))
{%- endmacro -%}
{%- macro spark__convert_timezone(column, target_tz, source_tz) -%}
from_utc_timestamp(
to_utc_timestamp({{ column }}, '{{ source_tz }}'),
'{{ target_tz }}'
)
{%- endmacro -%}
{% macro postgres__convert_timezone(column, target_tz, source_tz) -%}
{%- if source_tz -%}
cast({{ column }} at time zone '{{ source_tz }}' at time zone '{{ target_tz }}' as {{ dbt_utils.type_timestamp() }})
{%- else -%}
cast({{ column }} at time zone '{{ target_tz }}' as {{ dbt_utils.type_timestamp() }})
{%- endif -%}
{%- endmacro -%}
{%- macro redshift__convert_timezone(column, target_tz, source_tz) -%}
{{ return(dbt_date.default__convert_timezone(column, target_tz, source_tz)) }}
{%- endmacro -%}

convert_timezone as a macro is mostly useful for other package developers, so you can support timezone operations across a number of different databases, which all have their own slight syntax variations.
You didn't tag your RDBMS, but assuming you're on Snowflake, you could just use the built-in convert_timezone function, passing a field name as the <target_tz> argument.
If you really do want to modify the macro, it should be as simple as removing the single quotes around the tz arguments, so that you can pass in a field reference instead of a string literal:
{%- macro convert_timezone(column, target_tz=None, source_tz=None) -%}
{%- set source_tz = "'UTC'" if not source_tz else source_tz -%}
{%- set target_tz = var("dbt_date:time_zone") if not target_tz else target_tz -%}
{{ adapter.dispatch('convert_timezone', 'dbt_date') (column, target_tz, source_tz) }}
{%- endmacro -%}
{% macro default__convert_timezone(column, target_tz, source_tz) -%}
{%- if not source_tz -%}
cast(convert_timezone({{ target_tz }}, {{ column }}) as {{ dbt_utils.type_timestamp() }})
{%- else -%}
cast(convert_timezone({{ source_tz }}, {{ target_tz }}, {{ column }}) as {{ dbt_utils.type_timestamp() }})
{%- endif -%}
{%- endmacro -%}
{%- macro bigquery__convert_timezone(column, target_tz, source_tz=None) -%}
timestamp(datetime({{ column }}, {{ target_tz}}))
{%- endmacro -%}
{%- macro spark__convert_timezone(column, target_tz, source_tz) -%}
from_utc_timestamp(
to_utc_timestamp({{ column }}, {{ source_tz }}),
{{ target_tz }}
)
{%- endmacro -%}
{% macro postgres__convert_timezone(column, target_tz, source_tz) -%}
{%- if source_tz -%}
cast({{ column }} at time zone {{ source_tz }} at time zone {{ target_tz }} as {{ dbt_utils.type_timestamp() }})
{%- else -%}
cast({{ column }} at time zone {{ target_tz }} as {{ dbt_utils.type_timestamp() }})
{%- endif -%}
{%- endmacro -%}
{%- macro redshift__convert_timezone(column, target_tz, source_tz) -%}
{{ return(dbt_date.default__convert_timezone(column, target_tz, source_tz)) }}
{%- endmacro -%}
Assuming you have data:
created_at
tz
2022-06-08 T12:00:00Z
America/New_York
2022-06-07 T12:00:00Z
America/Denver
You can then call this like so:
select
{{ convert_timezone("created_at", "tz") }} as use_tz_field,
{{ convert_timezone("created_at", "'America/Los_Angeles'" }} as use_tz_literal

Related

(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.

Shopify - Shorten/change 'Sort By' filter list options (not just changing default order)

I want to reduce the number of options available for users to sort by on my website. The default list of options is too long (e.g. there is no need to sort alphabetically, which is one of the default options), and I'd possibly like to rephrase some of them too.
I believe the code below is the most relevant. I can't seem to find where the sort_options are stored. I am using the Debut theme.
Note that I am not trying to create any new criteria to sort-by, only to reduce the options and rename them.
{% if section.settings.sort_enable %}
<div class="filters-toolbar__item-child">
{%- assign sort_by = collection.sort_by | default: collection.default_sort_by -%}
<label class="filters-toolbar__label select-label" for="SortBy">{{ 'collections.sorting.title' | t }}</label>
<div class="filters-toolbar__input-wrapper select-group">
<select name="sort_by" id="SortBy"
class="filters-toolbar__input hidden"
aria-describedby="a11y-refresh-page-message a11y-selection-message"
data-default-sortby="{{ collection.default_sort_by }}">
{%- for option in collection.sort_options -%}
<option value="{{ option.value }}" {% if option.value == sort_by %}selected="selected"{% endif %}>{{ option.name }}</option>
{%- endfor -%}
</select>
{% include 'icon-chevron-down' %}
</div>
</div>
{% endif %}
Create a variable with option keys you want to hide and then just check whether the current option key is a part of that variable within the for loop.
{%- assign sortOptionsToSkip = "title-ascending,title-descending" -%}
{%- for option in collection.sort_options -%}
{%- if sortOptionsToSkip contains option.value -%}
{%- continue -%}
{%- endif -%}
<option value="{{ option.value }}" {% if option.value == sort_by %}selected="selected"{% endif %}>{{ option.name }}</option>
{%- endfor -%}
Or just use the following if you only need to hide the alphabetical sort options:
{%- for option in collection.sort_options -%}
{%- if option.value contains "title" -%}
{%- continue -%}
{%- endif -%}
<option value="{{ option.value }}" {% if option.value == sort_by %}selected="selected"{% endif %}>{{ option.name }}</option>
{%- endfor -%}
Use case/when control flow tags to use other option names, again by matching it by key.
{%- for option in collection.sort_options -%}
<option value="{{ option.value }}" {% if option.value == sort_by %}selected="selected"{% endif %}>
{% case option.value %}
{%- when "best-selling" -%}
Best
{%- when "created-ascending" -%}
Old first
{%- else -%}
{{ option.name }}
{%- endcase -%}
</option>
{%- endfor -%}

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