[Django] 日本ひげ男協会のサイトを作成する。その9
注意
この記事は、id:SumiTomohiko:20070127:1169923192の続きです。
フィルタ
Webアプリケーションを作成すると、モデルの内容を加工して表示する場面がしょっちゅうあります。例えば、モデルが持つURLをリンクとして表示する場合などです。Model-View-Controller(DjangoではModel-Template-View)モデルにおける役割分担の上では、このような変換はビュー(Djangoではテンプレート)で行うことになります。URLをリンクに変換する例の場合、テンプレートで次のように記述します。
{{ object.web|urlize }}
この、"|urlize"と記述するとURLがリンクになる機能を、フィルタといいます(PHPに詳しい方なら、Smartyにも同じ機能があることをご存知かと思います)。
日本ひげ男協会のサイトでも、このフィルタを使いたい箇所があります。具体的には、以下のところです。
- ひげをはやし始めた年から、現在までの経過年数(ひげ年齢)を計算する。
- 自己紹介文が長い場合、会員一覧画面では最初の方しか表示しない。
- 会員一覧画面で、ひげの写真を縮小して表示するため、画像の大きさから<img>タグのheight属性とwidth属性を計算する。
これらの実現方法を、個別に説明します。
ひげ年齢
ひげ年齢は、ひげをはやし始めた年から計算できますので、テンプレートで、
{{ object.from_year|age }}
と書いたら年齢が表示されるようにしたいです。
そのためには、まず始めにuser/templatetagsディレクトリを作成します。それから、user/templatetagsディレクトリに、__init__.pyという空のファイルを作成します。フィルタの実装は、user/templatetags/user_extras.pyに記述します。
from django import template import datetime register = template.Library() @register.filter(name="age") def _age(value): return datetime.datetime.now().year - value
この中の、@register.filter(name="age")というデコレータで、_age関数をageという名前で、使用できるフィルタとして登録しています。_age関数には、フィルタの前にある値が、value引数を通して渡されます。_age関数の戻り値が、実際に表示される(さらにフィルタが続いていたら、次のフィルタに渡される)値になります。
このフィルタを使うテンプレートでは、以下の一文を挿入します。
{% load user_extras %}
これにより、user/templatetags/user_extras.pyに含まれるフィルタが使用できるようになります。使いかたは、上の例で示した通りです。
自己紹介文を省略する。
自己紹介文が長い場合、全文は詳細画面で読んでもらうことにして、一覧画面では何文字かだけ表示するようにします。このとき、何文字表示するかもテンプレートで指定できたら、デザインの変更に柔軟に対応できて便利です。
このような場合、フィルタに引数を与えることができます。例えば、以下の通りです。
{{ object.introduction|cut:"20" }}
このとき、cutフィルタには、"20"という引数が与えられます。このようなフィルタは、次のようにして記述します。
@register.filter(name="cut") def _cut(value, length): try: length = int(length) except ValueError: return "" value = unicode(value, "utf-8") # Only for SQLite? if len(value) <= length: return value return value[:length] + "..."
単に、フィルタ本体の関数に引数が増えるだけです。ただし、引数は文字列となっているので、内部で適切な型に変換してから使用してください。
なお、上の_cut関数の途中にある、
value = unicode(value, "utf-8") # Only for SQLite?
は、文字列をユニコード文字列に変換する、という意味です。データベースにはSQLiteを使用しているのですが、データベースから文字列を読み込むと、(ユニコード文字列ではなく)ただの文字列となってしまうようで、len関数やスライスが思い通りに動作しません。なので、上の一文をいれて回避しています。本当はデータベースから読み込むところで変換した方がいいのでしょうが、データベースの種類が変わったら必要なくなりそうなので、今回は応急手当で済ませています。
画像を縮小表示する。
一覧画面では、画像を縮小表示、いわゆるサムネイルを表示します。ここでも横着します。本当は、画像がアップロードされた時点でサムネイル用の画像を生成し、閲覧時にダウンロードする容量を節約するのが筋なのですが、今回は単に<img>タグのheight属性とwidth属性の値を小さく指定するだけにします。フィルタは、height用とwidth用の2つが必要になります。
フィルタの実装は、次のようになります。
def _get_thumbnail_size(user, max_height=None, max_width=None): if max_height == None: max_height = juma.user.settings.THUMBNAIL_MAX_HEIGHT if max_width == None: max_width = juma.user.settings.THUMBNAIL_MAX_WIDTH height = user.get_image_height() width = user.get_image_width() if (height <= max_height) and (width <= max_width): return (height, width) elif (height <= max_height) and (max_width < width): ratio = float(max_width) / float(width) return (int(height * ratio), max_width) elif (max_height < height) and (width <= max_width): ratio = float(max_height) / float(height) return (max_height, int(width * ratio)) else: height_ratio = float(max_height) / float(height) width_ratio = float(max_width) / float(width) if height_ratio < width_ratio: return (int(height * height_ratio), int(width * height_ratio)) else: return (int(height * width_ratio), int(width * width_ratio)) @register.filter(name="thumbnail_height") def _get_thumbnail_height(user, max_height=None, max_width=None): return _get_thumbnail_size(user, max_height, max_width)[0] @register.filter(name="thumbnail_width") def _get_thumbnail_width(user, max_height=None, max_width=None): return _get_thumbnail_size(user, max_height, max_width)[1]
使うときは、次のように記述します。
<img height="{{ object|thumbnail_height }}" width="{{ object|thumbnail_width }}" src="foo"/>
テンプレートの継承
日本ひげ男協会のサイトでは、テンプレートの継承を利用しています。これは、各画面の共通点をひとつのファイルに記述しておき、各画面はそこからの差分のみを記述する、という方法です。これに関しては、テンプレート作者のための Django テンプレート言語ガイド以上に説明することがないので、そちらをご参照下さい。