たった数個のチェックボックスに対してのバリデーションに悩んでました。
要求:

  1. 個数は可変
  2. アプリが提供する、いくつかの決まった値とANDである必要がある(想定している正しい値である必要がある)
  3. いづれか一個は選択されていなければならない
  4. いっそ面倒なので値は必ず数字である
  5. エラーチェックは以下のとおり。
  6. 0こチェック状態は認めない
  7. [1,2,3]と想定したなら’4’も’A’も不正

……普通に考えたらきわめて楽勝な条件よね。よくあるでしょこんなの。
少なくともphpなら書くの5分ですよ。
このチェック機構を実装するだけで実に一週間以上悩む羽目になりました。
PythonがアホとかPylonsが云々とかいう理由ではないですが、FormEncodeでのバリデーションをするにあたって、上記の要求がいささか面倒なものだったのかもしれず、あたしには難儀なパズルでした。
いや、よくあるケースだと思うので勘違いだろうとは思うんだ。
「すべての中の最低ひとつでも選択しているか」という条件をどう拾うかというのが壁になりました。
蓋が開いてみれば、大前提への理解の問題だったんですけどね。

以下にトラブルの内容を羅列。
FormEncodeの使用方法で間違っている可能性は大いにあるので、勘違いも含まれているとは思います。

  • Setなどを使わない限りmissingValueで定義されてる例外”Missing value”が表示される。
  • 逆に言うとSetじゃ空でもスルーしてしまって使えない。
  • “Missing value”メッセージがでる条件下では、Validatorにto_python()を記述して各種条件で拾おうとしても状態を取り出せない
  • クラスをさかのぼっていくと、”Missing value”が定義されているのはclass Schema(FancyValidator)。ちょwほとんど一番上w
  • つまりto_python以前にraiseされてて検証ロジックを通せてない?
  • 無条件raiseを書いてみてもやっぱりmissingValue表示。自前Validatorでは引っ掛けるの無理なのか?

で、ふと思いついた解決法を試したところ、あたくしの要件は満たせたので解決いたしました。
きわめてダサい解法。笑ってください。あたしは笑いました。
ダサい解決法を例示する前に、結果的に判明したわりと当然な事実を書いておきます。

  1. 一個もチェックされていない状態のcheckboxは送信されていない
  2. だからmissingValueになる
  3. だから値をとる以前に蹴られていた

ような気がします。これってkeyerrorとかになる気がするんだけど。

「じゃあ絶対に目的のnameのフィールドが送信されるようにすればいいんだな!?」

そういうことでした。

ダサい解決手順:

1.templateに値を仕込む

<label><input type=”checkbox” name=”type_cd” value=”1″/>帆船</label>
<label><input type=”checkbox” name=”type_cd” value=”2″/>複合船(ガレー船)</label>
<input type=”checkbox” name=”type_cd” value=”0″ checked=”checked” style=”visibility:hidden;”/>

このvisibility:hiddenなチェックボックスはhidden tagでも大丈夫。

あたしの場合は、最終的に各チェック済値の合算を取るつもりだったので、0なら未選択と等しい、という解釈が可能でした。
全チェック状態なら0+1+2、未選択なら0、問題ない。そしてもし0ならraise。

2.Validatorを書く。親クラスは何でもいい(たぶんFancyValidatorでいい)。

上の例で行けば、[0, 1, 2]のいずれかを最低一個含み、それ以外の値が送信されていないことを検証するValidatorを書きます。

class DasaiValidate(formencode.validators.FancyValidator):
    #input param
    __unpackargs__ = ('inputlist',)
    def _to_python(self, value, c):
    if len(value) <= 1:
        raise formencode.validators.Invalid(u'どれかひとつは選択してください', value, c)
    for x in value:
        if x not in self.inputlist:
            raise formencode.validators.Invalid(u'不正な値', value, c)

最低限の内容ですがこれで取りこぼしなくいけました。ほんとはもう少し書いてますが。
あとは呼び出し側で、

inputlist = ['0','1','2']
type_cd   = DasaiValidate(inputlist)

こう。inputlistの中身はこちらが許容する値のリスト。必ず送信されることが保障されている’0’がキモ。
ちゃんとしたPythonistaの人たちが見たら殴られそうですが、万が一目をつけられたときに赤ペン先生してもらえるかもという期待をこめてここに恥晒しエントリを残します。

すっごいあきれるくらい簡単な解決方法があるんだろうな、ほんとは。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です