Django “sqlite Invalid connector for timedelta: /”を何とかする

クエリセットで、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で割ってやります。

これで、テンプレート側では、以下で取得できます。

 

 

 

 

Django モデルフォーム利用時に独自のバリデーションを追加

例として以下のようなモデルがあったとします。

この場合、モデルフォームを使ったフォームでは、値が空欄の時はエラーとなりますが、0の場合はOKとなります。

0より大きい値でなければ受け付けない場合は、モデルにバリデーションを設定します。

 

Django 1対多(one2many)でリレーションされた子テーブルの集計値を取得する

外部キーによって子テーブルが親テーブルのIDを参照している場合に、親テーブルのオブジェクトから、その子テーブルの任意カラムの合計値を取得するサンプルです。

以下のようなモデルを想定します。

 

なお、子テーブルのデータを単純に表示したいだけであれば、テンプレート変数「子テーブルのモデル名_set」だけで事足ります。

例えば、views.pyで、

としていれば、テンプレートでは、

のようにすることで可能です。

このとき、子テーブルの一部のカラムの値を合計(この場合は”defective_weight”)して取得したい場合は、”子テーブルのモデル名__カラム名”で子テーブルにアクセスできます。

テンプレートは以下のようになります。

 

ただし、複数の子テーブルをリレーションした場合は、計算が合わなくなります。例えば以下のようなモデルです。

先ほどと同じやり方でクエリセットを構成すると、

となりますが、これだと計算が合わなくなります。なぜなら、テーブル結合の方法がLEFT OUTER JOINとなるからです。

不良カテゴリ1と不良カテゴリ2の対象レコード数が異なっていた場合、結合後のテーブルの行数が、レコード数の多い方のテーブルに合わせた行数となります。

したがって、不良カテゴリ1の行数が4, 不良カテゴリ2の行数が2だった場合、不良カテゴリ2をSumしたときに、4行分を合計してしまいます。

これを避けるにはINNER JOINするべきなのですが、ORMではなるべく生のSQLは触りたくありません。

これを手っ取り早く解決するにはサブクエリを使い、子テーブルを個別に参照して計算し、結合します。ただし多少重くなります。

 

いずれにせよ、このようにしておけば、filterで歩留りの大小順に集計したりもできますので、便利です。

 

参考サイト)

 

Django 1画面に種類の違う複数のformsetを共存させる

複数の単体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タグが勝手に生成されてしまうので、この場合は使わない方が得策です。

 

参考サイト)

Django 1画面に同じフォームを複数表示させる

formsetを使います。

ネット上では、forms.pyの中でフォームセットを作っているパターンの紹介が多いですが、今回はviews.pyの中でやります。

汎用性があるものは、forms.pyにまとめたり、クラス化したりするのが良いかと思います。

まず、forms.pyに普通のフォームを書きます。ModelFormを使います。

 

views.pyの中で、フォームセットを使いテンプレートにレンダリングします。処理が複雑になるので、クラスviewは使わず、関数viewで書きます。

保存時に一括でsave()がうまくいかない時は、transactionを使用し、フォームセットのPOST値をループで回してsave()するとできます。(本当はbulk_createを使いたかったのですがうまくいきませんでした。)

 

テンプレートは、ただフォームセットを埋め込むだけです。

 

参考サイト)