python 変数の型を確認する

isinstance関数を使う。

isinstance(変数, 型) の戻り値がTrueなら型があっている、Falseなら型が違うということになる。

主な型一覧
型名 説明
bool 真偽値型
int 整数型
float 小数型
complex 複素数型
list list型
tuple タプル型
range range型
str 文字列型
bytes バイト型
set 集合型
frozenset イミュータブルな集合型
dict 辞書型
使用例

 

Django 記録時刻を10分単位や15分単位で丸める

出退勤記録をつける際に、例えば8:12に出社したら、出勤時刻は8:15として、15分刻みで実働時間や時給計算するというのはよくあります。

逆に退勤時は、15:12に退勤したら、15:00と記録したいですね。

特に丸めずにそのまま記録して、集計時に丸めてもよいのですが、djangoだと集計時にいろいろ面倒なので、最初から丸めて記録しておくと良いかと思います。

主にformの機能を使ってDBに記録するので、記録する際にviews.pyのform_validをいじります。

以下は出勤時の例です。

 

退勤用のフォームを作る場合は、form_validで以下のようにします。

 

なお実際には、出勤した直後に(15分もたたずに)すぐ退勤した場合、上記の例だと出勤時刻と退勤時刻が逆転してしまいますので、もし丸めた退勤時刻が出勤時刻より早くなってしまったら出勤時刻と同じにするなどのイレギュラー処理が必要となります。

参考サイト)

https://www.it-swarm.dev/ja/python/%E6%97%A5%E6%99%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E5%88%86%E3%82%92%E4%B8%B8%E3%82%81%E3%82%8B%E6%96%B9%E6%B3%95python/969167474/

Django FilterViewを使って作成した検索フォームのラベル名変更

FilterView(filters.py)を使うと、検索フォームに表示される検索項目の名前(ラベル名)は、モデルで定義した名前になります。

例えば、モデルに、

と書いてあれば、verbose_nameの値(出勤日時)になります。verbose_nameが無い場合はモデル名(attendance_at)になります。

このラベル名を変更する場合は、filters.pyに以下のように追記します。

 

参考サイト)

 

Django Migrate時に「The row in table ‘app_fuga’ with primary key ‘*’ has an invalid foreign key: app_fuga.hoge_id contains a value ‘*’ that does not have a corresponding value in app_hoge.id.」の解消

エラーの内容

makemigrationsまでは通るのですが、その後migrateしようとすると、以下のようなエラーに遭遇するときがあります。

このエラーは、開発環境でモデルを追加したり変更したりmigrateしたりを繰り返していると出ます。

直訳すると、

となり、つまり「子テーブル(app_fuga)の中に、親テーブル(app_hoge)の外部キーらしきIDの値があるけど、そんなIDを持つレコードはapp_hogeにはねーよ!」ということらしいです。

ここでは、app_hoge が親テーブル、app_fuga が子テーブルなので、app_fuga の hoge_id というカラムが、親テーブルのIDを参照する外部キーです。

このカラムに例えば「1」という値が入っていたとすると、app_hogeの中に、IDが「1」のレコードは存在しないと言っていることになります。

DBの中身を見てみる

一応確認します。

?結果がありません。どゆこと?

つまり、hoge_idが「1」となっているレコードが無いのにもかかわらず、「あるし!」と言われているので、訳が分かりません。

結論から言うと、おそらく「app/migrations」に、過去のモデル修正の繰り返しで作成されたマイグレーションファイル(000*_auto_YYYYMMDD_TT.pyとか)が蓄積されている状況で、これらが悪さをしているようです。

新しいマイグレーションファイルは、さらに過去のマイグレーションファイルを参照し、変更の履歴を見てコンフリクトを解消しようとします。

つまりその流れの中で、過去のマイグレーションで行ってきた操作ができなかった時、例えば前提とされているテーブル構造が実際には変わっていたり、あるはずのレコードが削除されてたりすると、途中経過でエラーとなる場合があるみたいです。

今回はおそらく、モデルに新しくデフォルト値などを張った都合で整合性が取れなくなったのかと思います。

解決方法

開発環境なので、手順なんていちいち記録していないことも多いでしょうから、どこで狂ったのかなんてわかりません。

そういう時はマイグレーションファイルを削除するのが手っ取り早いのですが、この場合、全てのアプリのマイグレーションファイルを削除してはいけません。全て削除すると、また同じ結果になります。

おそらくmakemigrationsの順番が関係しているのでしょう。

ポイントは、エラーが出ているテーブルのモデルを定義しているマイグレーションファイルをだけを削除します。

今回は「app_fuga」でエラーが出ているので、「app/migrations」の中の、__init__.py以外を全て削除します。

こうすることで、再度makemigrationsしたときに、どうやら変更履歴を無視して、現在のモデル構造だけを反映したマイグレーションファイルを作ってくれるようです。

無事にmigrateが通りました。

Django テンプレートでDurationフィールドの値の割り算と丸め

簡単な掛け算割り算で出るような値は、いちいちDBに結果を保持するのは冗長です。

views.pyで計算してレンダリングしても良いですが、テンプレートでサクッとやれちゃえば楽です。

しかし、DurationフィールドはTimeフィールドやDatetimeフィールドと違い、データ型が実際は浮動小数点型なので、日付け操作用の組み込みフィルタで小数点以下切り捨てなどがきません。

それになぜか、{{ value|flortformat:0 }} も効きません。

仕方が無いのでフィルタを自作します。

フィルタ自作の方法自体は、こちらが詳しいです。

ここでは、上記サイトの中の「utils.py」の内容だけ説明します。(別に名前は何でもよいです)

 

テンプレートで以下のように書けば、Durationフィールドの割り算と丸めが、できるようになります。

 

参考サイト)

Django テンプレートでchoicesの名前の方を出す

{{ ’クラス名’.get_’モデル名’_display }} で取得できます。

models.py

template

 

 

Django データ保存後に遷移先ページで簡単なメッセージを出す

messagesクラスを使い、遷移先またはbase.htmlでメッセージを表示させます。

views.py

 

base.html

 

 

Django CreateViewやUpdateView利用時、任意のデータを裏側で保存する

CreateViewやUpdateViewを使うけど、ボタンを押すだけのパターン(保存する値がすでに決まっているパターン)にしたい時とか、一部だけ選択させてそれ以外は決まった値を保存したい時。

たとえば、出退勤を記録するための出勤ボタン/退勤ボタン。

そういうケースでは、そもそもクラスビューを使う必要もなさそうだか、改修時に固定値を1個だけ追加したいとかあるかもしれない。

 

form_validメソッドをオーバーライドします。

views.py

forms.py

hoge_form.html

 

参考サイト)

Django UpdateViewなどで、データを作成したユーザ以外更新できないようにする

要するに、手書きでのpkのURLを書き換えられた時の対策。get_querysetメソッドをオーバーライドする。

views.py

 

参考サイト)

Django 特定のフィールドでGROUP BYして集計する

※model, urlsの定義は割愛します。

DBオブジェクト操作時、values(‘カラム名’).annotate(…)の順で書くと、カラム名でGROUP BYされた動きになります。

またvaluesするときでもannotateするときでも、’ラベル=カラム名’とすると、別名(ラベル名)で受け取れます。

 

select_related を加えるとで外部キー参照している親テーブルをJOINできます。

 

親テーブルをJOINした時、values に ‘キー_親テーブルのカラム名’を指定すると、親テーブルの値を取得できます。

 

具体的なview.py、templateの内容は以下のようになります。

views.py

 

template.html

別名を付けたものは辞書型で取り出せます。別名付けなかった場合は、Sumしたものであれば ‘カラム名__sum’ の形で取り出せます。