簡體   English   中英

如何在 Django REST 框架中注冊用戶?

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

我正在使用Django REST framework編寫 REST API。 API 將成為社交移動應用程序的后端。 遵循本教程后,我可以序列化我的所有模型,並且能夠創建新資源並更新它們。

我正在使用 AuthToken 進行身份驗證。

我的問題是:

一旦我擁有/users資源,我希望應用程序用戶能夠注冊。 那么,擁有像/register這樣的單獨資源還是允許匿名用戶向/users新資源更好?

此外,有關權限的一些指導會很棒。

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", )

ModelSerializer繼承的類的序列化字段必須在Meta for Django 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

我繼續制作自己的自定義視圖來處理注冊,因為我的序列化程序不希望顯示/檢索密碼。 我使 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)

我可能錯了,但您似乎不需要限制此視圖的權限,因為您想要未經身份驗證的請求......

最簡單的解決方案,在 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方法確保密碼不是以明文形式存儲,而是作為哈希存儲。

我通常像對待任何其他需要授權的 API 端點一樣對待用戶視圖,除了我只是用我自己的 POST(也稱為創建)覆蓋視圖類的權限集。 我通常使用這種模式:

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

我更新了 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 建議使用write_only_fields選項。 然而,這在 DRF 3.3.3 中對我不起作用

DRF 3.0 中write_only_fields上的write_only_fields選項已移至 PendingDeprecation,並在DRF 3.2 中替換為更通用的 extra_kwargs:

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

到目前為止,所有答案都會創建用戶,然后更新用戶的密碼。 這會導致兩次 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

參加聚會有點晚,但可能會對不想編寫更多代碼行的人有所幫助。

我們可以使用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

基於 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

雖然這個問題有很多答案,但沒有一個答案(在我撰寫本文時)解決關鍵的安全問題,即settings.AUTH_PASSWORD_VALIDATORS定義的密碼驗證。 所以有可能創建一個像'1'這樣的密碼,這是不可接受的。 所以我已經解決了這個主要的安全問題。 這是我的解決方案:

在 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

在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 響應:

現在,如果您嘗試使用像'1'這樣的簡單密碼,則會自動返回此響應:

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

如果是像'12345678'這樣的密碼,響應是:

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

通過這種方式,最終客戶端將確切地知道密碼有效還需要什么。

# 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

為簡化起見,將您的視圖修改為

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