FormEncode:checkboxのValidation

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

  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の人たちが見たら殴られそうですが、万が一目をつけられたときに赤ペン先生してもらえるかもという期待をこめてここに恥晒しエントリを残します。

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

Safariがフリーズする

おねーさんです。
別にあたしはMac好きでもなんでもないのですが、なんかモダンなブラウザは片っ端からいんすとろーるする癖がついてます。たぶん職業病。

たくさんのブラウザを立ち上げてるとみんなアイコンが同じで、且つタスクバーでまとめられてしまうので、目的のウインドウを探すのにタイムラグが発生することがあります。
「あ、そもそもアプリで分けちゃえば見分けつくじゃん→複数のブラウザをいっぺんに使う」
という、無駄にメモリを浪費するスタイルになりました。
alt+tabとかでバタバタ切り替えるの好きじゃないの。
で、アイコンが違うというだけの理由で在住を許され起動していたSafariさんが応答なしになることが増えました。なんなの。

どうしようと思案していたところ、ギア(設定?)のアイコンのところに「Safariをリセット」という項目があることに気づきました。
つまりたまにリセットでもしないと死んじゃうから優しくしてねってことですか。Macユーザで「Macは謝るから許せる」とかむかし意味不明なことを言ってた人がいたんですが、もはや謝らなくなった今もあの人はMac使ってるんでしょうか。どうでもいいですね。
あたしはWindowsさんなので謝ろうと|>_<|ノみたいなアイコンが出ようと凍ったり落ちたりするアプリは許さないわけですが 、はたしてリセット後のSafariはぱっと見順調であります。
リセットの中身は”溜め込んだいろんなのを消す”という至極簡単なもののようですが、抱えきれないくらい溜め込んだなら警告出すなりキャッシュ消すなりしなさいよ。ばかなの。

これが冴えた解決法なのかどうかはともかく、なんだよSafari固まるんだけどってひとはお試しあれ。加えて、もっといい解決方法があったら教えていただきたく。

業務連絡:1/28のらじおお休みの模様

波浪ノ
おねーさんです。
なんかお仕事で打ち合わせが入るらしく、帰りが遅そうです。
28日のらじおもお休みということでひとつよろしくお願いいたします。
二週連続になっちゃいますが楽しみにしてくださる奇特な皆様ごめんなさい。
ちなみにこの連絡が来たの昨晩01:30。なんなの。

進捗

出来:

船:一覧
パーツ:一覧
ユーザ:一覧・登録

半端:

セッション管理周辺ちょっと緩い。
船:詳細
パーツ:登録

未着手:

船:編集
パーツ:編集
ユーザ:編集

手が遅いのう。phpでやらなかっただけに、アプリの作り以前のところですっころぶケースが多々。
実装が汚くなるのは覚悟の上で、とりあえず動くものを作ってしまう方針にする。

Genshiで改行をbrタグにする

ぱっと見ではみつからない気がする。
改行を<br />タグに変換してくれる何か(aka nl2br)。
改行コミの文字列データをGenshiにそのまま渡すと、なんもしてくれないのね。で、じゃー父さんbrタグに変換しちゃうぞーっていうとこれもダメ。エスケープされちゃってどうしようもないの。

で、悩んで悩んであげくいつものところに聞きにいきますと、

<py:for each="line in message.split('\n')">
${line}<br />
</py:for>

なるほど。美しい。改行でばらして回してbrを加えるのねん。

さっくり答えでました。そうそうこういう状態をどうにかしたいってことだったのよ。
piquadratたんありがとー。

愚痴

船の部品一個表現するのに45項目ってなにごとなのー
うち4項目はid,論理削除フラグ、作成日、更新日ではあるわけですが
横に長いテーブルは嫌いだわ
JOINする方式の拡張はもっと嫌ですが

業務連絡

ラジオお休み!
相方さん体調不良につきごはんとか作ってます。
ごめんねーー
#18歳になりました^^

SQLAlchemy リレーションを調べる

船-建造港とかがone-to-manyになるはずなので調べてます。

http://wiki.liris.org/article/python_intro/python03#TOC-4

リレーションってとこ。
ほらーやっぱり中間テーブルいるんだよー
one-to-manyなんだこのやろうって思ってたけどそうじゃないかー
あれかね、createなんちゃらでテーブル作らせてる向きには関係ないってことなのかもね
作らせたよ一度はー
でもなんだか定義の中身がだいぶアレで、じゃあ一回作ったテーブルをloadしてTableオブジェクトダンプしてみればよくねえ?ってやったらさ
ぜんぜん違うっていうか超複雑なの。これを手で書けとか言われても無理。

あたくし古い?タイプでデータありきなんです。アプリにあわせたデータ構造って何じゃソレなので、適当に定義したPylons側のClassあわせのテーブルって気持ち悪い。逆なのか?アプリにあわせた構造であるべきなのか?ちゃんと(双方から見て等価な)定義できればいいんだろうけど、無駄にめんどくさすぎる。