モデルフォームはとても便利ですが、すこし複雑な処理をするときはどこまでカスタムするかという問題も出てきます。
カスタム沼にはまり込んでしまうより、むしろモデルフォームを使わない方が楽な時があります。
以下は、チェックされた価格が設定されていない本に、まとめて同じ価格を設定するときの例です。
同じnameの複数パラメタは、request.POST.getlistで受け取ることができます。
参考サイト)
※管理上の都合により2015年11月以前の記事は削除いたしました
モデルフォームはとても便利ですが、すこし複雑な処理をするときはどこまでカスタムするかという問題も出てきます。
カスタム沼にはまり込んでしまうより、むしろモデルフォームを使わない方が楽な時があります。
以下は、チェックされた価格が設定されていない本に、まとめて同じ価格を設定するときの例です。
同じnameの複数パラメタは、request.POST.getlistで受け取ることができます。
参考サイト)
テーブルの一部のカラムの値をSumしていると、集計対象のレコードが存在しなかった場合、Sumの結果には通常Noneが返されます。
ここで処理が終わりであればよいのですが、その後そのSumした集計値を使っていろいろ計算を続ける場合、数値として認識されないため、その後の計算の結果もすべてNoneになってしまいます。
なので、もしSumした結果がNone(合計する行が存在しない場合も含め)であれば、0としておくと都合が良いことになります。
Coalesceを使います。
ポイントとしては、annotateが連続する場合、最後のannotateの中でCoalesceを使います。
annotateが連続しない場合、最初のSum時に、
とやってもOKです。
参考サイト)
PWAアプリ化した場合でもそうなんですが、スマホをロックしたり自動で消灯した際、SetTimeoutの処理は一旦停止されます。その後画面を復帰させると、SetTimeoutの処理が再度復活します。
つまり、簡易ストップウォッチ機能とか、何かしらの時間測定をJS側で行っていると、スマホがスリープしていた時間はカウントされない場合があります。
今回はこの対策として、SetTimeout処理の中でスリープによってズレた時間を自動補正します。
HTMLには以下のように書いておきます。
SetTimeoutの中で、最終チェック時の経過時間からズレが5秒以上になったら自動的にズレた分を経過時間に追加します。(jQuery利用)
今回は簡易的なストップウォッチなのでこのような方法でやりましたが、欠点としては、ズレ補正の度に、若干(数ミリ秒ずつ)のずれが発生する可能性があります。
正確にズレ補正を行う場合は、計測スタート時に、開始時刻をミリ秒で記録しておくのが一番確実でしょう。
クエリセットで、DurationFieldの値を、掛けたり割ったりしたものを取得したい時があります。たとえばチームの作業で1人当たり何分かっかったのか、とか、1mあたり何分くらいかかっているのか、とか。
しかし、以下のようなクエリセットを構築し、レンダリングしようとすると怒られます。
“sqlite Invalid connector for timedelta: /” と言われてまして、どうやらSQLiteではtimedelta形式の値(Durationフィールド)をそのまま除算できないようで、”/”が不正なコネクタとして認識されてしまいます。
DjangoのDuration型は、SQLite3ではbigint型となります。(Djangoのフィールド型とDBの型の組合せはこちらを参照)
MySQLなどでは大丈夫とのことですが、SQLiteではバグか何かしりませんが、同じintなのに、bigint型を直接計算できない模様。
いろいろ試した結果、Djangoで型を合わせればイケルというこうとがわかったので、今回はDurationを、割る方のintegerに合わせます。
クエリセット作成時に、Castしてoutput_fieldを指定してやればOKです。Durationをintegerに変換すると、100万分の1秒が整数1位で返るので、秒単位にするには1,000,000で割ってやります。
これで、テンプレート側では、以下で取得できます。
例として以下のようなモデルがあったとします。
この場合、モデルフォームを使ったフォームでは、値が空欄の時はエラーとなりますが、0の場合はOKとなります。
0より大きい値でなければ受け付けない場合は、モデルにバリデーションを設定します。
外部キーによって子テーブルが親テーブルのIDを参照している場合に、親テーブルのオブジェクトから、その子テーブルの任意カラムの合計値を取得するサンプルです。
以下のようなモデルを想定します。
なお、子テーブルのデータを単純に表示したいだけであれば、テンプレート変数「子テーブルのモデル名_set」だけで事足ります。
例えば、views.pyで、
としていれば、テンプレートでは、
のようにすることで可能です。
このとき、子テーブルの一部のカラムの値を合計(この場合は”defective_weight”)して取得したい場合は、”子テーブルのモデル名__カラム名”で子テーブルにアクセスできます。
テンプレートは以下のようになります。
ただし、複数の子テーブルをリレーションした場合は、計算が合わなくなります。例えば以下のようなモデルです。
先ほどと同じやり方でクエリセットを構成すると、
となりますが、これだと計算が合わなくなります。なぜなら、テーブル結合の方法がLEFT OUTER JOINとなるからです。
不良カテゴリ1と不良カテゴリ2の対象レコード数が異なっていた場合、結合後のテーブルの行数が、レコード数の多い方のテーブルに合わせた行数となります。
したがって、不良カテゴリ1の行数が4, 不良カテゴリ2の行数が2だった場合、不良カテゴリ2をSumしたときに、4行分を合計してしまいます。
これを避けるにはINNER JOINするべきなのですが、ORMではなるべく生のSQLは触りたくありません。
これを手っ取り早く解決するにはサブクエリを使い、子テーブルを個別に参照して計算し、結合します。ただし多少重くなります。
いずれにせよ、このようにしておけば、filterで歩留りの大小順に集計したりもできますので、便利です。
参考サイト)
複数の単体formを共存させるやり方はネット上にいろいろありますが、複数のformsetを共存させる情報がなかったので、書いてみます。
少し冗長ですが、今回もviews.pyの中でやります。汎用性があれば、forms.pyにまとめたり、クラス化したりするのが良いかと思います。
まず、formsetの基となる2種類のフォーム情報を、forms.pyに書きます。ModelFormを使います。(models.pyは割愛します。)
views.pyの中で、フォームセットを定義します。処理が複雑になるので、今回もクラスviewは使わず、関数viewで書きます。
ポイントは、保存時にフォームセット数を調整してあげることです。
なぜかというと、複数のフォームセットをテンプレートに出力すると”form-TOTAL_FORMS”というhiddenフィールドがセットの数だけ複数出力されるため、単純にPOSTで受け取っただけだと、いずれかが無効になってしまいます。
その結果、form-TOTAL_FORMSの数と実際にPOSTしたフォームの数が合わずに、バリデーションエラーとなります。(※すべてのフォームセットへの入力数が同じ数であれば、この心配はいりません。)
この対策として、POSTされたデータを一旦コピー&form-TOTAL_FORMSフィールドの値を上書きし、フォームセットオブジェクト生成時に上書きしたPOSTデータを引数として渡します。
テンプレートは、ただフォームセットを埋め込むだけです。なお、crispyを使っていると、formタグが勝手に生成されてしまうので、この場合は使わない方が得策です。
参考サイト)
formsetを使います。
ネット上では、forms.pyの中でフォームセットを作っているパターンの紹介が多いですが、今回はviews.pyの中でやります。
汎用性があるものは、forms.pyにまとめたり、クラス化したりするのが良いかと思います。
まず、forms.pyに普通のフォームを書きます。ModelFormを使います。
views.pyの中で、フォームセットを使いテンプレートにレンダリングします。処理が複雑になるので、クラスviewは使わず、関数viewで書きます。
保存時に一括でsave()がうまくいかない時は、transactionを使用し、フォームセットのPOST値をループで回してsave()するとできます。(本当はbulk_createを使いたかったのですがうまくいきませんでした。)
テンプレートは、ただフォームセットを埋め込むだけです。
参考サイト)
自前で管理画面を用意しない場合や、多言語対応を行っていない場合、Djangoの管理画面ではデフォルトでアプリ名がそのまま英字で表示されます。
具体的には、setting.py で利用するアプリを指定しますが、その指定した名前が表示されます。
この部分を変更する(日本語にする)場合は、そのアプリのapps.pyをDjangoに認識させる必要があります。
apps.pyでverbose_nameを指定します。
apps.pyを介してアプリを呼び出すようsettting.pyを変更します。
isinstance関数を使う。
isinstance(変数, 型) の戻り値がTrueなら型があっている、Falseなら型が違うということになる。
型名 | 説明 |
---|---|
bool | 真偽値型 |
int | 整数型 |
float | 小数型 |
complex | 複素数型 |
list | list型 |
tuple | タプル型 |
range | range型 |
str | 文字列型 |
bytes | バイト型 |
set | 集合型 |
frozenset | イミュータブルな集合型 |
dict | 辞書型 |