Use templates

Modern Python Web Development: Detailed explanation of template engine and MVC pattern

In web development, the template engine is a tool that is often ignored by beginners, but is almost indispensable for experienced engineers. This article will start with "Why do you need templates", take you to a deep understanding of the MVC pattern, then start playing with Jinja2, and finally look at other options and the latest trends in this field. There are no complicated mathematical formulas, I just hope that through the actual code and scenarios, you can use it after reading it.

1. Why do we need a template engine?

How were the earliest dynamic web pages generated? Many people’s first reaction is to splice HTML strings directly in Python code:

def index():
    html = '<html><body><h1>Hello, ' + username + '</h1></body></html>'
    return html

This method is still barely usable in small scripts. Once the project grows, problems will follow:

  • Difficulty in Maintenance: Imagine trying to maintain an HTML structure with more than 6,000 lines like Sina's homepage in Python code. Add a<div>All have to be careful.
  • Confusion of responsibilities: Business logic and display logic are mixed together. People who read the code have to understand "how the data comes from" and "what the page looks like" at the same time.
  • Poor dynamic content support: If you want to make a blog list, you have to deal with loops and conditions in Python. After writing it, you don’t want to read it a second time.
  • Barriers to front-end and back-end collaboration: Front-end developers are not familiar with Python and cannot modify pages independently; back-end students are burdened by HTML details and cannot focus on core logic.

The emergence of template engines is to separate HTML skeleton and dynamic data. You can think of HTML as a template with "placeholders" and fill in the data at runtime, which is both clear and easy to maintain. This is what is often referred to as a "view layer" solution in web development.

2. MVC Pattern: The Art of Organizing Code

Now, almost all modern web frameworks recommend (or mandate) using the MVC pattern to organize code:

  • Model: Responsible for data processing, such as querying articles and user information from the database.
  • View: Responsible for display, which is our topic today - Template.
  • Controller: Responsible for business logic, calling the model to get data according to user requests, and then selecting the corresponding view to return to the user.

In Python web frameworks, this pattern usually works like this:

@app.route('/signin', methods=['POST'])
def signin():
    # Controller 处理业务逻辑
    username = request.form['username']
    # 把数据传给 View(模板)进行渲染
    return render_template('welcome.html', username=username)

The template engine is the bridge connecting the Controller and the View. It allows the Python code to only transfer data without caring about how to draw HTML.

3. Jinja2 template engine actual combat

Jinja2 is one of the most popular template engines in the Python world and the default choice for the Flask framework. It has concise syntax, powerful functions, and is easy to learn.

3.1 Install Jinja2

If you use Flask, Jinja2 is already built in. If used alone, it can be installed via pip:

pip install jinja2

3.2 Three basic grammars

The syntax of Jinja2 mainly revolves around the following tags:

  • Variable Rendering:{{ variable }}-- Insert the value of a Python variable into HTML.
  • Control Structure:{% %}—— Used for conditional judgment, loop and other logic control.
  • Note:{# #}——These comments will not appear in the final generated HTML, which is very convenient for temporary debugging.

Let's start by building a reusable page to feel the power of Jinja2.

3.3 Template Example: From Basics to Inheritance

Basic template (base.html) In actual projects, it is rare to write a complete<!DOCTYPE html>, instead create a basic skeleton for other pages to inherit.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}默认标题{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <header>
        <h1>我的网站</h1>
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        <p>&copy; 2025 我的公司</p>
    </footer>
    
    <script src="/static/js/app.js"></script>
</body>
</html>

here{% block %}Defines a "slot" whose content can be replaced by subpages.

Inherited template (home.html) Based on base.html we create a homepage:

{% extends "base.html" %}

{% block title %}首页 - 我的网站{% endblock %}

{% block content %}
    <section class="welcome">
        <h2>欢迎来到我的网站</h2>
        <p>当前用户: {{ current_user.username if current_user else '游客' }}</p>
    </section>
    
    <section class="articles">
        {% for article in articles %}
        <article>
            <h3>{{ article.title }}</h3>
            <p class="meta">发布于 {{ article.publish_date | datetimeformat }}</p>
            <div class="content">
                {{ article.content | truncate(200) }}
            </div>
            <a href="/article/{{ article.id }}">阅读更多</a>
        </article>
        {% else %}
        <p>暂无文章</p>
        {% endfor %}
    </section>
{% endblock %}

pass{% extends "base.html" %}One line inherits the entire skeleton, and we only need to focus on filling in the title and content areas. This method greatly improves the reuse rate of HTML in the project.

3.4 Practical features of Jinja2

In addition to basic rendering and inheritance, Jinja2 also provides a series of convenient features that make it easy for us to write templates.

  • Conditional judgment, elegantly handle the display of different permissions:
{% if user.is_admin %}
    <button class="admin">管理面板</button>
{% elif user.is_editor %}
    <button class="editor">编辑内容</button>
{% else %}
    <p>普通用户</p>
{% endif %}
  • Loops and built-in variables,loop.indexYou can directly get the sequence number of the current loop:
<ul class="categories">
    {% for category in categories %}
    <li>{{ loop.index }}. {{ category.name }}</li>
    {% endfor %}
</ul>
  • Filter, format or intercept variables when rendering:
<p>{{ bio | striptags | truncate(100) }}</p>
<p>价格: {{ price | float | round(2) }}</p>
  • Macro, equivalent to a function in a template, reuses common HTML fragments:
{% macro input(name, value='', type='text') %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

{{ input('username') }}
{{ input('password', type='password') }}
  • Template contains, split a page into multiple files and then put them together:
{% include 'header.html' %}
<!-- 页面主体内容 -->
{% include 'footer.html' %}

4. Best practices for template development

With the grammatical foundation in place, we will add a few more practices to make the project healthier in actual development.

  1. Make good use of template inheritance createbase.htmlbase_admin.htmlFor multi-level basic templates, sub-pages only cover the parts that need to be changed, avoiding copying and pasting.

  2. Properly organize static files Unify CSS, JS and images instatic/directory and passurl_for('static', ...)Quotes to avoid hard-coding paths:

    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
  3. Enable automatic reloading, which is suitable for development environments. Modifying templates does not require restarting the service:

    app.config['TEMPLATES_AUTO_RELOAD'] = True
  4. Inject global variables Many pages require the current user information. Instead of passing it through every view function, it is better to use a context processor:

    @app.context_processor
    def inject_user():
        return dict(current_user=get_current_user())

All templates can be used directly{{ current_user }}

  1. Custom filters When the built-in filters are not enough, you can extend them yourself, such as implementing a text reversal method:

    @app.template_filter('reverse')
    def reverse_filter(s):
        return s[::-1]
    
    # 模板中使用:{{ "hello" | reverse }}  -> "olleh"

5. Template engine comparison: Jinja2 is not the only choice

Although Jinja2 is good enough, there are far more template engines in the Python ecosystem than this one. The following business cards will help you quickly understand the differences.

Template engineSyntax featuresMain framework supportPerformance
Jinja2{{ }}, {% %}, {# #}Flask, FastAPIHigh
Mako<% %>, ${ }Pylons, PyramidVery high
django Templates{{ }}, {% %}djangomedium
ChameleonXML-based, TAL syntaxPyramidHigh
  • Mako is outstanding in performance, but the syntax is quite different from Jinja2 style.
  • django comes with templates Deeply bound to django, with complete functions but relatively medium speed.
  • Chameleon XML-based syntax, which some teams feel is cleaner.

Which one to choose mainly depends on the framework and team preferences. If you use Flask, Jinja2 is pretty much the answer.

In recent years, the separation of front-end and back-end has become very popular. The front-end of many applications is completely handed over to frameworks such as React and Vue, and the back-end only provides REST API. Are template engines useless? No, it is still active in many scenarios:

  • Server-side rendering (SSR): For pages with high SEO requirements, or applications that require fast loading on the first screen, template engines are still widely used for server-side rendering.
  • Traditional full-stack applications: small and medium-sized projects and internal enterprise systems can be quickly implemented with Flask/Jinja2 and are extremely efficient.
  • Template componentization: passedmacroorincludeImplement front-end-like component reuse to make templates cleaner.
  • Internationalization and static resource management: The template engine can seamlessly integrate internationalization plug-ins (such as Flask-Babel), as well as intelligent management of static files.

Template engines are not a thing of the past, but a classic and efficient piece of the puzzle in Python web development.

7. Summary

The template engine mainly solves the following pain points:

  • Achieved clear separation of business logic and display logic
  • Greatly improved the maintainability and readability of the code
  • Provides powerful capabilities for processing complex display logic (conditions, loops, macros, etc.)
  • Facilitates efficient collaboration between front-end and back-end developers on the same project

Jinja2 has become the preferred template engine for Python developers with its concise syntax, flexible inheritance mechanism, rich built-in functions and good extensibility. Whether you're using Flask, FastAPI, or another framework, you can easily integrate it.

Mastering the template engine is an important step in becoming a full-stack Python developer. It is like a bridge, safely sending the back-end business data to the front-end, and finally presenting a beautiful interface that users see. Hopefully this tutorial will help you cross that bridge and write cleaner, more professional web applications.