简体   繁体   English

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

[英]How to register users in Django REST framework?

I'm coding a REST API with Django REST framework .我正在使用Django REST framework编写 REST API。 The API will be the backend of a social mobile app. API 将成为社交移动应用程序的后端。 After following the tutorial, I can serialise all my models and I am able to create new resources and update them.遵循本教程后,我可以序列化我的所有模型,并且能够创建新资源并更新它们。

I'm using AuthToken for authentication.我正在使用 AuthToken 进行身份验证。

My question is:我的问题是:

Once I have the /users resource, I want the app user to be able to register.一旦我拥有/users资源,我希望应用程序用户能够注册。 So, is it better to have a separate resource like /register or allow anonymous users to POST to /users a new resource?那么,拥有像/register这样的单独资源还是允许匿名用户向/users新资源更好?

Also, some guidance about permissions would be great.此外,有关权限的一些指导会很棒。

Django REST Framework 3 allow override create method in serializers: Django REST Framework 3 允许在序列化程序中覆盖create方法:

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.ModelSerializer继承的类的序列化字段必须在Meta for Django Rest Framework v3.5和最新版本中明确声明。

File api.py :文件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

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.我使 url 与 /users 资源不同。

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 ...我可能错了,但您似乎不需要限制此视图的权限,因为您想要未经身份验证的请求......

The simplest solution, working in DRF 3.x:最简单的解决方案,在 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. write_only_fields将确保不显示密码(实际上:我们存储的它们的哈希),而覆盖的create方法确保密码不是以明文形式存储,而是作为哈希存储。

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).我通常像对待任何其他需要授权的 API 端点一样对待用户视图,除了我只是用我自己的 POST(也称为创建)覆盖视图类的权限集。 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 djangorestframework 3.3.x / Django 1.8.x

I updated Cahlan's answer to support custom user models from Django 1.5 and return the user's ID in the response.我更新了 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)

@cpury above suggested using write_only_fields option.上面的@cpury 建议使用write_only_fields选项。 This however did not work for me in DRF 3.3.3然而,这在 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:DRF 3.0 中write_only_fields上的write_only_fields选项已移至 PendingDeprecation,并在DRF 3.2 中替换为更通用的 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.这会导致两次 DB 写入。 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 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.我们可以使用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

A Python 3, Django 2 & Django REST Framework viewset based implementation:基于 Python 3、Django 2 和 Django REST Framework 视图集的实现:

File: serializers.py文件: 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 :文件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文件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 .虽然这个问题有很多答案,但没有一个答案(在我撰写本文时)解决关键的安全问题,即settings.AUTH_PASSWORD_VALIDATORS定义的密码验证。 So it is possible to create a password like '1' which must not be acceptable.所以有可能创建一个像'1'这样的密码,这是不可接受的。 So I have fixed this major security issue.所以我已经解决了这个主要的安全问题。 Here is my solution:这是我的解决方案:

In serializers.py:在 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:在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: API 响应:

Now, if you try with a simple password like '1' , this response will be returned automatically:现在,如果您尝试使用像'1'这样的简单密码,则会自动返回此响应:

{
    "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:如果是像'12345678'这样的密码,响应是:

{
    "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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM