Jinja2 : proper way to write [not so] complex template

Question:

I’m trying to implement a simple calendar webpage.

I’m quite new to Jinja2 but my understanding of templates is that writing HTML code in the Python sources should be avoided as templates are designed for this purpose.

The problem that I’m facing I that I don’t see how to write clear templates for this project. I guess there is an overall architecture problem in my project…

My page template:

{% extends "base.html" %}
{% block title %}The title{% endblock %}
{% block content %}
    <h1>Hey</h1>
    <p> I'm happy </p>
    <div id="calendar">
        <table>
            <tr>
                {% for month in range(1, 13) %}
                    <td valign="top" align="center">{# html code for a single month goes here... #}</td>
                {% endfor %}
            </tr>
        </table>
    </div>
{% endblock %}

Each month template is

<table>
    <th>{{ month_name }}</th>
    {% for day_number in days %}
        <tr><td>{{ day_number }}</td><td>{{ weekday }}</td></tr>
    {% endfor %}
</table>

Finally, I have a Python class for a calendar that basically provides helper functions to calculate the days of the month:

class Calendar:
    def __init__(self, year):
        self.year = year

    def monthrange(self, month):
        nextmonth = month % 12 + 1
        nextyear = self.year + 1 if nextmonth == 1 else self.year
        firstday = datetime.date(self.year, month, 1)
        lastday = datetime.date(nextyear, nextmonth, 1)
        return (1, (lastday - firstday).days)

    def itermonthdates(self, month):
        first, last = self.monthrange(month)
        for i in range(first, last + 1):
            yield datetime.date(self.year, month, i)

    def tohtml(self):
        def month_to_html(month):
            # !!! This code generate HTML but it should not !!!
            s = '<table>n'
            s += '<th>{}</th>'.format(MONTHS[month - 1])
            for day in self.itermonthdates(month):
                weekday = WEEKDAYS[day.weekday()]
                d = {'day': day.day, 'weekday': weekday}
                s += '<tr><td>%(day)02d</td><td>%(weekday)s</td></tr>n' % d
            s += '</table>n'
            return s

        template_loader = jinja2.FileSystemLoader(searchpath='templates')
        template_env = jinja2.Environment(loader=template_loader)
        template = template_env.get_template('template.html')
        print(template.render(months=[month_to_html(i) for i in range(1, 13)]))

So this code only partially work as I don’t know how to use Jinja2 to render each month.

Any help would be appreciated.
Ben

Asked By: ben

||

Answers:

I finally think I got what you want. Don’t write HTML in Python. Instead, you can create the logic to your calendar in Python then send that data to the template to be populated with Jinja. enough talk, let’s code a very simple example.

Simple Example

here we will get the current date to be displayed in a webpage.

logic.py

@app.route('/')
def index():
    import time
    current_time = time.strftime("%d/%m/%Y")
    return render_template('template.html', data = current_time)

template.html

{% if data %} # This checks if the variable "data" is set.
     <p> {{data}} </p> ## This will output the date here

By applying that to your code, you can send assign any logical data ( which means it requires Python to be generated ) to variables and send them to Jinja template via adding a second argument to render_template. Hope I Understood what you wanted right this time.

Answered By: Michael Yousrie

Thanks for your answer.
I ended with a solution. Don’t know if there is a better way. In the end, I pass my calendar object to the template.

The page template:

{% extends "base.html" %}
{% block title %}The title{% endblock %}
{% block content %}
    <h1>Hey</h1>
    <p> I'm happy </p>
    <div id="calendar">
        <table>
            <tr>
                {% for month in range(1, 13) %}
                <td valign="top" align="center">
                    {% include 'month_template.html' %}
                </td>
                {% endfor %}
            </tr>
        </table>
    </div>
{% endblock %}

The month template:

<table>
    <th>{{ calendar.month_name(month) }}</th>
    {% for day_number, weekday in calendar.itermonthdays(month) %}
        <tr><td>{{ day_number }}</td><td>{{ weekday }}</td></tr>
    {% endfor %}
</table>

The Python script:

class Calendar:
    def __init__(self, year):
        self.year = year

    @staticmethod
    def month_name(monthid):
        return MONTHS[monthid - 1]

    def monthrange(self, month):
        nextmonth = month % 12 + 1
        nextyear = self.year + 1 if nextmonth == 1 else self.year
        firstday = datetime.date(self.year, month, 1)
        lastday = datetime.date(nextyear, nextmonth, 1)
        return (1, (lastday - firstday).days)

    def itermonthdates(self, month):
        first, last = self.monthrange(month)
        for i in range(first, last + 1):
            yield datetime.date(self.year, month, i)

    def itermonthdays(self, month):
        for date in self.itermonthdates(month):
            day = '{:02d}'.format(date.day)
            weekday = WEEKDAYS[date.weekday()]
            yield day, weekday

    def tohtml(self):
        template_loader = jinja2.FileSystemLoader(searchpath='templates')
        template_env = jinja2.Environment(loader=template_loader)
        template = template_env.get_template('template.html')
        print(template.render(calendar=self))

Let me know if you see a better approach.

Thanks.

Ben

Answered By: ben
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.