1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

djangoのsave_userメソッドの引数commitについて調べてみた

Posted at

今回のお題

今回はdjangoのユーザー認証で用いられているcommitという引数について調べてみました。

この引数は必要に応じて初期値を切り替える必要があるのですが、そもそもこの変数が何をしているのか、そしてなぜ初期値を切り替えないといけないのかを自分なりに調べたのでこの場をお借りしてまとめておこうと思います。

事の起こり

事の起こりは前回の記事でCustomUserを実装している時のことでした。

CustomUserにフィールドを追加した場合、デフォルトのsave_userメソッドでは対応できないのでadapterを上書きする必要がある。

ふむふむ。

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の中身

allauth.account.adapter.py
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にすることが推奨されているということですね。

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) # ここでは"age"の値をセットしていないため、保存まで進むとエラーになる(ageがnull=falseの場合)。
    user.age = form.cleaned_data.get("age") # "age"の値をセット
    user.save() # "age"の値がセット済みなら保存して大丈夫

終わりに

以上で引数commitのお話を終わります。

今後もわからないことがあったときにはなるべく参照元のコードを見る癖をつけていきたいですね。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?