Django:this querydict instance is immutable request.data エラーの原因と対策

Django:this querydict instance is immutable request.data エラーの原因と対策

Django:this querydict instance is immutable request.data エラーの原因と対策

これが出てくるのは、POSTされた値を変更しようとしたときです。

request.POSTはQueryDictというオブジェクトで、書き換え不可になっている

だからエラーが発生するんですね。

querydict instance is immutableエラーの原因

書換できないrequest.POSTを書換ようとしてる事

これが原因です。となれば採用する手段は2つです。

  1. 書き換えなくてもよい値をPOSTする
  2. POSTされた値を別の変数に格納して保存工程に渡す

項目1が楽そうですが、TemplateからFormでPOSTした値が撥ねられることは多々あります。
少し実例を見てみましょう。

The Model名 could not be changed because the data didn’t validate.

これは私がCRUD作成の一番最初に苦しめられたエラーです。
エラー発生原は様々ですが、私の場合は下の組合せが原因でした。

  1. Models.py 内で【models.DateField(null=True,default=None)】としたフィールドに対し何も入れずにPOSTした。
  2. Models.py 内で【models.IntegerField(null=True,default=None)】としたフィールドに対し何も入れずにPOSTした。
  3. POSTしたリレーションカラム名がModel内の名称と違っていた

POSTされた値が文字列ではない時やリレーションを張っているカラムが存在する時、このエラーの発生率は高まります。

エラーの原因と対策は別ページにまとめました。

POSTされた値を上書きしようとしてもできない

タイトルの内容に戻ります。
【querydict instance is immutable】エラーはPOSTされた値を変更しようとしたときに発生します。

例えば上のような場合です。POSTされた値が[ ’’ ] だった時にNoneに置き換えようとしているのですが、
QueryDictであるrequest.POSTは書換え不可
なので【querydict instance is immutable】エラーが発生してしまいます。

POSTされた値を地道に変数に入れてその値を渡してあげる方法であれば【querydict instance is immutable】のエラーは発生しません。例えばこんな実装です。

■Views.py内

カラム数が少なければ『アリ』かもしれませんが、これだけのレコード数になるとさすがに悠長すぎです。

QueryDictのcopy()メソッドを使ってみる

エラーにあるようにTemplateからPOSTされた値は QueryDictというオブジェクトになっています。
このQueryDictにはcopy()というメソッドがあり、これを使うとPOSTされた値を書き換え可能な姿に複製してくれます。

〇 : 値を変数に格納する ( 例:corporates = request.POST[‘corporates’] )
〇 : request.POSTのコピーを作る ( 例:cp = request.POST.copy() )
× : 変数にrequest.POSTを格納する ( 例:cp = request.POST )

同じ【コピーする】の様でも上記例の3番目はNGです。理由はQueryDictオブジェクトである事も含めてrequest.POSTをそのまま変数に格納するから。なので、これだとどんなに頑張っても同じエラーが発生してしまいます。

1番目はサンプルスクリプトで活用した方法です。泥臭い方法ですがココをベースにして簡略化していきます。
その時に使う手段がcopy()メソッドです。

コードを短くするのために見るべき場所

長いコードを短くするためには3つの箇所に目を向けます。

  1. 同じ(似たような)コードが繰り返し発生していないか
  2. コードが長くなる原因のデータはどのような形になっているか(今回だったらrequest.POST)
  3. 簡略化できる命令セットはないか

同じ(似たような)コードが繰り返しが発生していないか

上のコード例にはバッチリありますね。

[ ” ]と[ ‘None’ ]で渡された値をNone(Null)に変更している工程が毎回繰り返し発生しています。
「forループで対応しよう」と思うのですが、この時障害になるのが  QueryDictであるrequest.POSTは書換え不可 というルールです。

コードが長くなる原因のデータはどのような形になっているか

request.POSTは次のような姿で渡されてきます。

辞書型といわれる形です。これはKeyを取り出したい場合には注意が必要です。

簡略化できる命令セットはないか

request.POST は for in ループで値を取り出せるが【書き換え】ができないため毎回変数を用意して格納している

辞書型でセットされているrequest.POSTも for in ループに入れれば値を取り出すことができます。

なので、値の上書きも可能…と思うのですがQueryDictの規制で書き換え出来ずにエラーが発生します。

ここで活躍するのがQueryDict.copy()です。

copy()で複製した辞書はQueryDictというカテゴライズは継承しますが上書き可能になります。これでForm/ModelFormに投げる『入力のないカラムをNoneに置き換えた値』を作ることができます。

置換なのにReplace関数は使えない?

Replaceできないかコード書いて実験してみます。

これを走らせると下のようなエラーが発生します。

‘QueryDict’ object has no attribute ‘replace’
訳:QueryDict’ オブジェクトには ‘replace’ 属性がありません。

Copy()で変更できるようにしていてもQueryDictである事に変わりはないのでreplaceは使えないと。
QueryDictで使えるコマンドについてはこちらでご確認を。

最終的に出来上がった形

POSTされた空文字や ’None’ を置換するシーンは頻繁に起こり得ます。
なので、このパーツを外部ライブラリ独自関数として保存しViewから呼び出して利用する形に実装します。

■Project/project/Libraries/library.py

■Project/project/manager/views.py

■Project/project/manager/forms.py

■Project/project/manager/models.py

■Project/project/manager/templates/CRUD/company_edit.html

html以外はだいぶ軽くなりました。

まとめ

Templateがまだボリューム満載ですが、forms.pyも極めていけばこれくらいのレスポンシブを{{ form }}で呼び出せるようになるのかな。