Combine two lists and alternating the result in Jinja? - jinja2

I'm working on a sandboxed CMS so I can't run any pythonic code, other than versions of Jinja.
I am pulling my lists from a database where I split them based on having a field = a certain value.
{# rows = command to pull from db #}
{% set row_one = rows|selectattr('resource','equalto','One') %}
{% set row_two = rows|selectattr('resource','equalto','Two') %}
{# Sets my empty array #}
{% set newobj = [] %}
From here, console logging either Row one/two will result in showing me only those items that apply to their respective resource type.
My problem comes when I try to stuff those two in an array and then iterate over it to alternate the result.
Here is what I tried:
{% for row in rows %}
{% newObj.update(row_one[loop.index0], row_two[loop.index0] %}
{% endfor %}
This seems to be throwing an error on newObj
Unknown tag 'newobj'
I had it stop throwing the error by using :
{% for row in rows %}
{% set objs = newObj.update(marketingRows[loop.index0], salesRows[loop.index0], designRows[loop.index0]) %}
{% endfor %}
But this has proved to return nothing when console logging objs.
My desired result
{# example input #}
row_one = ['1', '2', '3', '4']
row_two = ['a', 'b', 'c', 'd']
{# desired output #}
objs = ['1', 'a', '2', 'b', '3', 'c', '4', 'd']
I'm at a total loss here, any help is appreciated !

From personal experience, and since you are not able to run any python code, there is no easy way to accomplish this in jinja. Since jinja does not support zip, and max is not available until v2.10, so we need to improvise.
First, you need to get the longest length of either list.
{%- set row_one = ['1', '2', '3', '4'] -%}
{%- set row_two = ['a', 'b', 'c', 'd'] -%}
{%- set rows_combined = (row_one, row_two) -%}
{%- set lengths = [] %}
{%- for row in rows_combined -%}{%- if lengths.append(row|length)-%}{%- endif -%}{%- endfor -%}
{%- set max_length = (lengths|sort)[-1] -%}
Next, you need to do a nested loop. First iterating through the range of the max length and then rows_combined to grab the correct index of row_one and row_two.
{%- set rows = [] -%}
{# Loops through a range of max_length #}
{%- for r in range(max_length) -%}
{# Loops through the tuple containing row_one and row_two #}
{%- for a in rows_combined -%}
{# checks if a[r] exists if based on current index, if so appends it rows#}
{%- if a[r] -%}{%- if rows.append(a[r]) -%}{%- endif -%}{%- endif -%}
{%- endfor -%}
{%- endfor -%}
{{ rows }}
>>>['1', 'a', '2', 'b', '3', 'c', '4', 'd']
Tests with 3 lists instead of 2
{%- set row_one = ['1', '2', '3', '4'] -%}
{%- set row_two = ['a', 'b', 'c', 'd'] -%}
{%- set row_three = ['w', 'x', 'y', 'z'] -%}
{%- set rows_combined = (row_one, row_two, row_three) -%}
{%- set lengths = [] %}
{%- for row in rows_combined -%}{%- if lengths.append(row|length)-%}{%- endif -%}{%- endfor -%}
{%- set max_length = (lengths|sort)[-1] -%}
{%- set rows = [] -%}
{%- for r in range(max_length) -%}
{%- for a in rows_combined -%}
{%- if a[r] -%}{%- if rows.append(a[r]) -%}{%- endif -%}{%- endif -%}
{%- endfor -%}
{%- endfor -%}
{{ rows }}
>>> ['1', 'a', 'w', '2', 'b', 'x', '3', 'c', 'y', '4', 'd', 'z']

Related

Is there a way to write a Jinja set nested within a Jinja for loop

I am currently using Jinja within DBT to create these three sets which all work fine:
{% set value1 %}
select
category1,
category2,
category3,
category4
from {{ ref('table') }}
where category1 = 'value1'
order by category2
{% endset %}
{% set value2 %}
select
category1,
category2,
category3,
category4
from {{ ref('table') }}
where category1 = value2
order by category2
{% endset %}
{% set value3 %}
select
category1,
category2,
category3,
category4
from {{ ref('table') }}
where category1 = value3
order by category2
{% endset %}
I am wondering if there is a way I can condense the creation of those three sets by using a for loop.
This was my attempt at doing so:
{% set attributes = ('value1', 'value2', 'value3') %}
{% for i in attributes%}
{% set {{i}} %}
select
category1,
category2,
category3,
category4
from {{ ref('table') }}
where category1 = {{i}}
order by category2
{% endset %}
{% endfor %}
I keep running into errors regarding the first line of the set within the for loop. I have attempted writing it several ways with syntax changes within the for loop but nothing has worked.
I am wondering if I can recreate the output of the three sets in anyway which would be more condescend then having to write out all three sets?
Your use case seems a perfect fit for a macro:
Define your macro:
{% macro get_set(value) %}
select
category1,
category2,
category3,
category4
from {{ ref('table') }}
where category1 = {{ value }}
order by category2
{% endmacro %}
Then to use it:
{{ get_set('value1') }}
{# and #}
{{ get_set('value2') }}
{# and #}
{{ get_set('value3') }}

DBT : Getting error ‘>’ not supported between instances of ‘Table’ and ‘int’

I have to compare the result of query with hardcoded integer value in if condition
{%- set query %}
select cast(1 as integer)
{% endset -%}
{%- set main = run_query(query) -%}
{% if execute %}
{% if main > 2 %}
Getting below error…
'>' not supported between instances of 'Table' and 'int'
The query select cast(1 as integer) returns table(single column/single row) and it is not directly comparable with constant integer:
{% if main > 2 %}
should rather be:
{% if (main.columns[0].values()[0]) > 2 %}

Why am I getting an "unexpected ')' " error when I try and run my dbt jinja model?

I have created a model in DBT to run against Snowflake. I am using some jinja to iterate through a list of table names, and build some sql for each table.
The following script should loop through each table, generate some SQL script and insert a UNION statement between each piece of script except for the last iteration.
I am getting the following error however I can not see any stray parenthesis
11:44:23 001003 (42000): SQL compilation error
11:44:23 syntax error line 14 at position 6 unexpected ')'.
My script is
{{
config(
query_tag = 'DBT: Staging_History'
)
}}
{% set table_names_query %}
select table_name
from information_schema.tables
where table_type = 'BASE TABLE' and TABLE_CATALOG = 'PROD_SOURCE' and TABLE_SCHEMA =
'DIS_STG'
{% endset %}
{% set results = run_query(table_names_query) %}
{% if execute %}
{% set results_list = results.columns[0].values() %}
{% else %}
{% set results_list = [] %}
{% endif %}
{% for table_name in results_list %}
SELECT * FROM TABLE(INFORMATION_SCHEMA.copy_history(table_name=>'PROD_SOURCE.DIS_STG.DBO_ORGANIZATION', start_time=>dateadd(DAY, -90, current_timestamp)))
{% if not loop.last %} UNION {% endif %}
{% endfor %}
If you are going to use the macro of CURRENT_TIMESTAMP (aka without the parens) it must be in upper case.
So ether change to
current_timestamp()
or
CURRENT_TIMESTAMP

In Jekyll, how to show "posts from last week"

I'm not sure to get the liquid syntax to help me pull posts that were published during the same week as the build date. Is that possible?
There are simpler ways to get a list of recent posts, but this approach would be useful for my project. I've tried several things I found by searching but no joy yet.
Before even giving this answer a second thought, consider the fact that, at the very least the first part of this answer doesn't really work, because jekyll is a static site generator and therefore the shown posts are relative to the last build date, which may not be the same as current date.
The second part of the answer goes a bit deeper into the idea of actually generating a list of "recent posts", rather than "posts from last week".
To basically explain the code in words: First we get current year and current week, then we loop through every post and compare current year and current week to the week and year of the post. If they match, the post is shown.
Show — Build Week:
{% assign currentYear = site.time | date: "%Y" %}
{% assign currentWeek = site.time | date: "%W" %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postWeek = post.date | date: "%W" %}
{%- if currentYear == postYear and currentWeek == postWeek -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
Show — Build Day and 6 Days Prior:
{% assign currentYear = site.time | date: "%Y" %}
{% assign currentDay = site.time | date: "%j" | plus: 0 %}
{% assign currentDay_minus_week = site.time | date: "%j" | minus: 7 %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postDay = post.date | date: "%j" | plus: 0 %}
{%- if currentYear == postYear and postDay > currentDay_minus_week and postDay <= currentDay -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
In an effort to sort of salvage this answer, though it already kind of veered off course.... I wrote this code with similar logic, but this time it gets the year and week of the latest post and shows all posts posted that year and that week.
This would be bulletproof in terms of showing something even if you keep building the site without making new posts. But it also just shows only one post, if your last post is the only post posted that week, which may be a bit dumb...
On the other hand, the simplest method for showing "recent posts" is probably just using the limit and limit recent posts to like last 5 posts or something like that: {%- for post in site.posts limit: 5 -%}
Show — Latest Post Week:
{% assign latestPost_year = site.posts.first.date | date: "%Y" %}
{% assign latestPost_week = site.posts.first.date | date: "%W" %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postWeek = post.date | date: "%W" %}
{%- if latestPost_year == postYear and latestPost_week == postWeek -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
Show — Latest Post Day and 6 Days Prior
{% assign latestPost_year = site.posts.first.date | date: "%Y" %}
{% assign latestPost_day = site.posts.first.date | date: "%j" | plus: 0 %}
{% assign latestPost_day_minus_week = site.posts.first.date | date: "%j" | minus: 7 %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postDay = post.date | date: "%j" | plus: 0 %}
{%- if latestPost_year == postYear and postDay > latestPost_day_minus_week and postDay <= latestPost_day -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
The above solution works, but doesn't span years.
So I utilized the concepts and came up with a simpler and more flexible solution for filtering for a timeframe (which can be any variable time span of seconds, hours, days, weeks, or months). My solution has fewer variables, and less logic.
Liquid date/time uses unix timestamp (%s = seconds since 1970). So I kept the timeframe in seconds and do the conversion for the length of time. 86400 = 1 day, 604800 = 1 wk, 2678400 = 31 days, ... and so on.
The code also assumes your posts use last_modified_at in your post frontmatter. You could substitute for post.date if you're not.
Simple Solution
List posts within last week
{% assign timeframe = 604800 %}
{% for post in site.posts %}
{% assign post_in_seconds = post.last_modified_at | date: "%s" | plus: 0 %}
{% assign recent_posts = "now" | date: "%s" | minus: timeframe %}
{% if post_in_seconds > recent_posts %}
{{ post.title }}
{% endif %}
{% endfor %}
Alternative
List posts within timeframe and flag new or modified
This code will list all posts to within a limit and flag an posts as new or modified in timeframe. Length of list is limited to maxposts. Note: css class is designed to utilize Bootstrap, remove/edit to your liking.
timeframe 2419200 = seconds in 4 weeks
maxposts = 10
label_FOOBAR just clean way to handle html with liquid
Example Results with new/modified flags & dates
Code
{% assign timeframe = 2419200 %}
{% assign maxposts = 10 %}
{% assign date_format = site.minima.date_format | default: "%m/%d" %}
<ul class="post-list text-muted list-unstyled">
{% for post in site.posts limit: maxposts %}
{% assign post_in_seconds = post.last_modified_at | date: "%s" | plus: 0 %}
{% assign recent_posts = "now" | date: "%s" | minus: timeframe %}
{% assign post_updated = post.last_modified_at | date: date_format %}
{% capture post_date %}<small>{{ post.date | date: date_format }}</small>{% endcapture %}
{% if post_in_seconds > recent_posts %}
{% capture label_new %}<span class="label label-primary">new</span>{% endcapture %}
{% if post.last_modified_at > post.date %}
{% assign label_new = '' %}{% comment %}Clear NEW if modified{% endcomment %}
{% capture label_updated %}<span class="label label-info">Updated <span class="badge">{{ post_updated }}</span></span>{% endcapture %}
{% endif %}
{% endif %}
<li>
<h4>{{ post_date }}
<a class="post-link" href="{{ post.url | relative_url }}">
{{ post.title | escape }}</a> {{ label_new }}{{ label_updated }}
</h4>
</li>
{% assign post_date = '' %}
{% assign label_updated = '' %}
{% endfor %}
</ul>
You never know when Jekyll builds, so Jekyll should output a big(ger) list of posts. You can use the code of Joonas for this. Then you should use javascript to hide the non-relevant posts (those older than one week).
This can easily be done by adding a custom attribute to your list, like this:
<li date="{{ post.date }}">{{ post.title }}</li>
Use jQuery (or vanilla js) to hide old posts:
// loop through all list items with a date
$('li[date]').each(function(){
// create a postDate in a date object
var postDate = new Date($(this).attr('date'));
// create an object with the date of one week ago
var oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
// compare dates and hide old posts
if(postDate<oneWeekAgo) $(this).hide();
});

Jekyll: Looping with expired Dates

So I'm trying to show the latest 5 upcoming events. I'm pulling everything form a data file. But I noticed that when an event expires and remains in the data file, that the homepage is showing 5 minus the amount of expired events.
For example, if there was 1 event that was expired the homepage would only show 4 of the upcoming events, instead of skipping the expired event and looping. Can anyone help? Thanks!
{% assign eventPost = site.data.events | sort: 'date' %}
{% for event in eventPost limit:5 %}
{% assign curDate = site.time | date: '%Y-%m-%d' %}
{% assign postStartDate = event.date | date: '%Y-%m-%d' %}
{% if postStartDate >= curDate %}
<div class="content--block">
<div class="event--info">
<p class="content--date">{{ event.date | date: '%B %-d, %Y' }}</p>
<h3 class="content--title">{{ event.title }}</h3>
{% if event.time %}
<p class="content--location"><strong>{{ event.location }}</strong> at <strong>{{ event.time }}</strong></p>
{% else %}
<p class="content--location"><strong>{{ event.location }}</strong></p>
{% endif %}
{% if event.subheader %}
<p class="content--description">{{ event.subheader }}</p>
{% endif %}
</div>
{% if event.button == true %}
<button class="outline">Attend</button>
{% endif %}
</div>
{% endif %}
{% endfor %}
{% for event in eventPost limit:5 %} instructs liquid to create a temporary array with 5 first eventPost elements. Your loop is definitely looping over 5 elements and no more.
Use a counter instead. Note, I've sniped code in the print loop for brevity.
{% assign eventPost = site.data.events | sort: 'date' %}
{% assign eventsNumber = 5 %}
{% assign printedEvents = 0 %}
{% for event in eventPost %}
{% assign curDate = site.time | date: '%Y-%m-%d' %}
{% assign postStartDate = event.date | date: '%Y-%m-%d' %}
{% if (postStartDate >= curDate) and (printedEvents < eventsNumber) %}
{% assign printedEvents = printedEvents | plus: 1 %}
<p>{{ event.date | date: '%B %-d, %Y' }} - {{ event.title }}</p>
{% endif %}
{% endfor %}
You then just have to change the eventsNumber to change number of printed events.