Jake Jackson
Jake Jackson

Reputation: 1225

How to iterate over attributes of an object using Jinja

I have a model for coffee beans that I send over to my HTML pages and display the objects' data. Currently, I have a solution that requires me to update my HTML code if I modify my Bean object.

I want to the remove coupling from my program by iterating over the attributes associated with the object as opposed to hard-coding each attribute into the HTML.

My current (outdated) solution:

# Bean model defined using Flask-SQLAlchemy
class Bean(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20), index=True, nullable=False)
    ...
<!-- some_page.html -->
<table class="table">
        <thead>
            <tr>
                <th scope="col">#</th>
                <th scope="col">Name</th>
                ...
            </tr>
        </thead>
        <tbody>
            {% for bean in beans %}
            {% include '_bean_info.html' %}
            {% endfor %}
        </tbody>
</table>
<!-- _bean_info.html -->
<tr>
    <th scope="col">{{ bean.id }}</th>
    <th scope="col">{{ bean.name }}</th>
    ...
</tr>

What I am trying to do:

<!-- _bean_info.html -->
<tr>
    {% for each b in bean %}
        <th scope="col">
            {{ bean.b }} <!-- where 'b' is an attribute, e.g. 'name' -->
        </th>
</tr>

Upvotes: 0

Views: 2665

Answers (1)

larsks
larsks

Reputation: 311605

Depending on what your data structure looks like, you may be able to take advantage of the fact that the attributes of an object are available via the __dict__ attribute...in other words, if we have data like this:

from dataclasses import dataclass


@dataclass
class Bean:
    id: str
    name: str


beans = [
        Bean(id=1, name="Guatemalan"),
        Bean(id=2, name="Peruvian"),
        Bean(id=3, name="Hawaiian"),
        ]

Then we can use that in a template like this:

import jinja2

t = jinja2.Template('''
<table>
{% for bean in beans %}
<tr>
{% for attr in bean.__dict__.keys() -%}
    <td>{{ attr }}</td><td>{{ bean[attr] }}</td>
{% endfor -%}
</tr>
{% endfor %}
</table>
''')

print(t.render(beans=beans))

Put together, the examples above will produce:

<table>

<tr>
<td>id</td><td>1</td>
<td>name</td><td>Guatemalan</td>
</tr>

<tr>
<td>id</td><td>2</td>
<td>name</td><td>Peruvian</td>
</tr>

<tr>
<td>id</td><td>3</td>
<td>name</td><td>Hawaiian</td>
</tr>

</table>

...which is I think what you want.


In this particular situation, I would probably explicitly convert my model into a dict, because this makes the template a little less magical:

from dataclasses import dataclass, asdict

...

t = jinja2.Template('''
<table>
{% for bean in beans %}
<tr>
{% for attr in bean.keys() -%}
    <td>{{ attr }}</td><td>{{ bean[attr] }}</td>
{% endfor -%}
</tr>
{% endfor %}
</table>
''')

print(t.render(beans=(asdict(bean) for bean in beans)))

Upvotes: 1

Related Questions