如何在 DRF 中实现注册新用户功能。
首先应该理解 Django 原有的 流程。DRF 与 Django 的注册流程不同的是:
- Django 有现成的
UserCreationForm
,校验表单中提交的用户名密码信息并实际保存到数据库;DRF 没有,需要自己实现 - Django 的
<form>
中需要带有 csrf token,但 DRF 中不需要带;如果用户在未登陆状态,对它实施 CSRF 攻击意义也不大
下面的实现上重度参考了 Django: Auth: Signup 中 Django 本身的实现,以及这个 StackOverflow 帖子。实现的重点写在注释中。
Serializer 实现
# app/serializers.py
from rest_framework import serializers
from django.contrib.auth import get_user_model, password_validation
# get_user_model 可以获得 custom user model
UserModel = get_user_model()
class UserSerializer(serializers.ModelSerializer):
# password1 及 password2 表示用户在注册表单中,填写的「密码」和「确认密码」两个位置
# write_only 因为它不需要出现在读接口中
# input_type 使它在 DRF 的 browsable API 页面中是一个 <input type="password"> 而不是 <input type="text">
# min_length 及 max_length 是额外的要求,因为 Django 默认只限制密码长度为 128 位,看这里:
# django.contrib.auth.base_user.AbstractBaseUser
password1 = serializers.CharField(
label="Password", write_only=True, style={'input_type': 'password'},
min_length=8, max_length=20
)
password2 = serializers.CharField(
label="Password confirmation", write_only=True, style={'input_type': 'password'},
min_length=8, max_length=20
)
def validate_password1(self, value):
# 生成一个 UserModel 对象传进去,password validation 才可以做密码跟用户名的相似度检查
password_validation.validate_password(
value, UserModel(username=self.initial_data['username'], email=self.initial_data['email'])
)
return value
def validate(self, data):
# 多 field 组合校验,在这里做
if data['password1'] != data['password2']:
raise serializers.ValidationError("The two password fields didn't match")
return data
def create(self, validated_data):
user = UserModel.objects.create(
username=validated_data['username'],
email=validated_data['email'],
)
# set_password 会对明文密码做 hash
user.set_password(validated_data['password1'])
user.save()
return user
class Meta:
model = UserModel
fields = ("id", "username", "email", "password1", "password2")
extra_kwargs = {
# Django 默认的 django.contrib.auth.base_user.AbstractBase 对用户名长度限制宽松,这里加强
'username': {
'max_length': 16, 'min_length': 6,
'help_text': 'Required. 6 to 16 characters required. Letters, digits and @/./+/-/_ only.'
},
# Django 默认非必填,这里要求必填
'email': {'required': True},
}
Views 实现
# app/views.py
from django.contrib.auth import get_user_model
from rest_framework import permissions
from rest_framework.generics import CreateAPIView
from app.serializers import UserSerializer
class SignupView(CreateAPIView):
model = get_user_model()
permission_classes = [
# 使用 AllowAny 使匿名用户也可以注册。
# DRF 默认的权限会使得匿名用户无法调用 POST 方法。
permissions.AllowAny
]
serializer_class = UserSerializer
Urls 配置
# project/urls.py
from app.views import SignupView
urlpatterns = [
# ...
path('signup/', SignupView.as_view(), name='signup'),
]