如何在DRF + django-rest-auth中使用自定义用户模型保存有关注册的额外字段

Rik*_*eek 3 django django-rest-framework django-allauth django-rest-auth

使用带有django-rest-auth的Django REST Framework(DRF),我创建了一个带有一个额外字段的自定义用户模型。我的目标是使用django-rest-auth注册端点在一个请求中注册一个新用户,从而发送所有数据以创建一个新用户,包括多余字段的数据。

我正在使用AbstractUser,因为它似乎是推荐给初学者的,在初学者中,更高级的开发人员可以使用AbstractBaseUser。这也是为什么以下SO答案对于我想要实现的目标而言过于复杂的原因:链接此处

我知道这个问题已经被问过多次了,但是答案并不完全是我想要的。对于像我这样的初学者来说,这是一件复杂的事情。

所以,我的问题是,任何人都可以解释如何实现我想要的吗?

我在用:

Django              2.1.4
django-allauth      0.38.0
django-rest-auth    0.9.3
djangorestframework 3.9.0
Run Code Online (Sandbox Code Playgroud)

这是到目前为止的代码:

使用本教程来获得此代码

settings.py:

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!gxred^*penrx*qlb=@p)p(vb!&6t78z4n!poz=zj+a0_9#sw1'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'rest_framework.authtoken',

    'rest_auth',

    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'rest_auth.registration',

    'users',
]

SITE_ID = 1

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 = 'DRF_custom_user.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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 = 'DRF_custom_user.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

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


# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators

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',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/

STATIC_URL = '/static/'

AUTH_USER_MODEL = 'users.CustomUser'

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Run Code Online (Sandbox Code Playgroud)

users.models.py:

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


class CustomUser(AbstractUser):
    preferred_locale = models.CharField(blank=True, null=True, max_length=2)
Run Code Online (Sandbox Code Playgroud)

users.admin.py:

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser
    list_display = ['email', 'preferred_locale']

admin.site.register(CustomUser, CustomUserAdmin)
Run Code Online (Sandbox Code Playgroud)

users.forms.py:

from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser


class CustomUserCreationForm(UserCreationForm):

    class Meta(UserCreationForm):
        model = CustomUser
        fields = ('email', )


class CustomUserChangeForm(UserChangeForm):

    class Meta:
        model = CustomUser
        fields = UserChangeForm.Meta.fields
Run Code Online (Sandbox Code Playgroud)

Rik*_*eek 11

我自己去寻找答案。花一些时间来研究源代码。我意识到此解决方案可能会缺少对添加到自定义用户模型中的额外字段的实际验证,但是稍后我会进行研究。

我在下面写的内容是在考虑潜在博客帖子的情况下写的。

我将假设您知道如何设置DRF项目并安装上述软件包。django-rest-auth文档明确说明了如何安装该软件包(https://django-rest-auth.readthedocs.io/en/latest/index.html),请确保还遵循以下步骤来安装该部件django-rest-auth用于用户注册。

创建一个新的应用程序“用户”

该应用程序将保存我的用于实现自定义用户模型的自定义代码。我还将其安装在Django主要设置文件中:

settings.py:

INSTALLED_APPS = [
    ...
    'users',
]
Run Code Online (Sandbox Code Playgroud)

创建我的自定义用户模型

请注意,我只是添加了一个自定义字段,但是您可以添加所需的任何字段。

users.models.py:

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


class CustomUser(AbstractUser):
    preferred_locale = models.CharField(max_length=2, blank=True, null=True)
Run Code Online (Sandbox Code Playgroud)

告诉Django使用CustomUser模型

settings.py:

…
AUTH_USER_MODEL = 'users.CustomUser'
Run Code Online (Sandbox Code Playgroud)

在Django管理员处注册自定义用户模型

users.admin.py:

from django.contrib import admin

from .models import CustomUser


admin.site.register(CustomUser)
Run Code Online (Sandbox Code Playgroud)

进行迁移并运行它们

这是我第一次为此项目做。

在命令行中:

python manage.py makemigrations users
python manage.py migrate
Run Code Online (Sandbox Code Playgroud)

使用其他字段注册新用户

如果现在启动Django开发服务器,您将在admin中看到可以看到自定义用户模型以及其他字段。

但是,当您转到“ http://127.0.0.1:8000/rest-auth/registration/ ”时,您还没有看到额外的字段。

在用户注册过程中,使用了两个重要的类,即:

  • 序列化器“ rest_auth.registration.RegisterSerializer”
  • 适配器'allauth.account.adapter.DefaultAccountAdapter'

我们将为这两者创建一个自定义版本,以继承其父类的所有功能。

创建一个自定义的RegisterSerializer

在用户应用/文件夹中创建一个新文件“ serializers.py”。

users.serializers.py:

from rest_framework import serializers

from allauth.account.adapter import get_adapter
from allauth.account.utils import setup_user_email

from rest_auth.registration.serializers import RegisterSerializer


class CustomRegisterSerializer(RegisterSerializer):
    preferred_locale = serializers.CharField(
        required=False,
        max_length=2,
    )

    def get_cleaned_data(self):
        data_dict = super().get_cleaned_data()
        data_dict['preferred_locale'] = self.validated_data.get('preferred_locale', '')
        return data_dict
Run Code Online (Sandbox Code Playgroud)

在这里,我为自定义用户模型上的每个其他字段创建一个新字段。因此,在我的情况下,添加了以下内容:

preferred_locale = serializers.CharField(
        required=False,
        max_length=2,
    )
Run Code Online (Sandbox Code Playgroud)

另外,get_cleaned_data方法应返回一个dict,其中包含注册新用户时要保存的字段的所有数据。

这就是原始方法(默认RegisterSerializer的样子):

def get_cleaned_data(self):
    return {
        'username': self.validated_data.get('username', ''),
        'password1': self.validated_data.get('password1', ''),
        'email': self.validated_data.get('email', '')
    }
Run Code Online (Sandbox Code Playgroud)

如您所见,它返回一个字典,其中包含新用户的所有数据。您想为已添加到自定义用户模型中的每个额外字段向此字典添加keyval条目。

就我而言,只需为字段“ preferred_locale”添加数据,这就是结果方法:

def get_cleaned_data(self):
    data_dict = super().get_cleaned_data()
    data_dict['preferred_locale'] = self.validated_data.get('preferred_locale', '')
    return data_dict
Run Code Online (Sandbox Code Playgroud)

告诉Django使用这个新的序列化器

settings.py:

REST_AUTH_REGISTER_SERIALIZERS = {
    'REGISTER_SERIALIZER': 'users.serializers.CustomRegisterSerializer',
}
Run Code Online (Sandbox Code Playgroud)

预防错误

如果尝试注册新用户,则在运行开发服务器的控制台中可能会收到以下错误:ConnectionRefusedError:[Errno 111]连接被拒绝

尽管仍在创建用户,但是您可以通过将以下行添加到settings.py文件中来解决此错误:

settings.py:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Run Code Online (Sandbox Code Playgroud)

删除用户时将发生的另一个错误是:

django.db.utils.OperationalError: no such table: allauth_socialaccount
Run Code Online (Sandbox Code Playgroud)

要解决此问题,请将其添加到您的settings.py中:

settings.py:

INSTALLED_APPS = [
    ...
    'allauth.socialaccount',  
]
Run Code Online (Sandbox Code Playgroud)

之后,您应该先应用迁移,然后才能继续:

python manage.py migrate
Run Code Online (Sandbox Code Playgroud)

创建一个自定义AccountAdapter

完成上述步骤后,转到“ http://127.0.0.1:8000/rest-auth/registration/ ”将为您显示其他字段。但是,当您注册新用户并发送额外字段的数据时,不会保存额外字段的数据。

解决此问题的最后一件事是创建一个自定义AccountAdapter

在我们的用户应用程序/文件夹中,创建一个名为“ adapter.py”的新文件:

users.adapter.py:

from allauth.account.adapter import DefaultAccountAdapter


class CustomAccountAdapter(DefaultAccountAdapter):

    def save_user(self, request, user, form, commit=False):
        user = super().save_user(request, user, form, commit)
        data = form.cleaned_data
        user.preferred_locale = data.get('preferred_locale')
        user.save()
        return user
Run Code Online (Sandbox Code Playgroud)

在这里,如果您正确地执行了上述步骤,则可以访问form.cleaned_data词典中额外添加的字段的数据。这是由自定义RegisterSerializer中的get_cleaned_data方法返回的字典。

在上面的save_user方法中,我们可以使用此数据并将其保存到适当的字段中,如下所示:

user.preferred_locale = data.get('preferred_locale')
Run Code Online (Sandbox Code Playgroud)

告诉Django使用此新适配器

settings.py:

ACCOUNT_ADAPTER = 'users.adapter.CustomAccountAdapter'
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用django-rest-auth注册端点'/ rest-auth / registration /'注册用户,并发送添加的其他字段的数据。这将全部保存在一个请求中。

同样,我意识到需要为每个字段添加自定义验证。但这是我以后将要探讨的另一个主题,当我发现它的工作原理时,将对其进行更新。

  • 这是一个很棒的解决方案,我发现的唯一问题是我无法通过管理面板注册用户。具体问题是它没有对密码进行哈希处理。 (3认同)
  • 我在 Stackoverflow 上见过的最好的答案。太感谢了 (3认同)

Him*_*kar 4

让我们打破你的问题。Django REST Framework请注意,我正在向您解释基本知识。

覆盖用户模型

  • [x] 第 1 步:覆盖User模型:您做到了这一点。选择?是的。OneToOneForeignKey创建一个指向模型的模型User
  • [x] 第 2 步:使用这个CustomUserModel. 为此,您需要AUTH_USER_MODELsettings.py 链接到官方文档中设置您已执行此操作。
  • [ ] 第三步:创建一个UserManager用于处理用户的注册等信息。你还没有做到这一点。

从API注册

  • [ ] 创建一个serializer明确提及您期望最终用户提供的所有必填字段的字段。serializer.ModelSerializer如果没有自定义字段,您甚至可以使用。
  • [ ] 本身处理显式验证serializer。如果需要,请使用def validate(self, attrs)。这是官方文档链接。
  • [ ] 最后,创建一个视图并使用,APIView因为您希望使用UserManager上面创建的视图来注册用户。

我还可以向您推荐一个我自己构建的应用程序。这是链接:DRF-USER。我User在某种程度上定制了模型并遵循相同的过程。

希望这可以帮助。