Webéçºããèšå®ãã¡ã€ã«çæãŸã§ãJinja2ããã¹ã¿ãŒããã
Pythonã«ã¯å€ãã®åªããã©ã€ãã©ãªããããŸããããã®äžã§ãç¹ã«Webéçºãããã¹ãçæã®åéã§åºã䜿ãããŠããã®ããJinja2ãïŒãžã³ãžã£ããŒïŒã§ããJinja2ã¯ãPython補ã®é«éã§è¡šçŸåè±ããªãã³ãã¬ãŒããšã³ãžã³ã§ãããã®èšäºã§ã¯ãJinja2ã®åºæ¬çãªäœ¿ãæ¹ããå¿çšçãªæ©èœãŸã§ã詳ãã解説ããŠãããŸããWebã¢ããªã±ãŒã·ã§ã³éçºè ã®æ¹ã¯ãã¡ãããèšå®ãã¡ã€ã«ã®èªåçæãªã©ãè¡ãããæ¹ã«ã圹ç«ã€æ å ±ãæºèŒã§ãïŒð
Jinja2ãšã¯ïŒ ãªããã³ãã¬ãŒããšã³ãžã³ãå¿ èŠãªã®ãïŒ ð€
Jinja2ã¯ãPythonããã°ã©ãã³ã°èšèªã§æžããããã³ãã¬ãŒããšã³ãžã³ã§ããã§ã¯ãããã³ãã¬ãŒããšã³ãžã³ããšã¯äœã§ããããïŒ
ãã³ãã¬ãŒããšã³ãžã³ã¯ãéçãªãã³ãã¬ãŒããã¡ã€ã«ïŒäŸãã°HTMLã®éªšçµã¿ïŒãšåçãªããŒã¿ïŒPythonããã°ã©ã ããæž¡ãããå€æ°ãªã©ïŒãçµã¿åãããŠãæçµçãªåºåïŒå®æããHTMLããŒãžãªã©ïŒãçæããããã®ããŒã«ã§ãã
Webãµã€ããèããŠã¿ãŠãã ãããå€ãã®ããŒãžã§ããããŒãããã¿ãŒã¯å ±éã§ããã衚瀺ãããå 容ã¯ãŠãŒã¶ãŒãç¶æ³ã«ãã£ãŠå€ãããŸãããïŒäŸãã°ããã°ã€ã³ããŠãããŠãŒã¶ãŒåã衚瀺ããããããã°èšäºã®äžèŠ§ã衚瀺ãããããããã³ãã¬ãŒããšã³ãžã³ããªããã°ããããã®ããŒãžããšã«HTMLãçæããPythonã³ãŒããæžãå¿ èŠããããéåžžã«æéãããããŸããðš
ãã³ãã¬ãŒããšã³ãžã³ã䜿ãããšã§ãHTMLã®æ§é ïŒãã³ãã¬ãŒãïŒãšãããã«åã蟌ãããŒã¿ãããžãã¯ïŒPythonåŽïŒãåé¢ã§ããŸããããã«ãããã³ãŒãã®èŠéããè¯ããªããã¡ã³ããã³ã¹æ§ãåå©çšæ§ãåäžããŸããâš
Jinja2ã¯ãç¹ã«ä»¥äžã®ç¹åŸŽãæã£ãŠããŸãïŒ
- Pythonã©ã€ã¯ãªæ§æ: Pythonã®ã³ãŒãã«äŒŒãæ§æã§ãã³ãã¬ãŒããèšè¿°ã§ããŸãã
- é«é: ãã³ãã¬ãŒãã¯æé©åãããPythonã³ãŒãã«ã³ã³ãã€ã«ãããå¹ççã«å®è¡ãããŸãã
- æ¡åŒµæ§: ã«ã¹ã¿ã ãã£ã«ã¿ãã¿ã°ãªã©ãè¿œå ããŠæ©èœãæ¡åŒµã§ããŸãã
- å®å šæ§: èªåHTMLãšã¹ã±ãŒãæ©èœã«ãããã¯ãã¹ãµã€ãã¹ã¯ãªããã£ã³ã°ïŒXSSïŒæ»æãé²ãã®ã«åœ¹ç«ã¡ãŸãããµã³ãããã¯ã¹ç°å¢ã§ã®å®è¡ãå¯èœã§ãã
- ãã³ãã¬ãŒãç¶æ¿: ããŒã¹ãšãªããã³ãã¬ãŒããäœæãããããä»ã®ãã³ãã¬ãŒãã§æ¡åŒµïŒç¶æ¿ïŒã§ããŸãã
- ãã¯ã: åå©çšå¯èœãªãã³ãã¬ãŒãã®æçïŒé¢æ°ã®ãããªãã®ïŒãå®çŸ©ã§ããŸãã
Jinja2ã¯ãWebãã¬ãŒã ã¯ãŒã¯ Flask ã®ããã©ã«ããã³ãã¬ãŒããšã³ãžã³ãšããŠæ¡çšãããŠããããšã§æåã§ãããDjangoïŒãªãã·ã§ã³ãšããŠå©çšå¯èœïŒããæ§æ管çããŒã«ã® Ansibleãéçãµã€ããžã§ãã¬ãŒã¿ãŒã® Pelican ãªã©ãå€ãã®ãããžã§ã¯ãã§å©çšãããŠããŸãããã®æ±çšæ§ã®é«ãããããããŸããã
Jinja2ã¯ãArmin Ronacheræ°ã«ãã£ãŠäœæããã2008幎ã«æåã®ããŒãžã§ã³ããªãªãŒã¹ãããŸãããDjangoã®ãã³ãã¬ãŒããšã³ãžã³ã«åœ±é¿ãåããŠããŸãããããPythonicãªè¡šçŸãå¯èœãªã©ã®ç¬èªã®æ¹è¯ãå ããããŠããŸãã
ã€ã³ã¹ããŒã«ãšåºæ¬çãªäœ¿ãæ¹ ð ïž
ã€ã³ã¹ããŒã«
Jinja2ã¯pipã䜿ã£ãŠç°¡åã«ã€ã³ã¹ããŒã«ã§ããŸãã
pip install Jinja2
ããã§æºåå®äºã§ãïŒð
åºæ¬çãªæµã
Jinja2ã䜿ãåºæ¬çãªæµãã¯ä»¥äžã®ããã«ãªããŸãã
- ç°å¢(Environment)ã®äœæ: ãã³ãã¬ãŒãã®ããŒãæ¹æ³ãèšå®ã管çãã `Environment` ãªããžã§ã¯ããäœæããŸãã
- ãã³ãã¬ãŒãã®ããŒã: `Environment` ã䜿ã£ãŠãã³ãã¬ãŒããã¡ã€ã«ããã³ãã¬ãŒãæååãããŒããã`Template` ãªããžã§ã¯ããååŸããŸãã
- ã¬ã³ããªã³ã°(Rendering): `Template` ãªããžã§ã¯ãã® `render()` ã¡ãœããã«ããã³ãã¬ãŒãå ã§äœ¿çšããããŒã¿ãèŸæžãšããŠæž¡ããŠãæçµçãªæååãçæããŸãã
ç°¡åãªäŸïŒæååãããã³ãã¬ãŒããçæ
ãŸãã¯äžçªã·ã³ãã«ãªäŸãèŠãŠã¿ãŸãããã
from jinja2 import Environment
# 1. ç°å¢(Environment)ã®äœæ (ãã®äŸã§ã¯åçŽåã®ããçŽæ¥Templateã䜿çš)
# from jinja2 import Template # Environmentã䜿ããªãã·ã³ãã«ãªæ¹æ³
# ãã³ãã¬ãŒãæåå
template_string = "ããã«ã¡ã¯ã{{ name }} ããïŒ"
# 2. ãã³ãã¬ãŒãã®ããŒã (æååããçŽæ¥)
# template = Template(template_string)
# Environmentã䜿ã£ãæ¹ãäžè¬ç
env = Environment()
template = env.from_string(template_string)
# 3. ã¬ã³ããªã³ã°
data = {"name": "Jinjaãã¹ã¿ãŒ"}
output = template.render(data)
print(output)
# åºå: ããã«ã¡ã¯ãJinjaãã¹ã¿ãŒ ããïŒ
`{{ name }}` ã®éšåãã`render()` ã¡ãœããã«æž¡ãããèŸæžã® `name` ããŒã®å€ã«çœ®ãæããããŠããŸããããããJinja2ã®åºæ¬ã§ãã
ãã¡ã€ã«ãããã³ãã¬ãŒããããŒã
å®éã®éçºã§ã¯ããã³ãã¬ãŒããå¥ã®ãã¡ã€ã«ïŒéåžž `.html` ã `.j2` ãªã©ã®æ¡åŒµåïŒã«èšè¿°ããŸãã
ãŸãããã³ãã¬ãŒããã¡ã€ã«ã眮ãããã®ãã£ã¬ã¯ããªïŒäŸ: `templates`ïŒãäœæãããã®äžã« `greeting.html` ãšãããã¡ã€ã«ãäœæããŸãã
templates/greeting.html:
<!DOCTYPE html>
<html>
<head>
<title>ããããã€</title>
</head>
<body>
<h1>{{ greeting_message }}</h1>
<p>{{ username }} ãããããããïŒ</p>
</body>
</html>
次ã«ãPythonã³ãŒãã§ãã®ãã¡ã€ã«ãããŒãããŠã¬ã³ããªã³ã°ããŸãã
from jinja2 import Environment, FileSystemLoader
import os
# ãã³ãã¬ãŒããã¡ã€ã«ããããã£ã¬ã¯ããªã®ãã¹
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
# 1. ç°å¢(Environment)ã®äœæãšèšå®
# FileSystemLoader: æå®ããããã£ã¬ã¯ããªãããã³ãã¬ãŒããããŒããã
env = Environment(loader=FileSystemLoader(template_dir))
# 2. ãã³ãã¬ãŒãã®ããŒã (ãã¡ã€ã«åãæå®)
template = env.get_template('greeting.html')
# 3. ã¬ã³ããªã³ã°
data = {
"greeting_message": "ããããïŒ",
"username": "Pythonista"
}
output = template.render(data)
# output ã«ã¯ã¬ã³ããªã³ã°ãããHTMLæååãå
¥ã
print(output)
# å¿
èŠã§ããã°ãã¡ã€ã«ã«æžãåºã
# with open("output.html", "w", encoding="utf-8") as f:
# f.write(output)
`FileSystemLoader` ã䜿ãããšã§ãæå®ãããã£ã¬ã¯ããªïŒãã®äŸã§ã¯ `templates` ãã£ã¬ã¯ããªïŒãããã³ãã¬ãŒããã¡ã€ã«ãååã§èªã¿èŸŒããããã«ãªããŸãããããäžè¬çãªäœ¿ãæ¹ã§ãã
Jinja2ã®äž»èŠãªæ§æ ð
Jinja2ãã³ãã¬ãŒãå ã§ã¯ãç¹å¥ãªèšå·ã䜿ã£ãŠPythonã®ããŒã¿ãããžãã¯ãåã蟌ã¿ãŸããäž»ã«ä»¥äžã®3çš®é¡ããããŸãã
æ§æ | èšå· | 説æ | äŸ |
---|---|---|---|
å€æ° (Variables) | {{ ... }} |
Pythonããæž¡ãããå€æ°ã®å€ãåºåããŸãã | <p>{{ user.name }}</p> |
æ (Statements) | {% ... %} |
ifæãforã«ãŒããªã©ã®å¶åŸ¡æ§é ãèšè¿°ããŸããåºåã䌎ããªãåŠçã«äœ¿ãããŸãã | {% if user.is_active %} ... {% endif %} {% for item in item_list %} ... {% endfor %} |
ã³ã¡ã³ã (Comments) | {# ... #} |
ãã³ãã¬ãŒãå ã«ã³ã¡ã³ããèšè¿°ããŸããæçµçãªåºåã«ã¯å«ãŸããŸããã | {# ãã®éšåã¯åºåãããŸãã #} |
å€æ°ã®åºå {{ ... }}
Pythonã®èŸæžããªã¹ãããªããžã§ã¯ãã®å±æ§ãªã©ããããïŒ`.`ïŒãè§æ¬åŒ§ïŒ`[]`ïŒã§ã¢ã¯ã»ã¹ããŠåºåã§ããŸãã
{# PythonåŽ: context = {"user": {"name": "Alice", "age": 30}, "items": ["apple", "banana"]} #}
<p>ãŠãŒã¶ãŒå: {{ user.name }}</p>
<p>幎霢: {{ user['age'] }}</p> {# èŸæžã¢ã¯ã»ã¹ãå¯èœ #}
<p>æåã®ã¢ã€ãã : {{ items[0] }}</p>
å¶åŸ¡æ§é {% ... %}
ifæ
æ¡ä»¶åå²ãè¡ããŸãã`elif` ã `else` ã䜿ããŸããPythonã® `if` æãšäŒŒãæèŠã§èšè¿°ã§ããŸãã
{% if user.is_authenticated %}
<p>ããããã{{ user.username }} ããïŒ</p>
{% elif user.is_guest %}
<p>ã²ã¹ããšããŠãã°ã€ã³ããŠããŸãã</p>
{% else %}
<p><a href="/login">ãã°ã€ã³</a>ããŠãã ããã</p>
{% endif %}
forã«ãŒã
ãªã¹ããèŸæžãªã©ã®ã€ãã©ãã«ãªããžã§ã¯ããå埩åŠçããŸããã«ãŒãå ã§ç¹å¥ãªå€æ° `loop` ãå©çšã§ããã«ãŒãã®åæ° (`loop.index`, `loop.index0`) ãæå/æåŸã (`loop.first`, `loop.last`) ãªã©ãç¥ãããšãã§ããŸãã
{# PythonåŽ: context = {"fruits": ["Apple", "Banana", "Cherry"]} #}
<ul>
{% for fruit in fruits %}
<li>{{ loop.index }}. {{ fruit }}</li>
{% else %}
<li>æç©ã¯ãããŸããã</li> {# ãªã¹ãã空ã®å Žåã«è¡šç€º #}
{% endfor %}
</ul>
`{% else %}` ãããã¯ã¯ãã«ãŒã察象ã®ã·ãŒã±ã³ã¹ã空ã®å Žåã«å®è¡ãããŸãã
å€æ°ã®å®çŸ© {% set ... %}
ãã³ãã¬ãŒãå ã§äžæçãªå€æ°ãå®çŸ©ã§ããŸãã
{% set navigation = [('index', 'ããŒã '), ('about', 'æŠèŠ')] %}
<ul>
{% for id, caption in navigation %}
<li><a href="/{{ id }}">{{ caption }}</a></li>
{% endfor %}
</ul>
ã³ã¡ã³ã {# ... #}
ãã³ãã¬ãŒãã®ããžãã¯ã«é¢ããã¡ã¢ãªã©ãæ®ãã®ã«äŸ¿å©ã§ãããããã®ã³ã¡ã³ãã¯æçµçãªHTMLãªã©ã«ã¯åºåãããŸããã
{# ãŠãŒã¶ãŒãªã¹ãã衚瀺ããã«ãŒã #}
<ul>
{% for user in users %}
{# åãŠãŒã¶ãŒãžã®ãªã³ã¯ #}
<li><a href="{{ user.profile_url }}">{{ user.name }}</a></li>
{% endfor %}
</ul>
ãã³ãã¬ãŒãç¶æ¿: ã³ãŒãã®åå©çšæ§ãé«ãã ðïž
Jinja2ã®æã匷åãªæ©èœã®äžã€ããã³ãã¬ãŒãç¶æ¿ã§ããããã«ããããµã€ãå šäœã®å ±éã¬ã€ã¢ãŠãïŒããããŒãããã¿ãŒããµã€ãããŒãªã©ïŒãäžã€ã®ãããŒã¹ãã³ãã¬ãŒããã«å®çŸ©ããåå¥ã®ããŒãžã¯ãã®ããŒã¹ãã³ãã¬ãŒãããç¶æ¿ãããŠãç¹å®ã®ãããã¯ã ããäžæžãããããšãã§ããŸããDRY (Don’t Repeat Yourself) ååãå®è·µããã®ã«éåžžã«åœ¹ç«ã¡ãŸãïŒ ðª
ããŒã¹ãã³ãã¬ãŒãã®äœæ (`base.html`)
ãŸãããµã€ãå šäœã®éªšæ ŒãšãªãããŒã¹ãã³ãã¬ãŒããäœæããŸãã`{% block %}` ã¿ã°ã䜿ã£ãŠãåãã³ãã¬ãŒããäžæžãã§ããé åãå®çŸ©ããŸãã
{# templates/base.html #}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Website{% endblock %}</title> {# ã¿ã€ãã«ããã㯠#}
<link rel="stylesheet" href="/static/style.css">
{% block head_extra %}{% endblock %} {# ããããŒã«è¿œå ããèŠçŽ çšããã㯠#}
</head>
<body>
<header class="navbar">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item" href="/">My Site Logo</a>
</div>
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/">ããŒã </a>
<a class="navbar-item" href="/about">æŠèŠ</a>
</div>
</div>
</div>
</header>
<main class="section">
<div class="container">
{% block content %}{% endblock %} {# ã¡ã€ã³ã³ã³ãã³ãããã㯠#}
</div>
</main>
<footer class="footer">
<div class="content has-text-centered">
<p>
© 2025 My Website. All rights reserved.
</p>
</div>
</footer>
{% block scripts %}{% endblock %} {# ã¹ã¯ãªããçšããã㯠#}
</body>
</html>
ãã® `base.html` ã§ã¯ã`title`, `head_extra`, `content`, `scripts` ãšãã4ã€ã®ãããã¯ãå®çŸ©ããŸããã
åãã³ãã¬ãŒãã®äœæ (`home.html`)
次ã«ããã® `base.html` ãç¶æ¿ããåãã³ãã¬ãŒããäœæããŸãã`{% extends %}` ã¿ã°ããã³ãã¬ãŒãã®äžçªæåã«èšè¿°ããã©ã®ããŒã¹ãã³ãã¬ãŒããç¶æ¿ãããæå®ããŸãããããŠãäžæžãããã `{% block %}` ãå®çŸ©ããŸãã
{# templates/home.html #}
{% extends "base.html" %} {# base.html ãç¶æ¿ #}
{% block title %}ããããïŒ - My Website{% endblock %} {# title ãããã¯ãäžæžã #}
{% block content %} {# content ãããã¯ãäžæžã #}
<h1 class="title">ããŒã ããŒãžãžããããïŒ</h1>
<p>ãã㯠Jinja2 ãã³ãã¬ãŒãç¶æ¿ã®ãã¢ããŒãžã§ãã</p>
<h2 class="subtitle is-4">ä»æ¥ã®ãã¥ãŒã¹</h2>
<ul>
{% for item in news_items %}
<li>{{ item }}</li>
{% else %}
<li>ãã¥ãŒã¹ã¯ãããŸããã</li>
{% endfor %}
</ul>
{% endblock %}
{% block scripts %} {# scripts ãããã¯ãäžæžã (å¿
èŠãªã) #}
{# <script src="/static/home_specific.js"></script> #}
{# 芪ãããã¯ã®å
容ãç¶æãããå Žå㯠super() ã䜿ã #}
{{ super() }} {# 芪㮠scripts ãããã¯ã®å
容ãããã«åºå #}
{% endblock %}
Pythonã³ãŒããã `home.html` ãã¬ã³ããªã³ã°ãããšã`base.html` ã®éªšçµã¿ã« `home.html` ã§å®çŸ©ããã `title` ãš `content` ãåã蟌ãŸããå®å šãªHTMLãåºåãããŸãã`head_extra` ãããã¯ã¯ `home.html` ã§å®çŸ©ãããŠããªãã®ã§ã`base.html` ã§å®çŸ©ããã空ã®å 容ïŒã€ãŸãäœãåºåãããªãïŒã䜿ãããŸãã
ããåãã³ãã¬ãŒãã§ãããã¯ãå®çŸ©ãããã€èŠªãã³ãã¬ãŒãã®åããããã¯ã®å 容ã䜿ãããå Žåã¯ããããã¯å 㧠`{{ super() }}` ãåŒã³åºããŸããäžã®äŸã§ã¯ `scripts` ãããã¯ã§ `super()` ã䜿ã£ãŠããŸãïŒã³ã¡ã³ãã¢ãŠããããŠããŸããïŒã
ãã³ãã¬ãŒãç¶æ¿ã䜿ãããšã§ãå ±ééšåã®å€æŽãäžç®æã§æžã¿ãã¡ã³ããã³ã¹ãéåžžã«æ¥œã«ãªããŸããïŒ ð
ãã£ã«ã¿: å€æ°ã®è¡šç€ºãå å·¥ãã âš
ãã£ã«ã¿ã¯ãå€æ°ãåºåããéã«ãã®å€ãå å·¥ããããã®ç°¡åãªæ¹æ³ã§ãããã€ãèšå· (`|`) ã䜿ã£ãŠå€æ°ã«é©çšããŸããè€æ°ã®ãã£ã«ã¿ãé£çµããããšãå¯èœã§ãã
{{ variable | filter_name(argument) | another_filter }}
Jinja2ã«ã¯å€ãã®äŸ¿å©ãªçµã¿èŸŒã¿ãã£ã«ã¿ãçšæãããŠããŸãã
ãã䜿ãããçµã¿èŸŒã¿ãã£ã«ã¿ã®äŸ
ãã£ã«ã¿å | 説æ | äŸ | åºå (äŸ) |
---|---|---|---|
safe |
èªåãšã¹ã±ãŒããç¡å¹ã«ããŸããHTMLãæå³çã«åºåããå Žåã«äœ¿çšããŸãããä¿¡é Œã§ããªãå ¥åã«ã¯çµ¶å¯Ÿã«äœ¿çšããªãã§ãã ããã | {{ "<b>bold</b>" | safe }} |
<b>bold</b> (倪åã§è¡šç€ºããã) |
capitalize |
æååã®æåã®æåã倧æåã«ããæ®ããå°æåã«ããŸãã | {{ "hello world" | capitalize }} |
Hello world |
lower |
æååãå°æåã«ããŸãã | {{ "HELLO" | lower }} |
hello |
upper |
æååã倧æåã«ããŸãã | {{ "hello" | upper }} |
HELLO |
title |
ååèªã®æåã®æåã倧æåã«ããŸãã | {{ "hello world" | title }} |
Hello World |
trim |
æååã®ååŸã®ç©ºçœãé€å»ããŸãã | {{ " hello " | trim }} |
hello |
striptags |
æååããHTML/XMLã¿ã°ãé€å»ããŸãã | {{ "<p>Hello</p>" | striptags }} |
Hello |
length or count |
ã·ãŒã±ã³ã¹ïŒæååããªã¹ããªã©ïŒã®é·ããè¿ããŸãã | {{ ["a", "b", "c"] | length }} |
3 |
default(value, boolean=false) |
å€æ°ãæªå®çŸ©ãŸã㯠`false` (boolean=true ã®å Žå) ã®å Žåã«ãæå®ããããã©ã«ãå€ `value` ãè¿ããŸãã | {{ undefined_variable | default("N/A") }} |
N/A |
join(separator) |
ãªã¹ãã®èŠçŽ ãæååã§é£çµããŸãã | {{ ["a", "b", "c"] | join(", ") }} |
a, b, c |
int , float |
å€ãæŽæ°ãŸãã¯æµ®åå°æ°ç¹æ°ã«å€æããŸãã | {{ "123" | int }} |
123 (æ°å€) |
round(precision=0, method='common') |
æ°å€ãäžžããŸãã`method` ã« `ceil` (åãäžã) ã `floor` (åãæšãŠ) ãæå®å¯èœã | {{ 3.14159 | round(2) }} |
3.14 |
filesizeformat |
ãã€ãæ°ã人éãèªã¿ãããåœ¢åŒ (KB, MBãªã©) ã«å€æããŸãã | {{ 10240 | filesizeformat }} |
10.0 KB |
escape or e |
HTMLãšã¹ã±ãŒããè¡ããŸãïŒããã©ã«ãã§æå¹ãªããšãå€ãïŒã | {{ "<script>alert('XSS')</script>" | e }} |
<script>alert('XSS')</script> |
ã«ã¹ã¿ã ãã£ã«ã¿ã®äœæ
çµã¿èŸŒã¿ãã£ã«ã¿ã ãã§ã¯è¶³ããªãå Žåãèªåã§ãã£ã«ã¿é¢æ°ãäœæã㊠`Environment` ã«ç»é²ã§ããŸãã
from jinja2 import Environment, FileSystemLoader
import re
# é»è©±çªå·ããã©ãŒãããããã«ã¹ã¿ã ãã£ã«ã¿é¢æ°
def format_phone(value):
# ç°¡åãªäŸ: æ°åã®ã¿ãæœåºãããã€ãã³ã§åºåã (å®éã«ã¯ãã£ãšå
ç¢ãªåŠçãå¿
èŠ)
digits = re.sub(r'\D', '', str(value))
if len(digits) == 10:
return f"{digits[:3]}-{digits[3:6]}-{digits[6:]}"
elif len(digits) == 11: # æºåž¯é»è©±ãªã©
return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}"
return value # ãã©ãŒãããã§ããªãå Žåã¯ãã®ãŸãŸè¿ã
# ç°å¢ãäœæããã«ã¹ã¿ã ãã£ã«ã¿ãç»é²
env = Environment(loader=FileSystemLoader('templates'))
env.filters['phone'] = format_phone # 'phone' ãšããååã§ãã£ã«ã¿ãç»é²
# ãã³ãã¬ãŒããããŒã
template = env.get_template('user_profile.html')
# ã¬ã³ããªã³ã°
user_data = {"phone_number": "09012345678"}
output = template.render(user=user_data)
print(output)
templates/user_profile.html:
<p>é»è©±çªå·: {{ user.phone_number | phone }}</p>
{# åºåäŸ: <p>é»è©±çªå·: 090-1234-5678</p> #}
ã«ã¹ã¿ã ãã£ã«ã¿ã䜿ãããšã§ããã³ãã¬ãŒãå ã§ã®è¡šçŸåãããã«é«ããããšãã§ããŸããð
ãã¯ã: åå©çšå¯èœãªãã³ãã¬ãŒãéšå ð§©
ãã¯ãã¯ããã³ãã¬ãŒãå ã§ç¹°ãè¿ã䜿çšãããHTMLã®æçãããžãã¯ããé¢æ°ã®ããã«å®çŸ©ããŠåå©çšããããã®æ©èœã§ããããã«ããããã³ãã¬ãŒããããã¢ãžã¥ãŒã«åããä¿å®ããããããããšãã§ããŸãã
`{% macro %}` ã¿ã°ã䜿ã£ãŠãã¯ããå®çŸ©ããPythonã®é¢æ°ã®ããã«åŒã³åºãããšãã§ããŸããåŒæ°ãåãããšãå¯èœã§ãã
ãã¯ãã®å®çŸ©ãšäœ¿çšäŸ
{# --- ãã¯ãã®å®çŸ© --- #}
{% macro render_input(name, label, type='text', value='', placeholder='') %}
<div class="field">
<label class="label" for="{{ name }}">{{ label }}</label>
<div class="control">
<input class="input" type="{{ type }}" id="{{ name }}" name="{{ name }}" value="{{ value | e }}" placeholder="{{ placeholder | e }}">
</div>
</div>
{% endmacro %}
{# --- ãã¯ãã®åŒã³åºã --- #}
<form action="/submit" method="post">
{{ render_input('username', 'ãŠãŒã¶ãŒå', placeholder='Your Username') }}
{{ render_input('email', 'ã¡ãŒã«ã¢ãã¬ã¹', type='email', placeholder='your@email.com') }}
{{ render_input('password', 'ãã¹ã¯ãŒã', type='password') }}
<div class="field is-grouped">
<div class="control">
<button class="button is-link">éä¿¡</button>
</div>
</div>
</form>
ãã®äŸã§ã¯ã`render_input` ãšãããã¯ããå®çŸ©ãããã©ãŒã å ã®åå ¥åãã£ãŒã«ããçæããããã«åŒã³åºããŠããŸããåŒæ°ã䜿ã£ãŠããã£ãŒã«ãã®ååãã©ãã«ãã¿ã€ããªã©ãã«ã¹ã¿ãã€ãºããŠããŸããããã«ãããåããããªHTMLæ§é ãäœåºŠãæžãå¿ èŠããªããªããŸããDRYååäžæ³ïŒð
ãã¯ããå¥ãã¡ã€ã«ã«åé¢ããŠã€ã³ããŒã
ãã¯ããè€éã«ãªã£ãããè€æ°ã®ãã³ãã¬ãŒãã§äœ¿ããããªã£ããããå Žåã¯ããã¯ãå®çŸ©ãå¥ã®ãã¡ã€ã«ïŒäŸ: `_macros.html`ïŒã«ãŸãšããŠã`{% import %}` ã¿ã°ã䜿ã£ãŠã€ã³ããŒãããããšãã§ããŸãã
templates/_macros.html:
{# å
¥åãã£ãŒã«ãçæãã¯ã #}
{% macro render_input(name, label, type='text', value='', placeholder='') %}
<div class="field">
<label class="label" for="{{ name }}">{{ label }}</label>
<div class="control">
<input class="input" type="{{ type }}" id="{{ name }}" name="{{ name }}" value="{{ value | e }}" placeholder="{{ placeholder | e }}">
</div>
</div>
{% endmacro %}
{# ãã¿ã³çæãã¯ã #}
{% macro render_button(text, type='submit', css_class='is-primary') %}
<button class="button {{ css_class }}" type="{{ type }}">{{ text }}</button>
{% endmacro %}
templates/login_form.html:
{% extends "base.html" %}
{% import "_macros.html" as forms %} {# _macros.html ã 'forms' ãšããååã§ã€ã³ããŒã #}
{% block title %}ãã°ã€ã³{% endblock %}
{% block content %}
<h1 class="title">ãã°ã€ã³ããŠãã ãã</h1>
<form action="/login" method="post">
{{ forms.render_input('username', 'ãŠãŒã¶ãŒå') }} {# ã€ã³ããŒããããã¯ããäœ¿çš #}
{{ forms.render_input('password', 'ãã¹ã¯ãŒã', type='password') }}
<div class="field">
<div class="control">
{{ forms.render_button('ãã°ã€ã³', css_class='is-link') }}
</div>
</div>
</form>
{% endblock %}
`{% import “_macros.html” as forms %}` ã®ããã«ã€ã³ããŒããã`forms.render_input(…)` ã®ããã«åŒã³åºããŸããããã«ããããã¯ãã®ç®¡çãšåå©çšãããã«å®¹æã«ãªããŸãã倧èŠæš¡ãªãããžã§ã¯ãã§ã¯å¿ é ã®ãã¯ããã¯ãšèšããã§ããããð§
ãã®ä»ã®äŸ¿å©ãªæ©èœ âïž
include: ãã³ãã¬ãŒãã®éšåå
`{% include %}` ã¿ã°ã䜿ããšãå¥ã®ãã³ãã¬ãŒããã¡ã€ã«ã®å 容ãçŸåšã®å Žæã«æ¿å ¥ã§ããŸããããã¯ãããããŒãããã¿ãŒããµã€ãããŒãªã©ãè€æ°ã®ããŒãžã§å ±éããŠäœ¿ãããããç¶æ¿ã䜿ãã»ã©æ§é çã§ã¯ãªãéšåãçµã¿èŸŒãã®ã«äŸ¿å©ã§ãã
{# templates/page.html #}
{% extends "base.html" %}
{% block content %}
<div class="columns">
<div class="column is-three-quarters">
<h1>ã¡ã€ã³ã³ã³ãã³ã</h1>
<p>...</p>
</div>
<div class="column">
{% include "sidebar.html" %} {# sidebar.html ãããã«æ¿å
¥ #}
</div>
</div>
{% endblock %}
`include` ããããã³ãã¬ãŒã (`sidebar.html`) ã¯ãçŸåšã®ã³ã³ããã¹ãïŒå€æ°ïŒã«ã¢ã¯ã»ã¹ã§ããŸãã
ã°ããŒãã«å€æ°ãšé¢æ°
`Environment` ãªããžã§ã¯ãã® `globals` å±æ§ã«èŸæžãé¢æ°ãè¿œå ããããšã§ããã¹ãŠã®ãã³ãã¬ãŒãããã¢ã¯ã»ã¹å¯èœãªã°ããŒãã«å€æ°ãé¢æ°ãå®çŸ©ã§ããŸãããµã€ãå šäœã§äœ¿ãå®æ°ããŠãŒãã£ãªãã£é¢æ°ãªã©ãç»é²ããã®ã«äŸ¿å©ã§ãã
from jinja2 import Environment, FileSystemLoader
import datetime
def get_current_year():
return datetime.datetime.now().year
env = Environment(loader=FileSystemLoader('templates'))
# ã°ããŒãã«å€æ°ãè¿œå
env.globals['site_name'] = 'My Awesome Site'
# ã°ããŒãã«é¢æ°ãè¿œå
env.globals['current_year'] = get_current_year
# ããã§ãã©ã®ãã³ãã¬ãŒãããã§ã {{ site_name }} ã {{ current_year() }} ã䜿ããããã«ãªã
template = env.get_template('footer.html')
print(template.render())
templates/footer.html:
<footer>
© {{ current_year() }} {{ site_name }}. All rights reserved.
</footer>
ãã¹ã (Tests)
ãã¹ãã¯ãå€æ°ãç¹å®ã®æ¡ä»¶ãæºãããã©ããããã§ãã¯ããããã®æ©èœã§ãã`is` ããŒã¯ãŒãã䜿ã£ãŠãifæãªã©ã§äœ¿çšããŸãã
{% if my_variable is defined %}
<p>å€æ°ã¯å®çŸ©ãããŠããŸã: {{ my_variable }}</p>
{% endif %}
{% if count is divisibleby(2) %}
<p>{{ count }} ã¯å¶æ°ã§ãã</p>
{% endif %}
{% if user.role is sameas('admin') %}
<p>管çè
ãŠãŒã¶ãŒã§ãã</p>
{% endif %}
{% if score is number %}
<p>ã¹ã³ã¢ã¯æ°å€ã§ãã</p>
{% endif %}
ãã䜿ããããã¹ãã«ã¯ `defined`, `undefined`, `divisibleby`, `escaped`, `even`, `odd`, `iterable`, `lower`, `upper`, `number`, `string`, `sequence`, `sameas` ãªã©ããããŸããã«ã¹ã¿ã ãã¹ããäœæããããšãå¯èœã§ãã
ãã¯ã€ãã¹ããŒã¹ã®å¶åŸ¡
Jinja2ã¯ãã³ãã¬ãŒãå ã®ãã¯ã€ãã¹ããŒã¹ïŒç©ºçœãã¿ããæ¹è¡ïŒãåºæ¬çã«ä¿æããŸãããã¿ã°ã®ååŸã«ãããã¯ã€ãã¹ããŒã¹ãå¶åŸ¡ãããå ŽåããããŸããã¿ã°ã®çŽåŸã«ãã€ãã³ (`-`) ãä»ãããšãã®çŽåã®ãã¯ã€ãã¹ããŒã¹ããã¿ã°ã®çŽåã«ãã€ãã³ãä»ãããšãã®çŽåŸã®ãã¯ã€ãã¹ããŒã¹ãåé€ãããŸãã
<ul>
{% for item in items -%} {# li ã®åã®æ¹è¡ãšç©ºçœãåé€ #}
<li>{{ item }}</li>
{%- endfor %} {# ul ã®åã®æ¹è¡ãšç©ºçœãåé€ #}
</ul>
`Environment` ã®èšå®ã§ `trim_blocks` ã `lstrip_blocks` ãæå¹ã«ãããšããããã¯ã¿ã° (`{% … %}`) åšèŸºã®ãã¯ã€ãã¹ããŒã¹ãèªåçã«å¶åŸ¡ããããšãã§ããŸãã
ã»ãã¥ãªãã£: èªåãšã¹ã±ãŒããšãµã³ãããã¯ã¹ ð¡ïž
Webã¢ããªã±ãŒã·ã§ã³ã§ãã³ãã¬ãŒããšã³ãžã³ã䜿çšããéã«ã¯ãã»ãã¥ãªãã£ãç¹ã«ã¯ãã¹ãµã€ãã¹ã¯ãªããã£ã³ã° (XSS) æ»æã«æ³šæããå¿ èŠããããŸããXSSã¯ãæªæã®ãããŠãŒã¶ãŒãå ¥åãã©ãŒã ãªã©ãéããŠã¹ã¯ãªããïŒéåžžã¯JavaScriptïŒãæ³šå ¥ããä»ã®ãŠãŒã¶ãŒã®ãã©ãŠã¶ã§ãããå®è¡ãããŠããŸãæ»æã§ãã
Jinja2ã¯ããã®ãªã¹ã¯ã軜æžããããã®éèŠãªæ©èœãæäŸããŠããŸãã
èªåãšã¹ã±ãŒã (Autoescaping)
Jinja2ã®æãéèŠãªã»ãã¥ãªãã£æ©èœã¯èªåãšã¹ã±ãŒãã§ãã`Environment` ãäœæããéã« `autoescape=True` (ãŸã㯠`select_autoescape` ã䜿çšããŠç¹å®ã®æ¡åŒµåã«å¯ŸããŠæå¹å) ãèšå®ãããšãããã©ã«ã㧠`{{ … }}` ã§åºåããããã¹ãŠã®å€æ°ãHTMLãšã¹ã±ãŒããããŸãã
HTMLãšã¹ã±ãŒããšã¯ãHTMLã§ç¹å¥ãªæå³ãæã€æåïŒ `<`, `>`, `&`, `’`, `”` ãªã©ïŒããããã«å¯Ÿå¿ããå®å šãªãšã³ãã£ãã£ïŒ`<`, `>`, `&`, `'`, `"` ãªã©ïŒã«å€æããããšã§ããããã«ãããããšãå€æ°ã«æªæã®ããã¹ã¯ãªãããå«ãŸããŠããŠãããããåãªãæååãšããŠè¡šç€ºããããã©ãŠã¶ã§å®è¡ãããã®ãé²ããŸãã
from jinja2 import Environment, select_autoescape, FileSystemLoader
env = Environment(
loader=FileSystemLoader("templates"),
autoescape=select_autoescape(['html', 'xml', 'jinja2']) # html, xml, jinja2 æ¡åŒµåã§èªåãšã¹ã±ãŒãæå¹
)
template = env.get_template("unsafe_content.html")
# ãŠãŒã¶ãŒããã®å
¥åïŒæªæã®ããå¯èœæ§ïŒ
user_input = "<script>alert('XSSæ»æ!');</script>"
output = template.render(comment=user_input)
print(output)
templates/unsafe_content.html:
<p>ãŠãŒã¶ãŒã³ã¡ã³ã:</p>
<div class="comment-box">
{{ comment }} {# ããã§èªåãšã¹ã±ãŒããé©çšããã #}
</div>
åºåãããHTMLã¯ä»¥äžã®ããã«ãªããã¹ã¯ãªããã¯å®è¡ãããŸããã
<p>ãŠãŒã¶ãŒã³ã¡ã³ã:</p>
<div class="comment-box">
<script>alert('XSSæ»æ!');</script>
</div>
ååãšããŠãWebã¢ããªã±ãŒã·ã§ã³ã§ã¯åžžã«èªåãšã¹ã±ãŒããæå¹ã«ããŠãã ããã æå³çã«HTMLãåºåãããå Žåã®ã¿ã`safe` ãã£ã«ã¿ã䜿çšããŸããããã®å€æ°ã®å 容ã絶察ã«å®å šã§ãããšç¢ºä¿¡ã§ããå Žåã«éå®ããŠãã ããã
ãµã³ãããã¯ã¹å®è¡ (Sandboxed Environment)
ãããä¿¡é Œã§ããªããŠãŒã¶ãŒã«ãã³ãã¬ãŒãã®ç·šéãèš±å¯ãããããªç¶æ³ïŒäŸãã°ãCMSã®ããŒãç·šéæ©èœãªã©ïŒãããå Žåã`SandboxedEnvironment` ã䜿çšããããšãæ€èšããŠãã ããã
`SandboxedEnvironment` ã¯ããã³ãã¬ãŒãå ã§ã¢ã¯ã»ã¹ã§ããå±æ§ãã¡ãœãããå¶éããæœåšçã«å±éºãªæäœïŒãã¡ã€ã«ã®èªã¿æžããå éšå±æ§ãžã®ã¢ã¯ã»ã¹ãªã©ïŒãé²ããŸããããã«ããããã³ãã¬ãŒããã·ã¹ãã ã«æªåœ±é¿ãäžãããªã¹ã¯ãäœæžããŸãã
from jinja2.sandbox import SandboxedEnvironment
# ãµã³ãããã¯ã¹ç°å¢ãäœæ
# ããã©ã«ãã§å®å
šã§ãªãæäœã¯ãããã¯ããã
env = SandboxedEnvironment()
# ä¿¡é Œã§ããªããã³ãã¬ãŒãæåå
untrusted_template_string = """
{# user.__init__ ã¯éåžžã¢ã¯ã»ã¹ã§ããªã #}
{{ user.__init__('Illegal access attempt') }}
"""
class User:
def __init__(self, name):
self.name = name
user = User("Test")
try:
template = env.from_string(untrusted_template_string)
output = template.render(user=user)
print(output)
except Exception as e:
# SecurityError ãçºçãã
print(f"ãšã©ãŒãçºçããŸãã: {e}")
ãµã³ãããã¯ã¹ç°å¢ã䜿ãããšã§ãããå®å šã«ãã³ãã¬ãŒããæ±ãããšãã§ããŸãããå©çšã§ããæ©èœã«å¶éããããç¹ã«æ³šæãå¿ èŠã§ãã
ãŸãšããšæŽ»çšäŸ ð¡
Jinja2ã¯ãPythonã«ãããéåžžã«åŒ·åã§æè»ãªãã³ãã¬ãŒããšã³ãžã³ã§ããåºæ¬çãªå€æ°ã®åã蟌ã¿ãããå¶åŸ¡æ§é ããã³ãã¬ãŒãç¶æ¿ããã£ã«ã¿ããã¯ãããããŠã»ãã¥ãªãã£æ©èœãŸã§ãè±å¯ãªæ©èœãæäŸããŠããŸãã
äž»ãªç¹åŸŽã®å確èª:
- ð Pythonã©ã€ã¯ã§çŽæçãªæ§æ
- ð é«éãªå®è¡é床 (ã³ã³ãã€ã«ã«ããæé©å)
- ðïž ãã³ãã¬ãŒãç¶æ¿ã«ããé«ãåå©çšæ§
- âš ãã£ã«ã¿ã«ããæè»ãªããŒã¿å å·¥
- 𧩠ãã¯ãã«ããéšåå
- ð¡ïž èªåãšã¹ã±ãŒããšãµã³ãããã¯ã¹ã«ããã»ãã¥ãªãã£
- âïž é«ãæ¡åŒµæ§ (ã«ã¹ã¿ã ãã£ã«ã¿ãã¿ã°ããã¹ã)
Jinja2ã®æŽ»çšäŸ:
- Webã¢ããªã±ãŒã·ã§ã³éçº: Flask, Django (ãªãã·ã§ã³), Bottle ãªã©ã®ãã¬ãŒã ã¯ãŒã¯ãšçµã¿åãããŠãåçãªHTMLããŒãžãçæããããŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ã®æ§ç¯ã«äžå¯æ¬ ã§ãã
- èšå®ãã¡ã€ã«ã®çæ: Ansible, SaltStack ãªã©ã®æ§æ管çããŒã«ã§ãç°å¢ããšã«ç°ãªãèšå®ãã¡ã€ã«ïŒNginxèšå®ãDocker Composeãã¡ã€ã«ãªã©ïŒããã³ãã¬ãŒãããçæããã
- éçãµã€ãçæ: Pelican ãªã©ã®éçãµã€ããžã§ãã¬ãŒã¿ãŒã§ãMarkdownãªã©ã®ã³ã³ãã³ããšãã³ãã¬ãŒããçµã¿åãããŠHTMLãµã€ããæ§ç¯ããã
- ã³ãŒãçæ: ç¹å®ã®ãã¿ãŒã³ã«åºã¥ãããœãŒã¹ã³ãŒããããã¥ã¡ã³ããèªåçæããã
- ã¡ãŒã«ãã³ãã¬ãŒã: åçãªå 容ïŒãŠãŒã¶ãŒåã泚ææ å ±ãªã©ïŒãå«ãHTMLã¡ãŒã«ãçæããã
- ããŒã¿å€æãšã¬ããŒãçæ: ããŒã¿ããŒã¹ããååŸããããŒã¿ãæŽåœ¢ãã人éãèªã¿ããã圢åŒã®ã¬ããŒãïŒHTML, CSV, XMLãªã©ïŒãçæããã
Jinja2ããã¹ã¿ãŒããããšã§ãPythonã䜿ã£ãéçºã®å¹çãšè¡šçŸåãå€§å¹ ã«åäžãããããšãã§ããŸããWebéçºã ãã§ãªããæ§ã ãªããã¹ãçæã¿ã¹ã¯ã«å¿çšå¯èœã§ãããã²ãããªãã®ãããžã§ã¯ãã§Jinja2ã掻çšããŠã¿ãŠãã ããïŒ ð
ãã詳ããæ å ±ãææ°æ å ±ã«ã€ããŠã¯ãJinjaå ¬åŒããã¥ã¡ã³ããåç §ããããšããå§ãããŸãã
ã³ã¡ã³ã