Help us understand the problem. What is going on with this article?

Djangoでdjango-allauthとCustomUserを使った認証機能を作成

はじめに

django-allauthとCustomUserを使った認証をしたかったのですが、日本語記事が少なく、いい英語記事を探すのが大変だったので、sampleとハマったところの解説、プログラムを貼っておきます。

目次

1.sample
2. ハマったところ
3. ファイル構成
4. url.py
5. settings.py
6. models.py
7. forms.py
8. adapter.py
9. templates
10.参考

1.sample

samplecode(Github)

実際に動かしてみるとこうなります。1573995017.jpg

2.ハマったところ

  • ハマったところ
    Userとsignupformを拡張して、年齢と体重の項目を追加したが、CustomUserで追加したage,weightに値が入らない。
  • 理由
    django-allauthでは、Customuserでuserを、CustomSignupformでsignupformを拡張してもsignupformから入力された値はユーザーフィールドにdefaultで保存されない。
  • 解決法
    adapter.pyを作り、AccountAdapterクラスsave_user関数を記述すると、拡張したフィールドに入力された値がCustomUserに保存できるようになる。

3.ファイル構成

testsite/
 ├templates/
 │    ├accounts/
 │    │   └registration/
 │    │       └login.html/
 │    │       └signup.html/
 │   ├testapp/
 │   │   └home.html
 │   └base.html
 ├testapp/
 │   ├__pycache__
 │   ├migrations
 │   ├adapter.py
 │   ├admin.py
 │   ├apps.py
 │   ├forms.py
 │   ├models.py
 │   ├tests.py
 │   ├urls.py
 │   └views.py
 ├testsite/
 │   ├__pycache__
 │   ├__init__
 │   ├settings.py
 │   ├urls.py
 │   └wsgi.py
 └manage.py

4.url.py

url.py
from django.contrib import admin
from django.urls import include, path
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('testapp.urls')),
    path('accounts/', include('allauth.urls')),
    path('accounts/login/', TemplateView.as_view(template_name = 'login.html'), name='login'),
    path('accounts/logout/', TemplateView.as_view(template_name = 'logout.html'), name='logout'),
    path('accounts/signup/', TemplateView.as_view(template_name = 'signup.html'), name='signup'),
]

5.settings.py

customuser,signupform,adapterの設定は必須。

settings.py
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = ''

DEBUG = True

ALLOWED_HOSTS = ['*']

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'testapp.apps.TestappConfig',#testapp
    'django.contrib.sites', #for djnago-allauth
    'allauth', #for djnago-allauth
    'allauth.account', #for djnago-allauth
    'allauth.socialaccount',#for djnago-allauth
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'testsite.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'testsite.wsgi.application'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'

#追加
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
)

# 認証方式を「メールアドレスとパスワード」に変更
ACCOUNT_AUTHENTICATION_METHOD = 'email'
# ユーザー名を使用する
ACCOUNT_USERNAME_REQUIRED = True

# ユーザー登録確認メールは送信しない
ACCOUNT_EMAIL_VERIFICATION = 'none'
# メールアドレスを必須項目にする
ACCOUNT_EMAIL_REQUIRED = True

#ユーザーモデルの拡張(customuser)
AUTH_USER_MODEL = 'testapp.CustomUser'

SITE_ID = 1 #django-allauthがsitesフレームワークを使っているため

LOGIN_REDIRECT_URL = 'home'
ACCOUNT_LOGOUT_REDIRECT_URL = '/accounts/login/'
ACCOUNT_LOGOUT_ON_GET = True

#signupformを指定
ACCOUNT_FORMS = {
    'signup' : 'testapp.forms.CustomSignupForm',
}
#signupformからの情報をcustomusermodelに保存するのに必要
ACCOUNT_ADAPTER = 'testapp.adapter.AccountAdapter'
#passwordの入力を一回に
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False

6.models.py

models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.
class CustomUser(AbstractUser):
    """拡張ユーザーモデル"""
    class Meta(AbstractUser.Meta):
        db_table = 'custom_user'

    age = models.IntegerField('年齢', blank=True, default=0)
    weight = models.IntegerField('体重',blank=True, default=0)

7.forms.py

forms.py
from allauth.account.forms import SignupForm
from django import forms
from .models import CustomUser
from allauth.account.adapter import DefaultAccountAdapter

class CustomSignupForm(SignupForm):
    age = forms.IntegerField()
    weight = forms.IntegerField()

    class Meta:
        model = CustomUser

    def signup(self, request,user):
        user.age = self.cleaned_data['age']
        user.weight = self.cleaned_data['weight']
        user.save()
        return user

8.adapter.py

adapter.py
from allauth.account.adapter import DefaultAccountAdapter

class AccountAdapter(DefaultAccountAdapter):

    def save_user(self, request, user, form, commit=True):
        """
        This is called when saving user via allauth registration.
        We override this to set additional data on user object.
        """
        # Do not persist the user yet so we pass commit=False
        # (last argument)
        user = super(AccountAdapter, self).save_user(request, user, form, commit=False)
        user.age = form.cleaned_data.get('age')
        user.weight = form.cleaned_data.get('weight')
        user.save()

9.templates

base.html
<!doctype html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{% block title %}{% endblock %}</title>

    {# --- css --- #}
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <style type="text/css">
        body {
            padding-top: 5rem;
        }
    </style>
    {% block extra_css %}{% endblock %}
</head>

<body>
    <main class="container">
        {% block content %}{% endblock %}
    </main>

    {# --- js --- #}
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
        integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous">
    </script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
        integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous">
    </script>
    {% block extra_js %}{% endblock %}
</body>

</html>
account/registration/login.html
<!doctype html>
{% extends "base.html" %}

{% block title %}ログイン{% endblock %}

{% block content %}
<h2>ログイン</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">ログイン</button>
</form>
{% endblock %}
account/registration/signup.html
<!doctype html>
{% extends "base.html" %}

{% block title %}ユーザー登録{% endblock %}

{% block content %}
<h2>ユーザー登録</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <p><a href="{% url 'login' %}"><button type="submit">ユーザー登録</button></a></p>
</form>
{% endblock %}
testapp/home.html
<!doctype html>
{% extends "base.html" %}

{% block title %}ホーム{% endblock %}

{% block content %}
<h2>ホーム</h2>
{% if user.is_authenticated %}
ようこそ {{ user.get_username }} さん
あなたの年齢は{{ user.age }}です
あなたの体重は{{ user.weight }}kgです
<p><a href="{% url 'logout' %}">ログアウト</a></p>
{% else %}
<p><a href="{% url 'login' %}">ログイン</a></p>
<p><a href="{% url 'signup' %}">signup</a></p>
{% endif %}
{% endblock %}

10.参考

横瀬 明仁「現場で使えるdjangoの教科書」
django-allauthの公式1
django-allauthの公式2
the-complete-django-allauth-guide
extending-and-customizing-django-allauth

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away