Output array in Twig - arrays

I trying to output an array from the database to the screen.
In my entity:
/**
* #ORM\Column(type="array", nullable=true)
*/
private $category;
In my twig template:
{% for category in user.profile.category %}
{{ category }}
{% endfor %}
Error: Array to string conversion in ...
Where is my mistake?

So, as error shows you are trying convert array (in category variable) to string. You can preview array by dump() (doc.). In your case:
{% for category in user.profile.category %}
{{ dump(category) }}
{% endfor %}
Please notice that dump() should be use only for debugging.

You can use join to output an array as a concatenated string. It behaves like implode() in php.
Example:
{{ [1, 2, 3]|join }}
{# returns 123 #}
{{ [1, 2, 3]|join('|') }}
{# outputs 1|2|3 #}
{{ [1, 2, 3]|join(', ', ' and ') }}
{# outputs 1, 2 and 3 #}
See the twig join documentation.

TWIG doesn't know how you want to display your table.
By the way, you should consider naming your variable $categories instead of $category, as you table contains several categories.
Then try this:
{% for category in user.profile.categories %}
{{ category }}
{% endfor %}
If my answer doesn't help, please give us the structure of your array (is there any keys or sub-arrays in you table or is it just a list?)

For anybody who want to output an associative array easily :
(here, the array is user.profile.category)
<table>
<tr>
{% for key,value in user.profile.category[0] %}
<td>{{key|e }}</td>
{% endfor %}
</tr>
{% for cat in user.profile.category %}
<tr>
{% for cell in cat %}
<td>{{ cell|e }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>

Related

Iterate over all Rows and Columns in DBT - Jinja

I have a table that looks something like this:
id
name
table_name
agg_type
agg_column
condition,unique
1
count_of_courses
stg_courses
count
null
null
2
count_of_bundles
stg_bundles
count
null
null
3
count_of_products
stg_products
count
null
null
4
sum_of_gmv
stg_transactions
sum
revenue_usd
null
5
count_of_course_admins
stg_users
count
user_id
is_course_admin
My goal is to use this table as metadata to feed into a large jinja loop that loops over each table_name and performs different aggregations (count, sum) depending on the metadata from that table above. I'd then union all of the table results together.
It might look something like this:
{% set results =
["count_of_courses", "courses", 'count'],
["count_of_bundles", "bundles", 'count'],
["count_of_products", "products", 'count'],
["sum_of_gmv", "stg_transactions", 'sum', 'revenue_usd'],
["count_of_course_admins", "users", 'count', 'user_id', 'is_course_admin is true', 'distinct']
%}
{% for i in results %}
SELECT
user_id,
{{ i[3] }}( --where i[3] refers to the agg_type (COUNT/SUM)
{%- if i[3] == 'sum' -%}
{{- i[4] -}}
{%- elif i[6] == 'distinct' -%}
distinct {{ i[4] }}
{%- else -%}
*
{%- endif -%}
FROM analytics.{{ i[2] }} -- where i[2] refers to the table name
{% if not loop.last %}
union all
{% endif %}
{% endfor %}
Looking for help on how to deal with agate tables, lists, etc. The source data is coming from a dbt seed (which is essentially a table) so I'll need a way to convert the table into a list of some sort.
I have been successful manually passing a list from the set operator in dbt (as shown above) but I'm not able to convert the dbt sedd/model into this list format (also shown below).
["count_of_courses", "courses", 'count'],
["count_of_bundles", "bundles", 'count'],
["count_of_products", "products", 'count'],
["sum_of_gmv", "stg_transactions", 'sum', 'revenue_usd'],
["count_of_course_admins", "users", 'count', 'user_id', 'is_course_admin is true', 'distinct']
I'd love to know how best I can translate the table (from above) into this list format so I can do all of this dynamically and not have to manually add to a list.
Thanks.
For future readers:
I experimented and researched a bit more and eventually landed on the solution of a generic macro which essentially stores SQL query results in a list. It looks like this:
{# This macro collects data from a query and inserts it into a list #}
{% macro query_to_list(query) %}
{% set query_to_process %}
{{ query }}
{% endset %}
{% set results = run_query(query_to_process) %}
{% if execute %}
{% set results_list = results.rows %}
{% else %}
{% set results_list = [] %}
{% endif %}
{{ return(results_list) }}
{% endmacro %}
Beyond this, I can now call the macro and store the results in a variable, iterate over them, etc. Here's what this might look like. I'm giving a simplified version but essentially, you should be able to parse any of the values from the query/variable and use them in magical ways:
Passing a Query into the Macro
{% set query %}
select id, name, table_name
from {{ ref( 'rollups' ) }}
order by id
{% endset %}
Iterating over the Query Results
{% for i in query_to_list(query) %}
{{ log(dbt_utils.pretty_log_format(i), info=True) }}
select
user_id,
'{{ i[2] }}' as table_name,
'{{ i[1] }}' as name
from {{ref( [i[2]]|join('') ) }} {# Extracts the name of the table. We use 'join' to convert it to a string #}
{# If there is a condition, we extract that here #}
{% if i[6] %}
where {{ i[6] }}
{% endif %}
group by 1, 2, 3, 4
{% if not loop.last %}
union all
{% endif %}
{% endfor %}
That's a smart way to build your models !
Maybe the best function for your use case would be run_query (ref here).
I can't test the code below but it should be something similar:
{% set fetch_operations_query %}
SELECT * FROM your.seed.table
{% endset %}
{% set results = run_query(fetch_operations_query) %}
{% if execute %}
{% set results_list = results.rows %}
{% else %}
{% set results_list = [] %}
{% endif %}
{% for row in result_list %}
SELECT
user_id,
{{ row[3] }}( --where row[3] refers to the agg_type (COUNT/SUM)
{%- if row[3] == 'sum' -%}
{{- row[4] -}}
{%- elif row[6] == 'distinct' -%}
distinct {{ row[4] }}
{%- else -%}
*
{%- endif -%}
FROM analytics.{{ row[2] }} -- where row[2] refers to the table name
{% if not loop.last %}
union all
{% endif %}
{% endfor %}

How do i array a collection in 3 cols in liquid by a status

I wanna array my collection in 3 cols, for 3 different statuses. And if there isn't any gig/item with that status, it has to say ' No projects'
I have tried this:
<div class="col-sm">
<h2>Up next</h2>
{% assign next = site.gigs | gig.status == 'Next' | sort: gig.date %}
{% if next.gigs.size == 0 %}
No projects
{% else %}
{% for gig in next %}
{{ gig.title }}
{% endfor %}
{% endif %}
</div>
<div class="col-sm">
<h2>Working on</h2>
{% assign on = site.gigs | gig.status == 'On' | sort: gig.date %}
{% if on.gigs.size == 0 %}
No projects
{% else %}
{% for gig in on %}
{{ gig.title }}
{% endfor %}
{% endif %}
</div>
<div class="col-sm">
<h2>Done</h2>
{% assign done = site.gigs | gig.status == 'Done' | sort: gig.date %}
{% if done.gigs.size == 0 %}
No projects
{% else %}
{% for gig in done %}
{{ gig.title }}
{% endfor %}
{% endif %}
</div>
But it just arrays all of the gigs/items :(
Maybe it can be done in a way more simple way.
I don't know if you could make one compact liquid code and array 3 columns by counting the number of different statuses.
Help!
Ordering
Our statuses are "Next", "On" and "Done". They must be ordered in such order.
As this is not an alphabetical sort order, we need to define this order by ourself.
In _config.yml :
status-order:
-
name: "Next" ### status as it is set on gigs (!!! Case-Sensitive !!!)
display: "Up Next" ### status column header to display
-
name: "On"
display: "Working On"
-
name: "Done"
display: "Done"
We can now loop over site.status-order and get our gigs in desired status order.
{% for status in site.status-order %}
{{ status.name }} - {{ status.display }}
{% endfor %}
Presenting
As your current code is a little repetitive, we can factor it like this :
{% for status in site.status-order %}
{% assign items = site.gigs | where: 'status', status.name | sort: date %}
<div class="col-sm">
<h2>{{ status.display }} ({{ items.size }})</h2>
{% if items.size > 0 %}
<ul>
{% for item in items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% else %}
No project
{% endif %}
</div>
{% endfor %}
Note
You must be sure to set status with the right case (eg : "Next" an not "next").
And the right type. If you set status: On it is understood as the true boolean value, not the string "on". In this case the correct status expression is status: "On". It must be quoted or double quoted to be understood as "On" string.
Any item with incorrectly cased or typed status expression will not appear in our listing.

How to specify multiple authors for a single post, then count the posts each author is related to?

What I'm trying to do:
I'm trying to create a list for the posts each author has written. This is a problem since each post should be able to specify multiple authors.
Let's say we have 3 posts in total and 3 authors in total.
Edit 1: as suggested in the answers this is best done by listing the authors in a front matter list instead of a CSV string. So,
Like this
Post:
---
title: post
authors:
- foo
- bar
---
instead of like this:
Post:
---
title: post
authors: [foo, bar]
---
Problem setup:
(edited, according to Edit 1)
Post 1:
---
title: post1
authors:
- foo
- bar
---
Post 2:
---
title: post2
authors:
- foo
- bar
---
Post 3:
---
title: post3
authors:
- john doe
- bar
---
Out:
bar (3)
john doe (1)
foo (2)
A click on the author should then result in all posts getting displayed.
Alternatively an array can be displayed like this, but it's not helping the case, just an equivalent style.
What I tried
I did the same with categories and tags and this algorithm worked like a charm. However, replacing site.categories with site.authors is somehow not supported.
returns:
Liquid Exception: 0 is not a symbol nor a string in authors.html
I suppose this is due to the nature of categories and tags being arrays by default.
I think it would help to be able to set the front matter tag authors as an array somehow. I suppose this is done in _config.yml, but I busted by head in with it.
As of now I got as far as coming up with a way to target individual authors in an array, but I'm far from being able to list them individually and counting them up. I suppose I'm limited due to the nature of authors not being an array by default, otherwise implementations like this one would work with custom front matter variables like authors, but they don't.
What I meant (when I said "As of now"):
{% for post in site.posts %}
<li>{{ post.authors }} ({{ post.authors[0] }})</li>
{% endfor %}
Out:
foobar (foo)
foobar (foo)
john doebar (john doe)
Afterall, I think I'm missing something here. I'm probably not the first one who tried this but the only documentation I found was from people who attempted what I'm trying but didn't really get there.
This user for example created a way to count users, but when used with site.authors
it returns the array size == 0-if clause:
No author
It seems like a simple thing, but for some reason isn't. At least for me.
Edit 2:
based on Kieths answer I came closer to getting there, but I have issues with creating an emphty array. According to this issue in Jekyll this seems to be a problem in general. However a workaround seems to be to assign a variable and split it with an emphty tag.
Currently I struggle with adding authors to the array so I can assess it's size.
{% assign authors = site.posts | map: 'authors' | uniq %}
{% assign authorCount = '' | split: '' %}
<div class="softwaretitle">
{% for author in authors %}
<a>{{ author }}</a>
{% for post in site.posts %}
{% for post_author in post.authors %}
{% if post_author == author %}
{% assign authorCount = authorCount | push author %}
<a>({{ page.authorCount | size }})</a>
{% endif %}
{% endfor %Edit 2:}
{% endfor %}
{% endfor %}
</div>
Out:
Error: Liquid error (.../_includes/authors.html line 14): wrong number of arguments (given 1, expected 2) included
Error: Run jekyll build --trace for more information.
Line 14:
{% assign authorCount = authorCount | push author %}
Edit 3:
At last, the final result (without link to the post list, but thats details)
<!-- Get a list of unique authors -->
{% assign authors = site.posts | map: 'authors' | uniq | sort%}
{% assign author_post_count = 0 %}
{% for author in authors %}
{% assign author_post_count = 0 %}
<div class="">
<li><a>{{ author }}
{% for post in site.posts %}
{% for post_author in post.authors %}
{% if post_author == author %}
{% assign author_post_count = author_post_count | | plus: 1 %}
{% endif %}
{% endfor %}
{% endfor %}
<span>&nbsp ({{ author_post_count }})</span></a></li>
</div>
{% endfor %}
Out:
bar (3)
john doe (1)
foo (2)
Updated answer:
To get a list of authors (without duplicates) and including the total number of posts the author has contributed to, together with a list of posts' titles and a link to the posts.
{% assign authors = site.posts | map: 'authors' | uniq %}
{% for author in authors %}
{% assign author_post_count = 0 %}
{% for post in site.posts %}
{% if post.authors %}
{% for post_author in post.authors %}
{% if post_author == author %}
{% assign author_post_count = author_post_count | plus: 1 %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<h2>{{ author }} - {{ author_post_count }}</h2>
{% for post in site.posts %}
{% if post.authors %}
{% for post_author in post.authors %}
{% if post_author == author %}
{% assign author_url_query = author | replace: ' ', '-' %}
<a href="{{ post.url }}" title="A posts {{ author }}">
{{ post.title }}
</a>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endfor %}
If instead, you want to have a page per author that includes a list of posts written by them (automatically generated), you will need to extend Jekyll through a custom plugin. This is very possible if you have experience with the Ruby programming language. This is a very close example: https://github.com/avillafiorita/jekyll-datapage_gen and you can simply remove the _config data requirements and hardcode the directory names and permalinks :)
Problem :
on a post : print author(s) + number of posts written and link to the authors page
on an author's page : print author's datas + list written posts
Solution
We already have posts that are described like this :
---
title: post2
authors: [foo, bar]
# or with the alternate YAML array syntax
authors:
- foo
- bar
---
For authors, we can user a specific collection that will automatically generate author's page.
In _config.yml :
collections:
authors:
output: true
defaults:
- scope:
type: authors
values:
layout: authors
An author's page can be described like this :
_authors/foo.md
---
uid : foo
firstname: John
lastname: Foo
---
Some bio here ...
Posts List (index or any page):
{% assign posts = site.posts %}
{% comment %}
or {% assign posts = paginator.posts %} if you use pagination
{% endcomment %}
<ul>
{% for post in posts %}
<li>
{{ p.title }}
<br>{% include authors.html post=post %}
</li>
{% endfor %}
</ul>
We will also use our authors include in _layouts/post.html
...
<h1 class="post-title" itemprop="name headline">{{ page.title | escape }}</h1>
<p>{% include authors.html post=page %}</p>
...
Now the magic : _includes/authors.html
{% assign post = include.post %}
{% comment %}## if we have a least one author in post authors array {% endcomment %}
{% if post.authors and post.authors != empty %}
{% comment %} ## We will build a string for each author,
store it in authorsArray
then reassemble it at the end {% endcomment %}
{% assign authorsArray = "" | split: "" %}
{% for author in post.authors %}
{% comment %}## Count posts for current author {% endcomment %}
{% assign authorsPostsCount = site.posts | where_exp: "post", "post.authors contains author" | size %}
{% comment %}## Get authors data based on uid matching in the collection {% endcomment %}
{% assign authorsDatas = site.authors | where: "uid", author | first %}
{% if authorsDatas %}
{% capture authorString %}
{{ authorsDatas.firstname }} {{ authorsDatas.lastname }} ({{ authorsPostsCount }})
{% endcapture %}
{% else %}
{% comment %}## No entry for this author in the collection
## or author spelling is wrong {% endcomment %}
{% capture authorString %}
{{ author | capitalize }} ({{ authorsPostsCount }})
{% endcapture %}
{% endif %}
{% comment %}## Push result in authorsArray {% endcomment %}
{% assign authorsArray = authorsArray | push: authorString %}
{% endfor %}
{% comment %}## Create a sentence with array elements {% endcomment %}
by {{ authorsArray | array_to_sentence_string }}
{% endif %}
_layouts/author.html
---
layout: default
---
<h1>{{ page.firstname }} - {{ page. lastname }}</h1>
{% assign authorsPosts = site.posts | where_exp: "post", "post.authors contains page.uid" %}
<ul>
{% for p in authorsPosts %}
<li>{{ p.title }}</li>
{% endfor %}
</ul>

Twig loop index into array

I want to show my string value into my array 'nameComments' with key {{loop.index}} of my comments array, but {{ nameComments[{{ loop.index }}] }} show an error
{% for com in comments %}
<p>Comment {{ nameComments[{{ loop.index }}] }} : "{{ com['comment'] }}"</p>
{% endfor %}
If I try:
{% for com in comments %}
<p>Comment {{ nameComments[1] }} : "{{ com['comment'] }}"</p>
{% endfor %}
And the {{ loop.index }} show me value : 1
So how can I implement my loop index into my array?
{% for com in comments %}
<p>Comment {{ nameComments[ loop.index ] }} : "{{ com['comment'] }}"</p>
{% endfor %}
Just leave out the curly brackets. This should work fine.
By the way loop.index is 1 indexed. If you loop through an array which normally starts with index 0 you should consider using loop.index0
See the documentation
It is safer to iterate over the real value of the array index and not using loop.index and loop.index0 in case where array indexes do not start in 1 or 0 or do not follow a sequence, or they are not integers.
To do so, just try this:
{% for key,com in comments %}
<p>Comment {{ nameComments[key] }} : "{{ com['comment'] }}"</p>
{% endfor %}
See the documentation

google app angine template: loop and check the existence of the corresponding entity

I need to create a 100-cell table, and in each cell if the corresponding entity exists, display it's information, otherwise, display "Empty". How do I do that? The python program (Item has properties of "seqNumber" and "name"):
query = db.Query(Item)
items = query.fetch(100)
render(..., {'range100':range(100), 'items':items}, ...)
HTML:
<table>
<tr>
{% for i in range100 %} <!-- for item in items (how?) -->
<td>
{% if item.seqNumber == forloop.counter (how?) %}
{{item.name}}
{% else %}
Empty
{% endif %}
{% endfor %}
</tr>
</table>
query = db.Query(Item)
items = query.fetch(100)
l = []
for i in enumerate(range(99)):
try:
l.append((i,items[i].name))
except:
l.append((i,None))
render(stuff = l)
This is all untested and the try/except is no doubt not ideal, just easier to write code then in a comment to give you the general idea of how I'd approach this.
<table>
<tr>
{% for i in stuff %}
<td>
{{ i.0 }}<!-- ID -->
{% if i.1 %}
{{ i.1 }}<!-- value -->
{% else %}<!-- if the value is none -->
"VALUE NEEDED"
{% endif %}
{% endfor %}
</tr>
</table>

Resources