0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

django_registrationを3.4から5.1.0にアップグレードしたらエラーになった

Last updated at Posted at 2024-11-26

はじめに

Djangoを利用しているアプリがあり外部に公開していることもあって、定期的にライブラリをバージョンアップしてリグレッションテストを行っています。

Dockerコンテナとして運用しているのですが、Djangoに限らずORMの機構は初期化時や節目で手動での作業を必要とすることがあるため、コンテナ環境との相性は必ずしも良くない部分があるなと感じています。

さてdjango_registratrion@pypIのバージョンがいきなり、3.4から5.1.0に数字が大きく変化したので何か変更があったのかなと思ったのですが、適用してみるとtype object 'AuthUser' has no attribute 'EMAIL_FIELD'というエラーが表示されるようになりました。

自前のAUTH_USER_MODELが原因で引き起したエラーではあったのですが、顛末をまとめておきます。

そして2段階有効化フロー(two-step activcation workflow)がGETで完結していたものがさらにPOSTで確定する方法へ変更になったため、formタグを含むファイルを1つ追加する必要もありました。

環境

  • Ubuntu 24.04.1 (amd64版)
  • Python 3.12.3
  • django 4.2.16
  • django_registration 5.1.0

メールによる確認プロセスを利用しているので、myapp/urls.pyには次のような記述をしています。

    path('accounts/', include('django_registration.backends.activation.urls')),
    path('accounts/', include('django.contrib.auth.urls')),

エラーメッセージ

単純にdjango_registrationのバージョンを上げると、DeploymentモードのWebブラウザには次のようなエラーメッセージが表示されました。

AttributeError at /webapp/accounts/register/

type object 'AuthUser' has no attribute 'EMAIL_FIELD'

Request Method: 	GET
Request URL: 	http://127.0.0.1:8000/webapp/accounts/register/
Django Version: 	4.2.16
Exception Type: 	AttributeError
Exception Value: 	

type object 'AuthUser' has no attribute 'EMAIL_FIELD'
Exception Location: 	.../venv/myapp/lib/python3.12/site-packages/django_registration/forms.py, line 259, in __init__
Raised during: 	django_registration.backends.activation.views.RegistrationView

エラーを出しているdjango_registration/forms.pyの259行目前後は次のようになっています。

259行前後
from django.contrib.auth import get_user_model
...
class RegistrationForm(SetPasswordMixin, BaseRegistrationForm):
    ...
    class Meta(BaseRegistrationForm.Meta):
        fields = (
            UserModel.REQUIRED_FIELDS
            if UserModel.USERNAME_FIELD in UserModel.REQUIRED_FIELDS
            else [UserModel.USERNAME_FIELD] + UserModel.REQUIRED_FIELDS
        )
        field_classes = {UserModel.USERNAME_FIELD: auth_forms.UsernameField}

    def __init__(self, *args, **kwargs):
        ...
        self.fields[UserModel.EMAIL_FIELD].validators = [  ## ← 259行目
            validators.HTML5EmailValidator(),
            validators.validate_confusables_email,
        ]
        self.fields[UserModel.EMAIL_FIELD].required = True
        ...

myapp/settings.pyの中で指定しているAUTH_USER_MODELで指定しているユーザーは次のようになっています。

自前のUserModelの定義
class AuthUser(AbstractBaseUser, PermissionsMixin):
    ...
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['family_name','first_name', 'gender', 'phone']
    ...

Django 4.2.16のdjango/contrib/auth/models.pyを調べると次のようになっています。

django/contrib/auth/models.pyからの抜粋
class AbstractUser(AbstractBaseUser, PermissionsMixin):
    ...
    EMAIL_FIELD = "email"
    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email"]
    ...

自前のクラスとシステムのデフォルト設定との間の差分がエラーを引き起したことが分かりました。

この差分を適用して、この部分については解決しました。

activation_form.htmlの追加

これまでは2段階認証の場合にはメールアドレスに記載されたURLをクリックするだけで完了しましたが、5.1.0からPOSTで情報を送信するためのページを追加する必要があります。

変更箇所
diff --git a/templates/django_registration/activation_email_body.txt b/templates/django_registration/activation_email_body.txt
index cc2eeca..4ae162c 100644
--- a/templates/django_registration/activation_email_body.txt
+++ b/templates/django_registration/activation_email_body.txt
@@ -1,6 +1,6 @@
 {% load i18n %}
 {% trans "Activate account at" %} {{ settings.DEFAULT_SMTP_FROM_HOST }}:
 
-https://{{ settings.DEFAULT_SMTP_FROM_HOST }}{% url 'django_registration_activate' activation_key %}
+https://{{ settings.DEFAULT_SMTP_FROM_HOST }}{% url 'django_registration_activate' %}?activation_key={{ activation_key }}
 
 {% blocktrans %}Link is valid for {{ expiration_days }} days.{% endblocktrans %}
diff --git a/templates/django_registration/activation_form.html b/templates/django_registration/activation_form.html
new file mode 100644
index 0000000..be877a4
--- /dev/null
+++ b/templates/django_registration/activation_form.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+<h1>Activation Forms</h1>
+<p>
+  <form action="." method="post">
+    {% csrf_token %}
+    <input type="hidden" name="activation_key" value="{{ form.activation_key.value }}">
+    <input class="my-3 btn btn-primary form-group" type="submit" value="Activate" />
+  </form>
+</p>
+{% endblock %}

urls.pyなどは変更しなくてもdjango_registration/activation_form.htmlがデフォルトのテンプレート名なので、ファイルを配置するだけで自動的に表示されます。

Quick Start Guideの中では新しく追加されたことが、それほど強調もされずにさらっと書かれていました。

ドキュメントでは{{ form }}を表示すれば良いように読めたのですが、textフィールドに表示させてしまうのは見た目がよくないので、hiddenの中で値だけを利用しています。

さいごに

自前のAuthUserクラスをAbstractUserから派生させなかったのが敗因ではあるのですが、それはしょうがないので、自前のクラスにEMAIL_FIELD = 'email'を追加して解決しました。

差分は次のようになっています。

最終的に解決したコードの差分
diff --git a/myapp/authenticate/models.py b/myapp/authenticate/models.py
index 2af7ffa..e291e69 100644
--- a/myapp/authenticate/models.py
+++ b/myapp/authenticate/models.py
@@ -61,6 +61,7 @@ class AuthUser(AbstractBaseUser, PermissionsMixin):
     is_staff = models.BooleanField(default=False)
     is_admin = models.BooleanField(default=False)
                        
+    EMAIL_FIELD = 'email'
     USERNAME_FIELD = 'email'
     REQUIRED_FIELDS = ['family_name','first_name', 'gender', 'phone']

解決してみれば1行の修正と、テンプレートファイルの追加といった簡単な作業なのですが、理解するまで少し時間を取られました。

以上

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?