今回のお題
今回はdjangoのユーザー認証で用いられているcommitという引数について調べてみました。
この引数は必要に応じて初期値を切り替える必要があるのですが、そもそもこの変数が何をしているのか、そしてなぜ初期値を切り替えないといけないのかを自分なりに調べたのでこの場をお借りしてまとめておこうと思います。
事の起こり
事の起こりは前回の記事でCustomUserを実装している時のことでした。
CustomUserにフィールドを追加した場合、デフォルトのsave_userメソッドでは対応できないのでadapterを上書きする必要がある。
ふむふむ。
adapterは以下のように記述する。
from allauth.account.adapter import DefaultAccountAdapter
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit):
user = super().save_user(request, user, form, commit=False)
user.age = form.cleaned_data.get("age") # "age"が追加したフィールド
user.save()
なるほど。
引数のcommitはfalseにすること
そしてここで躓くわけですね。
commitってなんだ?と。
というわけで、DefaultAccountAdapterを除いてきました。
DefaultAccountAdapterの中身
class DefaultAccountAdapter(object):
# ...省略...
def save_user(self, request, user, form, commit=True):
"""
Saves a new `User` instance using information provided in the
signup form.
"""
from .utils import user_email, user_field, user_username
data = form.cleaned_data
first_name = data.get("first_name")
last_name = data.get("last_name")
email = data.get("email")
username = data.get("username")
user_email(user, email)
user_username(user, username)
if first_name:
user_field(user, "first_name", first_name)
if last_name:
user_field(user, "last_name", last_name)
if "password1" in data:
user.set_password(data["password1"])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
# Ability not to commit makes it easier to derive from
# this adapter by adding
user.save()
return user
基本的にcleaned_dataの中身をuserインスタンスに入れていますね。
その作業が終わった後に書かれている以下の部分に注目してください。
if commit:
# Ability not to commit makes it easier to derive from
# this adapter by adding
user.save()
commit == true
であるかどうかでユーザーインスタンスを保存するかどうかを分岐させています。
つまりsave_user
メソッドとは
- allauthにあらかじめ用意されている属性の値をuserインスタンスにセットする
- commit == trueであれば保存を行う
というものになります。
そして今回adapterでこれを上書きして作ったsave_user
メソッドは
- allauthにあらかじめ用意されている属性の値をuserインスタンスにセットする
- commit == trueであれば保存を行う
- CustomUserで追加したフィール値をセットする
- commitの値によらず保存を行う
という内容になります。
つまり、commit==true
だと2回保存が入るわけですね。
この2回というのが曲者で、特に1回目はまだ追加したフィールド(今回で言えば"age")の値をセットしていないため、バリデーションの状況によってはエラーになります。
こういった状況を回避するために、commit=false
にすることが推奨されているということですね。
from allauth.account.adapter import DefaultAccountAdapter
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit):
user = super().save_user(request, user, form, commit=False) # ここでは"age"の値をセットしていないため、保存まで進むとエラーになる(ageがnull=falseの場合)。
user.age = form.cleaned_data.get("age") # "age"の値をセット
user.save() # "age"の値がセット済みなら保存して大丈夫
終わりに
以上で引数commitのお話を終わります。
今後もわからないことがあったときにはなるべく参照元のコードを見る癖をつけていきたいですね。