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


77

我正在使用Django REST框架编写REST API 。该API将成为社交移动应用程序的后端。完成本教程之后,我可以序列化所有模型,并且可以创建新资源并对其进行更新。

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

我的问题是:

获得/users资源后,我希望应用程序用户能够注册。因此,拥有像这样的单独资源/register还是允许匿名用户将其发布到/users新资源更好?

另外,有关权限的一些指导也将非常有用。


Answers:


49

因为我的序列化程序不希望显示/检索密码,所以我继续进行了自己的自定义视图来处理注册。我使该URL与/ users资源不同。

我的网址配置:

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

我的看法:

@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)

我可能是错的,但是似乎您不需要限制此视图的权限,因为您想要未经身份验证的请求...


12
想指出的是,Cahlan之所以使用init_data而不是data的原因是因为UserSerializer它不读/写密码。致电后is_valid(),如果有人想使用就可以了serialized.data['email']serialized.data['username']但是密码仅在中可用serialized.init_data['password']。电子邮件和用户名参数的顺序也应该切换(至少在Django 1.6中)。或者您可以始终传递命名参数,例如User.objects.create_user(email='me@example.com', username='admin', password='admin123')

只是好奇,这个解决方案会不安全吗?这意味着任何了解此端点并不断注册用户的机构?
DjangoRocks

1
@DjangoRocks你说得对,但可以使用限制
yossi 2015年

1
@yossi解决方案是使用CAPTCHA。节流不能完全解决问题。
Purrell '16

有什么方法可以在序列化数据中将用户名作为电子邮件插入?
Hardik Gajjar

93

Django REST Framework 3允许create序列化程序中的override方法:

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(
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()

        return user

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

从继承的类的序列化字段ModelSerializer必须在MetaDjango Rest Framework v3.5中明确声明和最新版本中。

文件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

2
这是最快,最新的方法。
SeedyROM '17

为什么使用user.set_password而不是在UserModel.objects.create()中设置password关键字参数?
personjerry

啊,没关系,我看到您没有使用处理密码哈希的create_user
personjerry

如何为注册添加额外的字段?
超级

41

在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

无需其他更改,只需确保未经身份验证的用户具有创建新用户对象的权限。

write_only_fields将确保不显示密码(实际上是:我们存储的哈希值),而覆盖create方法则确保密码不是以明文形式存储,而是以哈希形式存储。


抱歉,如果我错了,但是明确需要重写create方法吗?我尝试只添加write_only_fields和read_only_fields,它按我的预期工作。有什么线索吗?
vabada

8
@dabad如果这样做,密码可能会以明文形式存储在数据库中,这是您绝对不希望的。自定义create方法添加的唯一一行是Django本机set_password方法,用于为密码生成哈希。
cpury

33

我通常将User视图与其他需要授权的API端点一样对待,只不过我只是使用自己的POST(也称为create)覆盖了视图类的权限集。我通常使用这种模式:

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()

为了达到良好的效果,以下是我通常使用的序列化程序:

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


1
我查看了所有答案,似乎每个人都建议在串行器中执行节省模型的逻辑。我认为这违反了Django MVVM准则,在该准则中应考虑“控制器”逻辑
Oleg Belousov,

27

我更新了Cahlan的答案,以支持Django 1.5中的自定义用户模型,并在响应中返回用户的ID。

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)

您在这里如何处理密码?
blueFast

19
危险如果我没记错的话,此代码允许提交is_superuseris_staff值。如其他示例所示,应在IMO中明确指定允许的字段。
马塞尔·查斯顿

12

上面的@cpury建议使用write_only_fields选项。但是,这在DRF 3.3.3中对我不起作用

DRF 3.0write_only_fields,ModelSerializer上的选项已移至PendingDeprecation,而在DRF 3.2中,该选项已替换为更通用的extra_kwargs:

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


7

到目前为止,所有答案都将创建用户,然后更新用户密码。这导致两次DB写操作。为避免不必要的数据库写操作,请在保存之前设置用户密码:

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

1
好点子。更糟糕的是,分两步进行似乎是出于安全考虑。如果没有在原子事务中完成此操作,则在创建用户的位置与使用哈希密码保存的用户之间存在错误时,则保存在数据库中的数据将采用明文格式
rottweilers_anonymous

5

晚会晚了一点,但是可能对不想编写更多代码行的人有所帮助。

我们可以使用super实现此目的的方法。

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

我是否需要为注册设置不同的URL?
Bernard'Beta Berlin'Parah

对于想要隐藏密码字段的用户来说,这是额外的一点:类UserSerializer(serializers.HyperlinkedModelSerializer):password = serializers.CharField(write_only = True,style = {'input_type':'password','placeholder':'Password' },)
timi95 '19

2

一个基于Python 3,Django 2和Django REST Framework视图集的实现:

文件: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',)

文件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

文件urls.py

from rest_framework.routers import DefaultRouter
from .views import CreateUserView

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

urlpatterns = router.urls
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.