Search code examples
pythonflaskjinja2htmx

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?


Solution

  • 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 %}