What structure to generate reports using Jinja2? - python

I have some Python in Jupyter notebooks that creates pivot tables and some graphs from data. I now want to make PDF reports from this data and I'm looking at making HTML with Jinja2 and converting that to PDF.
I get that Jinja can have a base template and child templates that inherit from it. What I want is a base template that I can render that pulls HTML in from other files (so the base template doesn't get huge and I can debug smaller pieces).
What's the best way to achieve this?

Good evening,
In your case, what I would do is having a folder with your templates.
For example:
templates
|- base.html
|- my_template.html
|- another_template.html
Include
A solution is to use include:
For example in your base.html you would have:
<html>
<head>
</head>
<body>
{% include ['my_template.html', 'another_template.html'] %}
</body>
</html>
Here we include the results of rendering my_template.html and another_template.html in your base.html template.
You will have to give to your render function all the parameters needed for all the HTML templates you want to render.
Extends
With jinja2 you can also do what you want by using the extends capacity.
So let's say you have a template base.html of the type:
<html>
<head>
</head>
<body>
{% block core %}
{{ content }}
{% endblock %}
</body>
</html>
Here, we have a block named core.
You can then in another template extend the base template and replace the core block by something else, for example:
{% extends "base.html" %}
{% block core %}
<h1>Hello world</h1>
{% endblock %}
Unfortunately as you can see it means that if you want various HTML pieces you will have to do several extends.
You can have another template which is going to extend the previous template which extend the base one.
Manual
The last solution which is in my opinion not recommended, but for the sake of it I will expose it here:
Have a base.html of the kind:
<html>
<head>
</head>
<body>
{% for html in list_html_to_render %}
{{ html }}
{% endfor %}
</body>
</html>
Then we don't use Jinja2 anymore in this case, but we render each html contained in the list_html_to_render passed to the render function.
I hope it helps.
Have a lovely day,
My best regards.

Related

Generate full page or HTML fragment based on request header (HTMX)

When using HTMX framework with Python Flask, you have to be able to:
serve a request as a HTML fragment if it's done by HTMX (via AJAX)
server a request as a full page if it's done by the user (e.g. entered directly in the browser URL bar)
See Single-page-application with fixed header/footer with HTMX, with browsing URL history or Allow Manual Page Reloading
for more details.
How to do this with the Flask template system?
from flask import Flask, render_template, request
app = Flask("")
#app.route('/pages/<path>')
def main(path):
htmx_request = request.headers.get('HX-Request') is not None
return render_template(path + '.html', fullpage=not htmx_request)
app.run()
What's the standard way to output a full page (based on a parent template pagelayout.html):
{% extends "pagelayout.html" %}
{% block container %}
<button>Click me</button>
{% endblock %}
if fullpage is True, and just a HTML fragment:
<button>Click me</button>
if it is False?
This solution based on that we can use a dynamic variable when extending a base template. So depending on the type or the request, we use the full base template or a minimal base template that returns only our fragment's content.
Lets call our base template for fragments base-fragments.html:
{% block container %}
{% endblock %}
It's just returns the main block's content, nothing else. At the view function we have a new template variable baselayout, that contains the name of the base template depending on the request's type (originating from HTMX or not):
#app.route('/pages/<path>')
def main(path):
htmx_request = request.headers.get('HX-Request') is not None
baselayout = 'base-fragments.html' if htmx_request else 'pagelayout.html'
return render_template(path + '.html', baselayout=baselayout)
And in the page template, we use this baselayout variable at the extends:
{% extends baselayout %}
{% block container %}
<button>Click me</button>
{% endblock %}
As pointed in the section Null-Default Fallback of Jinja documentation, the extends tag can actually come in an if statement:
Jinja supports dynamic inheritance and does not distinguish between parent and child template as long as no extends tag is visited. While this leads to the surprising behavior that everything before the first extends tag including whitespace is printed out instead of being ignored, it can be used for a neat trick.
Usually child templates extend from one template that adds a basic HTML skeleton. However it’s possible to put the extends tag into an if tag to only extend from the layout template if the standalone variable evaluates to false which it does per default if it’s not defined. Additionally a very basic skeleton is added to the file so that if it’s indeed rendered with standalone set to True a very basic HTML skeleton is added:
{% if not standalone %}{% extends 'default.html' %}{% endif -%}
<!DOCTYPE html>
<title>{% block title %}The Page Title{% endblock %}</title>
<link rel="stylesheet" href="style.css" type="text/css">
{% block body %}
<p>This is the page body.</p>
{% endblock %}
Source: https://jinja.palletsprojects.com/en/3.0.x/tricks/#null-default-fallback
So, your requirement could be fulfilled doing:
{% if not fullpage %}{% extends 'pagelayout.html' %}{% endif -%}
{% block container -%}
<button>Click me</button>
{%- endblock %}

Flask doesn't load css correctly

I'm working with Flask and everything is going well until I created another path and the stylesheet didn't load.
The stylesheet is in the static folder and everything but I can't get it to load.
It works perfectly for the routes on first routes after home e.g. ("/home" , "/index", "/dashboard"). It removes the styles when it goes into secondary routes e.g. ("/home/first", "/index/first")
make sure the file extends the base or whatever file that contains the link to the css styling
{% extends "template_that_links_to_css.py" %}
The most powerful part of Jinja is template inheritance. Template inheritance allows you to build a base “skeleton” template that contains all the common elements of your site and defines blocks that child templates can override.
For example:
<!-- base.html -->
{% extends 'bootstrap/base.html' %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/styles.css') }}">
{% endblock %}
{% block content %}
<!-- your child templates will go here -->
{% endblock %}
This base template inherits the styles from flask-bootstrap to begin with. Then it defines the link to the styles.css file. All child templates' content will come within the block content. So regardless of what template you create, as long as it inherits this base template, your styles defined in styles.css will always apply.
A child template, say index.html, which is rendered when the home() view function is called will look like this:
<!-- index.html-->
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-md-4">
<!-- content -->
</div>
</div>
{% endblock %}
You inherit the base template using the keyword extends. As mentioned ealier, the content will appear in the block content of the base template, so this has to be defined explictly.
The route to render this page can look like this:
# routes.py
from app import app
from flask import render_template
#app.route("/home")
def home():
return render_template("index.html")
So, regardless of whatever template you create, as long as you are inheriting the base template, your styles will apply in each one of them.
Read more from template inheritance doc.

How to add hash strings to custom resources urls in a Bokeh Application?

I am programming a Bokeh Server Application with templates. I have added some custom CSS and JS files. But when I make some changes the files are reloaded from the cache and I cannot see the new changes. One solution is to add a variable at the end of the URL in order to load them from scratch just if there were some changes. Bokeh resources work like that.
I have added the resources as in this example so far
{% extends base %}
<!-- goes in head -->
{% block preamble %}
<link href="app/static/css/custom.min.css" rel="stylesheet">
<script type="text/javascript" src="app/static/js/custom.min.js"></script>
{% endblock %}
<!-- goes in body -->
{% block contents %}
<div> {{ embed(roots.scatter) }} </div>
<div> {{ embed(roots.line) }} </div>
{% endblock %}
Is there a builtin way to add these hashes? The result I would like to have is:
<link href="app/static/css/custom.min.css?v=f5ee62ee57a37d94c35e8f7be218e11fc63df459" rel="stylesheet">
This hash string must be added before the page is loaded to make it work well
I found this class, but it seems just for Bokeh resources
Bokeh uses Jinja2 templates but it doesn't provide any access to template variables. One of possible workarounds is to create a custom template class:
from hashlib import md5
from jinja2 import Template
from bokeh.io import save
from bokeh.models import Div
class ExtendedTemplate(Template):
def render(self, *args, **kwargs):
if 'with_hash' not in kwargs:
kwargs['with_hash'] = with_hash
return super().render(*args, **kwargs)
t = ExtendedTemplate("""\
<link href="{{ with_hash('app/static/css/custom.min.css') }}" rel="stylesheet">
""")
def with_hash(s):
with open(s, 'rb') as f:
return f'{s}?v={md5(f.read()).hexdigest()}'
save(Div(), template=t)
Note that this particular code assumes that the CSS file's path is accessible from its current working directory.

Flask Jinja2 template is not rendering style tag?

I am making (learning) a simple web app using flask. I am not using the templates for css input. I'm directly coding the css using the 'style' tag in the jinja2 template. But the style tag is not working. Even though using inspect element in chrome i can see the style tag being rendered?
index.html code, which is to be rendered
{% extends "base.html" %}
{% block style %}
body{
line-height:1.5;
background-color:gray;
font-size:10px;
}
{% endblock %}
base.html
<html>
<head>
{% if title %}
<title>{{ title }}</title>
{% else %}
<title>Welcome to Micro-blogger Index page!</title>
{% endif %}
<style>
{% block style %}{% endblock %}
</style>
</head>
<body>
<h1><a href='/'>Index</a>
<br/>
{% block content %}{% endblock %}
</body>
</html>
Edit: By not working i mean like the background color is still white and font size & line-height are not as coded.
Edit:When I put the css element in the base.html , the rendering works. But when i pass it through variable in the index.html file the prev. mentioned thing happens. Yeah i could link a stylesheet or pass the css element from the base.html template , but what would i do in a scenario (i hope i don't have to , maybe for debug purposes) when i have two html docs both inheriting from same base.html but need to have different css attributes (like different background color etc.)
What is the need to add style using the ninja template custom tags.
Instead you can directly use the style tag.
Here is the code.
<style>
body{
line-height:1.5;
background-color:gray;
font-size:10px;
}
</style>
and you can directly place it wherever you want.

Django : How to handle css/js specific to a template tag?

Background:
I have a css and a js that is used only by the Google maps template tag, that I include in my templates where-ever needed.
Inside template tag google_map:
...
...
return render_to_string('google_map.html', context)
Inside google_map.html I have the required css and the js :
<link rel="stylesheet" type="text/css" href="mystyle.css" />
My problem is :
I have a page with 3 maps, therefore 3 calls to the template tag. This implies the css and the js is included 3 times in the same page. [Should not happen]
Now, I can-not include these css and js in the header of the page, since all the pages are not having the maps so there, on all those pages it would be a burden.
What should I do, so that if a page has 3 or more maps, even then there is only a single include of the css & js and not repeated?
I'd recommend following the pattern introduced by Paul Irish for Markup-based unobtrusive comprehensive DOM-ready execution. There's a number of side benefits to the approach such as encapsulation, namespacing, etc., but the chief benefit for you would be conditional execution of JavaScript based on a id or class on <body>.
So in your base template change your <body> tag to something like the following:
<body class="{% block body_class %}{% endblock %}">
Then, for templates that need maps just do:
{% block body_class %}{% if block.super %}{{ block.super }} {% endif %}maps{% endblock %}
That looks a little complicated, but all it's doing is inheriting from the values any parent templates set for that block. The if block is used to conditionally include the space needed between multiple classes.
Finally, in your JavaScript you simply create a module for "maps" as the article describes and put all your map-specific JS there.
You might use a new django block in the <head> section of your base template you extend:
<head>
/* ... other css/js calls */
{% block googlemapassets %}{% endblock %}
</head>
Then in the view template (assuming content is you main content block):
{% block content %}
{% google_map %}
{% endblock %}
{% block googlemapassets %}
<link rel="stylesheet" type="text/css" href="mystyle.css" />
{% endblock %}

Categories

Resources