年明けから、ようやくPythonを始める時間が取れて、Google App Engineにも採用されているDjangoを使ってみることにした。言語もフレームワークもどっちも知らないって状態から、両方を調べながらサンプルアプリケーションを作ってみては、色々と試行錯誤しているので、すごく時間がかかっている。もしかしたら、かなり突拍子もない実装をしているのかもしれない。で、手を動かして考えてみたけど、Djangoは良く考えられてるなと思った。今日のエントリーはその過程で考えてみたバリデーション方法について。

前フリ

DjangoにはFormというのがあって、

  • 入力フィールドのhtmlタグへの変換
  • 入力値のバリデーション(ならびに、エラー文字列の表示と、DBでのデータ型の取得)
  • 入力値の受け渡し
  • 入力値の保存

あたりの機能をカプセル化している。ここでのバリデーションを、「requestの事前処理」という見方をすると、これってアスペクトか?と思えてきたので、あとはそのルールをどの入力フィールドに適用するか、という観点で実装したのが以下のソース。ここではPythonのデコレータを使っている。Pythonのデコレータの書き方は、.NETのAttributeとかJavaのAnnotationと書き方が似ているけど、中身は別物(のはず)。とりあえず、簡単そうな例として、必須入力フィールドの検証を載せてみる。

# -*- encoding:utf-8 -*-
from django.http import HttpRequest
def invalid(dics):
"""
@summary: 検証結果が妥当かどうかをチェックします。
@param dics: 検証結果ディクショナリ(dics['validation'][検証項目])。引数にTrueが含まれている場合:False、含まれていない場合:True
"""
for dic in dics.values():
if any(dic.values()):
return False
return True
def get_request(fields, args):
"""
@summary: argsからHttpRequestオブジェクトを取得します。fieldsがNoneの場合、args[0]がHttpRequestオブジェクトでない場合は例外を発生させます。
@param fields:検証対象リスト
@param args: HttpRequestオブジェクト
"""
if fields is None or len(fields) == 0 or args is None or len(args) == 0 or not isinstance(args[0], HttpRequest):
#例外(どの例外使うのがいいんだろ。とりあえずExceptionで。)
raise Exception('validation argument exception.')
else:
return args[0]
def is_empty(str):
"""
@summary:文字列がNone、または空かどうかを判定します。スペースのみの文字列も空と判定します。
@param str: 判定する文字列。
"""
#判定
return True if str is None or len(str.strip()) == 0 else False
def required_field(fields=None, **kwargs):
"""
@summary: 必須入力フィールドに値が入力されているかをチェックします。
@param fileds: 必須入力となるフィールド名のリスト(id名)
@param **kwargs: 検証結果ディクショナリ。上位の検証デコレータによって引数が追加されます。
"""
def actual_decorator(org_f=None):
"""
@summary:必須入力フィールドの検証デコレータです。
@param org_f: デコレーションされる関数。org_fの第一引数はrequestを取ります。
"""
if org_f is None:
return None
def decorated_f(*args):
"""
@summary: 必須入力フィールドを検証します。
@param *args: デコレーションされる関数の引数。
"""
#事前準備はアスペクトなので、この処理もデコレータにした方がいいかも?
#パラメータ検証・リクエスト取得
request = get_request(fields, args)
#検証結果ディクショナリ
if not 'validation' in kwargs:
kwargs['validation'] = {}
#検証ロジック
dic = {}
for f in fields:
dic[f] = is_empty(request.POST.get(f))
kwargs['validation']['is_empty'] = dic
return org_f(*args, **kwargs)
return decorated_f
return actual_decorator

で、view関数では以下のようにして使う。

# -*- encoding:utf-8 -*-
from django.shortcuts import render_to_response
from validator import required_field, invalid
@required_field(['username', 'tel'])
def post(request, validation=None):
return render_to_response('main.html', {
'valid': invalid(validation),
'message': validation})

Formを使う場合にはrequest.methodがGETかPOSTかで関数内の処理を分けるのがマナーらしいが、サンプルではgetの場合、postの場合としていたので、上の例ではpost時のview関数のみを載せた。main.htmlに表示しているのは、以下の2つ。

  • username
  • tel

それぞれが必須入力項目とした場合に、その検証結果を表示するという内容。

まとめ

作ってみたはいいものの、Formを使った方がコードがすっきりしたので、Djangoではあまり使い道がないのかもしれない。ただし、Formだけでうまくいかない事があれば使う場合も出てくるかもしれないし、むしろwebじゃない場合の検証の実装方法としてはありなのかも、と思った。あるいはクエリ文字列の検証とか。

後で、もう少しきれいにしておきたい気がする。