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’ の形で取り出せます。

 

 

 

Django 前回のフォーム入力値を保持・出力する

入力値データをsessionに保存して行いますが、多少複雑なので、クラスビューではなく関数ビューを使うのが良いかと思います。

  • views.py

 

テンプレートの、テンプレートタグ{{ form }}に、フォームと値がセットされます。

以上、備忘録。

djangoのCreateViewでフォームの初期値を設定する方法

get_initial関数をオーバーライドします。

 

以上、備忘録。

djangoのフォームを扱うビュー3種類

フォーム入力画面を作成するには、

  • CreateViewクラスをオーバーライドする方法
  • FormViewクラスをオーバーライドする方法
  • 関数ビューで自分で書く方法

の主に3種類があります。

以下は、まったく同じ動きをするフォームを、3種類の書き方で書いています。(フォームのクラスはforms.pyで別途定義してあるものとします。)

  • CreateViewクラスをオーバーライドする方法

CreateViewクラスは、FormViewクラスをオーバーライドした、フォームからのデータ登録に特化したクラスです。

単純に入力→保存を行うような機能がめちゃくちゃ簡単にできます。テンプレートは指定がなければ、アプリ名_form.htmlがデフォルトテンプレートとなります。

 

  • FormViewクラスをオーバーライドする方法

CreateViewは値が正しければDBに自動的に登録してくれるクラスですが、FormViewはそのままでは登録までは勝手にしてくれません。

送られた値が正しかった時に他に何かしたい、といった時に使うのがいいのではないかと思います。

 

  • 関数ビューで自分で書く方法

FormViewクラスに比べ、テンプレートへのレンダリングなども含みます。

sessionやcookieに何か保存したり呼び出したりなど、仕様を細かく指定した時には自分で関数ビューを書いた方が良いです。

 

以上、備忘録。