如何在 Django REST 框架中注册用户?

我正在用 Django REST 框架编写 REST API。这个 API 将成为一个社交移动应用程序的后端。在遵循教程后,我可以序列化我所有的模型,我能够创建新的资源和更新他们。

我正在使用 AuthToken 进行身份验证。

我的问题是:

一旦我有了 /users资源,我希望应用程序用户能够注册。那么,是使用单独的资源(如 /register)更好,还是允许匿名用户将新资源 POST 到 /users

另外,一些关于权限的指导也很不错。

105369 次浏览

I went ahead and made my own custom view for handling registration since my serializer doesn't expect to show/retrieve the password. I made the url different from the /users resource.

My url conf:

url(r'^users/register', 'myapp.views.create_auth'),

My view:

@api_view(['POST'])
def create_auth(request):
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
User.objects.create_user(
serialized.init_data['email'],
serialized.init_data['username'],
serialized.init_data['password']
)
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

I may be wrong, but it doesn't seem like you'll need to limit permissions on this view since you'd want unauthenticated requests ...

I updated Cahlan's answer to support custom user models from Django 1.5 and return the user's ID in the response.

from django.contrib.auth import get_user_model


from rest_framework import status, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response


class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()


@api_view(['POST'])
def register(request):
VALID_USER_FIELDS = [f.name for f in get_user_model()._meta.fields]
DEFAULTS = {
# you can define any defaults that you would like for the user, here
}
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
user_data = {field: data for (field, data) in request.DATA.items() if field in VALID_USER_FIELDS}
user_data.update(DEFAULTS)
user = get_user_model().objects.create_user(
**user_data
)
return Response(UserSerializer(instance=user).data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

Django REST Framework 3 allow override create method in serializers:

from rest_framework import serializers
from django.contrib.auth import get_user_model # If used custom user model


UserModel = get_user_model()




class UserSerializer(serializers.ModelSerializer):


password = serializers.CharField(write_only=True)


def create(self, validated_data):


user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
)


return user


class Meta:
model = UserModel
# Tuple of serialized model fields (see link [2])
fields = ( "id", "username", "password", )

Serialized fields for classes inherited from ModelSerializer must be declared patently in Meta for Django Rest Framework v3.5 and newest.

File api.py:

from rest_framework import permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth import get_user_model # If used custom user model


from .serializers import UserSerializer




class CreateUserView(CreateAPIView):


model = get_user_model()
permission_classes = [
permissions.AllowAny # Or anon users can't register
]
serializer_class = UserSerializer

The simplest solution, working in DRF 3.x:

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
write_only_fields = ('password',)
read_only_fields = ('id',)


def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)


user.set_password(validated_data['password'])
user.save()


return user

No need for other changes, just make sure that unauthenticated users have the permission to create a new user object.

write_only_fields will make sure passwords (actually: their hash we store) are not displayed, while the overwritten create method ensures that the password is not stored in clear text, but as a hash.

I typically treat the User view just like any other API endpoint that required authorization, except I just override the view class's permission set with my own for POST (aka create). I typically use this pattern:

from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework.permissions import AllowAny




class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects
serializer_class = UserSerializer


def get_permissions(self):
if self.request.method == 'POST':
self.permission_classes = (AllowAny,)


return super(UserViewSet, self).get_permissions()

For good measure, here is the serializer I typically use with it:

class UserSerializer(serializers.ModelSerializer):


class Meta:
model = get_user_model()
fields = (
'id',
'username',
'password',
'email',
...,
)
extra_kwargs = {
'password': {'write_only': True},
}


def create(self, validated_data):
user = get_user_model().objects.create_user(**validated_data)
return user


def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password')
instance.set_password(password)
return super(UserSerializer, self).update(instance, validated_data)

djangorestframework 3.3.x / Django 1.8.x

A little late to the party, but might help someone who do not want to write more lines of code.

We can user the super method to achieve this.

class UserSerializer(serializers.ModelSerializer):


password = serializers.CharField(
write_only=True,
)


class Meta:
model = User
fields = ('password', 'username', 'first_name', 'last_name',)


def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
if 'password' in validated_data:
user.set_password(validated_data['password'])
user.save()
return user

@cpury above suggested using write_only_fields option. This however did not work for me in DRF 3.3.3

In DRF 3.0 the write_only_fields option on ModelSerializer has been moved to PendingDeprecation and in DRF 3.2 replaced with a more generic extra_kwargs:

extra_kwargs = {'password': {'write_only': True}}

All of the answers so far create the user, then update the user's password. This results in two DB writes. To avoid an extra unnecessary DB write, set the user's password before saving it:

from rest_framework.serializers import ModelSerializer


class UserSerializer(ModelSerializer):


class Meta:
model = User


def create(self, validated_data):
user = User(**validated_data)
# Hash the user's password.
user.set_password(validated_data['password'])
user.save()
return user

A Python 3, Django 2 & Django REST Framework viewset based implementation:

File: serializers.py

from rest_framework.serializers import ModelSerializers
from django.contrib.auth import get_user_model


UserModel = get_user_model()


class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True)


def create(self, validated_data):
user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
)
return user


class Meta:
model = UserModel
fields = ('password', 'username', 'first_name', 'last_name',)

File views.py:

from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from django.contrib.auth import get_user_model
from .serializers import UserSerializer


class CreateUserView(CreateModelMixin, GenericViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer

File urls.py

from rest_framework.routers import DefaultRouter
from .views import CreateUserView


router = DefaultRouter()
router.register(r'createuser', CreateUserView)


urlpatterns = router.urls

While there are many answers to this question, none of the answers (as of my writing) addresses the critical security concern, the password validation that is defined in settings.AUTH_PASSWORD_VALIDATORS. So it is possible to create a password like '1' which must not be acceptable. So I have fixed this major security issue. Here is my solution:

In serializers.py:

from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers




class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email', 'password', ]
extra_kwargs = {
'password': {'write_only': True}
}


def validate_password(self, value):
validate_password(value)
return value


def create(self, validated_data):
user = get_user_model()(**validated_data)


user.set_password(validated_data['password'])
user.save()


return user

In views.py:

from rest_framework import mixins, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated


from . import forms, serializers




class SignupViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
permission_classes = [AllowAny]
serializer_class = serializers.SignupSerializer

API Response:

Now, if you try with a simple password like '1', this response will be returned automatically:

{
"password": [
"This password is too short. It must contain at least 8 characters.",
"This password is too common.",
"This password is entirely numeric."
]
}

In case of a password like '12345678', the response is:

{
"password": [
"This password is too common.",
"This password is entirely numeric."
]
}

In this way, the end-client will know exactly what else are required for the password to be valid.

# This work nicely, but serializer will reamain as it is, like


from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers




class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email', 'password', ]
extra_kwargs = {
'password': {'write_only': True}
}


def validate_password(self, value):
validate_password(value)
return value


def create(self, validated_data):
user = get_user_model()(**validated_data)


user.set_password(validated_data['password'])
user.save()


return user

To simplify, modify your view to

from rest_framework import mixins, viewsets


from rest_framework.permissions import AllowAny, IsAuthenticated


from . import forms, serializers
class SignUpUserView(mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [AllowAny]
queryset = get_user_model().objects.all() #Add this line
serializer_class = SignUpSerializer