January 31, 2021
←前回:Day 23 テンプレートタグを使ってサイドバーを作成する
「Djangoを学びたい」とのことでありましたら[Day 1]Djangoの開発環境から読むことをおすすめします。
#はじめに
前回テンプレートタグを作成しました。今回はテンプレート上で用いる独自フィルターを使用していきます。
#Djangoテンプレートのフィルター
Djangoのテンプレートには他のフレームワーク同様にテンプレート上でフィルター処理を行えます。
そのため、予め組み込まれた組み込みフィルターが用意されています。
例えば、truncatecharsという組込みフィルターがありますが、これは{{ context.hoge | truncatechars:10 }}と書くと10文字に切り詰めて表示するという機能を持つフィルターです。
#カスタムフィルターを作る
ではカスタムフィルターを作成していきましょう。
今回はコメントにURLが記載された場合リンクとして表示するフィルターを作成しましょう。
thread/templatetags/threadfilters.pyを作成します。
from django.template import Library
register = Library()
@register.filter
def comment_filter(text):
return ''.join(list(map(convert_url, text.split('\n'))))
def convert_url(text_line):
'''
URLリンク行をaタグ付きの行に変換
'''
if 'https://' in text_line or 'http://' in text_line:
return '<a href="' + text_line + '" target="_blank" rel="noopener noreferrer">' + text_line + '</a>'
else:
return text_line
register.filterメソッドは引数nameを省略すると関数の名前がフィルター名として適用されます。
今回はcomment_filterですね。
前回のテンプレートタグと同様にthreadfiltersをロードしてcomment_filterを適用することにします。
{% extends 'base/base.html' %}
{% block title %}トピック作成 - {{ block.super }}{% endblock %}
{% block content %}
{% load threadfilters %}
<div class="ui grid stackable">
<div class="eleven wide column">
<div class="ui breadcrumb">
<a href="{% url 'base:top' %}" class="section">TOP</a>
<i class="right angle icon divider"></i>
<a href="{% url 'thread:category' url_code=topic.category.url_code %}" class="section">{{topic.category.name}}</a>
<i class="right angle icon divider"></i>
<a class="active section">{{topic.title}}</a>
</div>
<div class="ui segment">
<div class="content">
<div class="header"><h3>{{topic.title}}</h3></div>
<p>{{topic.user_name}} - {{topic.created}}</p>
<div class="ui segment">
<p><pre>{{topic.message}}</pre></p>
</div>
</div>
</div>
<!--コメント表示-->
<div class="ui segment">
{% if comment_list %}
{% for comment in comment_list %}
<div class="ui segment secondary">
<p>{{comment.no}}. {{comment.user_name}}<br>{{comment.created}}</p>
{% if comment.pub_flg %}
<p>{{comment.message | comment_filter}}</p>
{% else %}
<p style="color: #aaa">このコメントは非表示となりました。</p>
{% endif %}
</div>
{% endfor %}
{% else %}
<div class="ui warning message"><p>まだコメントはありません</p></div>
{% endif %}
</div>
<!--//コメント表示-->
<!--コメント投稿-->
<h4>コメント投稿</h4>
<div class="ui segment">
<form class="ui form" action="" method="POST">
{% csrf_token %}
{{form.as_p}}
<button class="ui button orange" type="submit">コメント投稿</button>
</form>
</div>
<!--//コメント投稿-->
</div>
{% include 'base/sidebar.html' %}
</div>
{% endblock %}
このようになります。{% load threadfilters %}でフィルターを読み込んでいます。
{{comment.message | comment_filter}}の部分でコンテキストで渡された文字列を変換しています。
ではブラウザで確認してみましょう。
URLを含んだ投稿をします。(今回のケースではURL行は改行される必要があります。)
変換された結果
あれ?ちょっとおかしな結果になりましたね。原因を考えていきましょう。
#Djangoテンプレートエスケープ処理
確かに文字列は変換されていますが、HTMLタグがそのまま表示されてしまいました。
これはどういうことでしょうか?
実はDjangoテンプレートはデフォルトでエスケープ機能がONになっていて、コンテキストで渡された文字列はエスケープ処理が施されて出力されることになっています。
PHPでいうとhtmlspecialchars関数が適用されたような状態です。
これにより万が一コンテキストに悪意のあるスクリプトが渡されたとしても実行を防ぐ作用をしています。(ただし過信は禁物です。)
今回のようにフィルターでHTMLを生成する場合にはエスケープ機能をOFFにする必要があります。
しかし、この機能を外す際にはプログラマーは十分に注意する必要があります。今回はbleachライブラリを使用してエスケープ処理をした後にHTML変換を書けることで対応してみます。
まずはbleachをインストールしましょう。
(venv)$ pip install bleach
次にthread/templatetags/threadfilters.pyを下記のように書き換えます。
from django.template import Library
import bleach
register = Library()
@register.filter
def comment_filter(text):
return ''.join(list(map(convert_url, bleach.clean(text).split('\n'))))
def convert_url(text_line):
'''
URLリンク行をaタグ付きの行に変換
'''
if 'https://' in text_line or 'http://' in text_line:
return '$lt;a href="' + text_line + '" target="_blank" rel="noopener noreferrer">' + text_line + '</a>'
else:
return text_line
先で説明した通り、テンプレートの機能でエスケープ処理が行われないために、関数内部でエスケープ処理をしています。ではテンプレートを修正しましょう。
- <p>{{comment.message | comment_filter}}</p>
+ <p>{{comment.message | comment_filter | safe}}</p>
このようにsafeフィルターを付けることでテンプレートのエスケープ処理が外れます。
これはプログラマが’safe’な文字列であることを保証するという意味です。
その意味を考えたら気軽には使えませんよね。
今回はbleach関数で手軽に事前エスケープで対応しましたが、実際はもっと気を配るケースが多いと思います。
ではブラウザでどのように表示されるかを確認してみましょう。
URLリンクが想定通り出力されたでしょうか?
#おわりに
今日で2021年1月も終わりですね。
もうすぐ節分です。
僕のバイト先でも恵方巻きの準備が着々と進んでいます。
毎年、恵方巻き残りすぎニュースを目にすると思いますが、僕のバイト先では毎回全部売り切ります。
ニュースが先行してしまって「恵方巻き=残るもの」と世間では、なってしまっていますが、大量に残るのは特定の店なのかな?と思ったりします。(大量には変わりないのですが。。。)
それではまたまた
←前回:Day 23 テンプレートタグを使ってサイドバーを作成する
→次回:Day 25 DjangoのAPIとAjax通信する「いいねボタン」を作成する