Reusable jinja2 table - jinja2

I want to use the template to create all the tables of my project, but after writing is appearing errors like this:
jinja2.exceptions.TemplateSyntaxError: expected token ':', got '}'
{{ table(headers={{headers}},items={{item}},url='None') }}
I've looked at the jinja2 website but couldn't find the answer to the syntax error.
# python
#app.route('/products')
def products():
context = {}
qproducts = list(s.query(Product))
context['products'] = qproducts
return render_template('products.html', **context)
# table.html
{% macro table(headers,items,url,var) -%}
<table class='table table-sm table-dark'>
<thead>
<tr>
{{headers}}
</tr>
</thead>
<tbody>
{% for item in items %}
<tr onclick="window.location='{{url}}'">
{{items}}
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}
# products.html
{% from 'table.html' import table %}
{% block headers %}
<th>ID</th>
<th>Price</th>
{%endblock headers%}
{%block item%}
{%for item in products%}
<td>{{item.id}}<td><br>
<td>{{item.price}}<td><br>
{%endfor%}
{%endblock item%}
{{ table(headers={{headers}},items={{item}},url='None') }}

Even when you fix the references to variables (i. e. remove surrounding {{/}}) it won't work the way you expect. block tag can only be used with extends tag, but you use import for macros importing. If you want to write universal macro for table rendering it's better to use combination of macro/call tags:
{% macro table(headers,items,url,var) -%}
<table class='table table-sm table-dark'>
<thead>
<tr>
{{ caller(mode='header') }}
</tr>
</thead>
<tbody>
{% for item in items %}
<tr onclick="window.location='{{url}}'">
{{ caller(mode='row', item) }}
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}
caller here is a reference to a special function which invokes externally-provided callback. Then you can call this macro this way:
{% call(mode, items) table(headers=headers, items=item, url='None') %}
{% if mode='header' %}
<th>ID</th>
<th>Price</th>
{% else %}
{%for item in products%}
<td>{{item.id}}<td><br>
<td>{{item.price}}<td><br>
{% endfor %}
{% endif %}
{% endcall %}
Every mention of caller in macro table invokes body of the caller tag with the specified parameters. So, you can customize the behavior of the macro.

Related

Infinite scroll with htmx in flask is not working for me

This is my first time using htmx and I'm trying to use the infinite scroll but it's not working. This is my code below
#app.route('/home', methods = ['GET'])
def home():
# pagination
page = request.args.get('page', 1, type=int)
all_data = Data.query.order_by(desc(Data.timestamp)).paginate(page = page, per_page = 20)
if "hx_request" in request.headers:
return render_template("table.html",datas = all_data)
return render_template("home.html", datas = all_data)
This is the html code i am trying to add the infinite scroll to
{% for data in datas.items %}
{% if loop.last and data.has_next %}
<tr hx-get="{{url_for('home', page = data.next_num)}}" hx-trigger="revealed" hx-swap="afterend">
{% else %}
<tr>
{% endif %}
<td scope="row">{{data.uuid}}</td>
<td scope="row">{{data.timestamp}}</td>
<td scope="row">{{data.decibel}}</td>
</tr>
{% endfor %}
And this is my home.html that contains the table
{% extends 'layout.html' %}
{% block head %}
<title>home</title>
{% endblock %}
{% block body %}
<center>
<table class="table table-primary table-striped" >
<tr>
<th scope="col">UUID</th>
<th scope="col">Timestamp</th>
<th scope="col">Decibel</th>
</tr>
<tbody>
{% include 'table.html' %}
</tbody>
</table>
</center>
{% endblock %}
I updated some things here and there but it's still not working
The following example shows you a not very nice way of using htmx infinite scroll. It's pretty similar to your approach.
from flask import (
Flask,
render_template,
request
)
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
class Record(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
with app.app_context():
db.drop_all()
db.create_all()
items = [Record(name=f'Record-{i}') for i in range(100)]
db.session.add_all(items)
db.session.commit()
#app.route('/')
def index():
page = request.args.get('page', 1, type=int)
records = Record.query.paginate(page=page, per_page=20)
return render_template('index.html', **locals())
A macro is defined which uses the paginate object to add the htmx pattern for each last row of the loaded chunk as long as there is another page.
The macro iterates over the elements of the loaded page, returning a table row for each entry. The attributes necessary for the infinite scroll pattern are added to the last row, provided there is another page.
In order to make the macro usable for other data records as well, the current object is sent back to the call within the iteration using caller(). This makes it usable as an argument in the nested block of the call.
See also the call section in the documentation for a more detailed explanation.
{% macro htmx_infinite_table_rows(paginate, endpoint) -%}
{% for item in paginate.items -%}
{% if loop.last and paginate.has_next -%}
<tr
hx-get="{{ url_for(endpoint, page=paginate.next_num) }}"
hx-trigger="revealed"
hx-swap="afterend"
>
{% else -%}
<tr>
{% endif -%}
{{ caller(item) }}
</tr>
{% endfor -%}
{% endmacro -%}
{% if 'hx_request' in request.headers -%}
{% call(record) htmx_infinite_table_rows(records, 'index') -%}
<td>{{ record.name }}</td>
{% endcall -%}
{% else -%}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.htmx-indicator {
display:none;
}
.htmx-indicator.htmx-request {
display:inline;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{% call(record) htmx_infinite_table_rows(records, 'index') -%}
<td>{{ record.name }}</td>
{% endcall -%}
</tbody>
</table>
<center><img class="htmx-indicator" width="60" src="https://htmx.org/img/bars.svg"></center>
<script
src="https://unpkg.com/htmx.org#1.7.0"
integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo"
crossorigin="anonymous"
defer></script>
</body>
</html>
{% endif -%}
Update:
You made a mistake when getting the attributes has_next and next_num. The code should be as follows.
{% for data in datas.items %}
{% if loop.last and datas.has_next %}
<tr hx-get="{{url_for('home', page=datas.next_num)}}" hx-trigger="revealed" hx-swap="afterend">
{% else %}
<tr>
{% endif %}
<td scope="row">{{data.uuid}}</td>
<td scope="row">{{data.timestamp}}</td>
<td scope="row">{{data.decibel}}</td>
</tr>
{% endfor %}

generate unique id in each loop of for loop django template

I need to generate unique id in each loop instead of ````city-selected```
{% for form in formset.forms %}
<tr>
{% for field in form %}
<td class="input_td{% if field.errors %} error_td{% endif %}">
<select name="city-select" id="city-select"></select>
</td>
{% endfor %}
<td class="delete_formset_td"></td>
</tr>
{% endfor %}
How can I generate it here?
I need some thing like this for ids:
output:
city-1
city-2
city-3
...
You can use {{ forloop.counter }}. It gives you the loop iteration as a number.
See here.
{% for field in form %}
<!-- your html -->
city-{{ forloop.counter }}
{% endfor %}

Flask - Making HTML table of hyperlinks via nested for-loops?

I'm designing a Flask application that works with a MySQL database.
I have this Flask code below:
#app.route("/test")
def test():
cursor.execute("SELECT * from testtable;")
data = cursor.fetchall()
return render_template('test.html', data = data)
I wish to make an HTML table from this data, and I want the first column of this table to be hyper-linked. My current test.html is shown below:
<table border="1" cellpadding="5" cellspacing="5">
{% for row in data %}
<tr>
{% for d in row %}
<td>{{ d }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
This HTML makes a hyper-link out of every cell in every column of the table. Is there a way to make only the cells in the first column be hyper-linked, and make all other cells just show {{ d }}?
The default template engine in Flask is jinja2.
In jinja2 you can check the loop index, which means that you could do something like the following.
{% for d in row %}
{% if loop.index == 1 %} # You can also use loop.index0 for 0-based indexing
<td>{{ d }}</td>
{% else %}
<td>{{ d }}</td>
{% endif %}
{% endfor %}
You can also skip the first element in the row list by using the following syntax:
{% for d in row[1:] %}
In your table, put for for loop outside the tr element.
<table border="1" cellpadding="5" cellspacing="5">
{% for row in data %}
<tr>
<td>{{ row.d }}</td>
{% endfor %}
</tr>
</table>

Intelligent way to generate tables with Liquid from a CSV file?

Okay, I'll try to make it short.
I got an XML file from Musicbrainz. Then I converted it to CSV and replaced the .'s with _'s to make it work with Liquid.
I put music.csv in _data and call it with:
{% include music.html %}
_includes/music.html looks (in part) like:
<table border="1" style="width:100%">
<tr>
<td>Artist</td>
<td>Track title</td>
</tr>
<tr>
{% if member.release_medium-list_medium_track-list_track_0_recording_artist-credit_name-credit_artist_name %}
<td>{{ member.release_medium-list_medium_track-list_track_0_recording_artist-credit_name-credit_artist_name }}</td>
{% endif %}
{% if member.release_medium-list_medium_track-list_track_0_recording_title %}
<td>{{ member.release_medium-list_medium_track-list_track_0_recording_title }}</td>
{% endif %}
</tr>
<tr>
{% if member.release_medium-list_medium_track-list_track_1_recording_artist-credit_name-credit_artist_name %}
<td>{{ member.release_medium-list_medium_track-list_track_1_recording_artist-credit_name-credit_artist_name }}</td>
{% endif %}
{% if member.release_medium-list_medium_track-list_track_1_recording_title %}
<td>{{ member.release_medium-list_medium_track-list_track_1_recording_title }}</td>
{% endif %}
</tr>
<tr>
{% if member.release_medium-list_medium_track-list_track_2_recording_artist-credit_name-credit_artist_name %}
<td>{{ member.release_medium-list_medium_track-list_track_2_recording_artist-credit_name-credit_artist_name }}</td>
{% endif %}
{% if member.release_medium-list_medium_track-list_track_2_recording_title %}
<td>{{ member.release_medium-list_medium_track-list_track_2_recording_title }}</td>
{% endif %}
</tr>
Now, clearly, this is not the best way to do it. What I'm after is something that:
Looks for the relevant data
If the data is there, creates the cells and fills them out.
I'm pretty sure this can be done with Liquid, but I have no idea how. Can someone here help?
EDIT: Turns out I forgot the CSV file -- here it is, on Pastebin.
EDIT 2: This Cheat Sheet might be of help!
The format of a CSV file inherently does not support repeated sections so I think it would be a bad fit for the variable length data format you are trying to use. I feel JSON is more appropriate for this use case as it can handle the structure of the source data you are trying to work with.
As a quick example I put the XML file you supplied through this converter to create a JSON version of the output. This was saved as "_data/music.json".
This liquid code was then used to parse this:
{% for item in site.data.music %}
<h2> {{ item[1].release.title }}</h2>
{% for medium in item[1].release.medium-list %}
<h3> {{ medium[1].format }} </h3>
<table border="1" style="width:100%">
<tr>
<td>Artist</td>
<td>Track title</td>
</tr>
{% for track in medium[1].track-list.track %}
<tr>
<td>{{ track.recording.artist-credit.name-credit.artist.name }}</td>
<td>{{ track.recording.title }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
{% endfor %}
This produces HTML like this (trimmed):
<h2> The Quatermass Film Music Collection</h2>
<h3> CD </h3>
<table border="1" style="width:100%">
<tr>
<td>Artist</td>
<td>Track title</td>
</tr>
<tr>
<td>Tristram Cary</td>
<td>Quatermass and the Pit: Opening Credits</td>
</tr>
<tr>
<td>Tristram Cary</td>
<td>Quatermass and the Pit: Bones</td>
</tr>
</table>
Based on this you should be able to produce the format you ultimately want.
This is a simple way to iterate over a csv file.
<div class="table-responsive">
<table class="table">
<thead>
<tr>
{% for column in include.datafile[0] %}
<th>{{ column[0] }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for spec in include.datafile %}
<tr>
{% for value in spec %}
<td>{{ value[1] }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>

HTML table formatting with django

I'm creating an e-commerce website and I wanted to build a page where I could view a list of all the orders created.
If the order contains just 1 type of item, the format works correctly, but i can't think of a way to construct the table when there are multiple types of items ordered.
This is how it looks like when there are 2 items ordered (last entry):
I want the "Queso Burrito" to be right under "steak and egg burrito" for #18.
This is my code:
<table>
<tr>
<td>#</td>
<td>Name</td>
<td>Email</td>
<td>Phone</td>
<td>Order</td>
<td>Order Quantity</td>
<td>Delivered</td>
</tr>
{% for ord in orders %}
<tr>
<td>{{ord.pk}}</td>
<td>{{ord.user.first_name}}</td>
<td>{{ord.user.email}}</td>
<td>{{ord.user.get_profile.phone}}</td>
{% for food in ord.orderitem_set.all %}
<td>{{food.name}}</td>
<td>{{food.quantity}}</td>
{% endfor %}
<td>x</td>
</tr>
{% endfor %}
</table>
With multiple items, you typically see tables with order data repeated for each line item.
{% for order in orders %}
{% for orderitem in order.items %}
<td>{{order.id}}</td><td>...</td>
{% endfor %}
{% endfor %}
If you want exactly the formatting you described, you could check if the inner loop is past its first item and hide the fields you don't want repeated.
<table>
{% for ord in orders %}
{% for item in ord.orderitem_set.all %}
<tr>
{% if forloop.counter == 1 %}
<td>{{ord.pk}}</td>
<td>{{ord.user.first_name}}</td>
<td>{{ord.user.email}}</td>
<td>{{ord.user.get_profile.phone}}</td>
{% else %}
<td colspan="4"></td>
{% endif %}
<td>{{item.name}}</td>
<td>{{item.quantity}}</td>
<td>{% if forloop.counter == 1 %}x{% endif %}</td>
</tr>
{% endfor %}
{% endfor %}
</table>