Làm thế nào để đăng ký người dùng trong khuôn khổ Django REST?


78

Tôi đang viết mã REST API với khuôn khổ Django REST . API sẽ là phần phụ trợ của một ứng dụng di động xã hội. Sau khi làm theo hướng dẫn, tôi có thể tuần tự hóa tất cả các mô hình của mình và tôi có thể tạo tài nguyên mới và cập nhật chúng.

Tôi đang sử dụng AuthToken để xác thực.

Câu hỏi của tôi là:

Khi tôi có /userstài nguyên, tôi muốn người dùng ứng dụng có thể đăng ký. Vì vậy, tốt hơn là có một tài nguyên riêng biệt như /registerhoặc cho phép người dùng ẩn danh ĐĂNG lên /usersmột tài nguyên mới?

Ngoài ra, một số hướng dẫn về quyền sẽ rất tuyệt.


Câu trả lời:


49

Tôi đã tiếp tục và thực hiện chế độ xem tùy chỉnh của riêng mình để xử lý đăng ký vì trình tuần tự của tôi không mong đợi hiển thị / truy xuất mật khẩu. Tôi đã tạo url khác với tài nguyên / users.

Giới thiệu url của tôi:

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

Quan điểm của tôi:

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

Tôi có thể sai, nhưng có vẻ như bạn không cần giới hạn quyền đối với chế độ xem này vì bạn muốn các yêu cầu chưa được xác thực ...


12
muốn chỉ ra rằng lý do Cahlan sử dụng init_data thay vì dữ liệu là vì UserSerializerkhông đọc / ghi mật khẩu. sau khi gọi is_valid(), sẽ tốt nếu ai đó muốn sử dụng serialized.data['email']serialized.data['username']mật khẩu sẽ chỉ có sẵn trong serialized.init_data['password']. Ngoài ra, thứ tự của các thông số email và tên người dùng nên được chuyển đổi (ít nhất là trong Django 1.6). hay bạn luôn có thể vượt qua tên các thông số ví dụUser.objects.create_user(email='me@example.com', username='admin', password='admin123')

Chỉ tò mò rằng giải pháp này sẽ không an toàn? điều này có nghĩa là bất kỳ cơ quan nào có kiến ​​thức về điểm cuối này và tiếp tục đăng ký người dùng?
DjangoRocks

1
@DjangoRocks bạn là đúng, nhưng bạn có thể sử dụng throttling
Yossi

1
@yossi Giải pháp là sử dụng CAPTCHA. Throttling không hoàn toàn giải quyết được vấn đề.
Purrell

có cách nào để chèn tên người dùng dưới dạng email trong dữ liệu được tuần tự hóa không?
Hardik Gajjar

93

Django REST Framework 3 cho phépcreate phương pháp ghi đè trong trình tuần tự:

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

Các trường được tuần tự hóa cho các lớp kế thừa từ ModelSerializerđó phải được khai báo bằng sáng chế Metacho Django Rest Framework v3.5 và mới nhất.

Tệp 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
Đây là cách nhanh nhất và cập nhật nhất để làm điều này.
SeedyROM

Tại sao bạn sử dụng user.set_password thay vì đặt tham số từ khóa mật khẩu trong UserModel.objects.create ()?
personjerry

Ah nevermind Tôi thấy bạn không sử dụng create_user mà xử lý mật khẩu băm
personjerry

làm cách nào để thêm các trường bổ sung vào đăng ký?
uber

41

Giải pháp đơn giản nhất, hoạt động trong 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

Không cần các thay đổi khác, chỉ cần đảm bảo rằng người dùng chưa được xác thực có quyền tạo đối tượng người dùng mới.

write_only_fieldssẽ đảm bảo mật khẩu (thực tế là: mã băm của chúng mà chúng tôi lưu trữ) không được hiển thị, trong khi createphương pháp ghi đè đảm bảo rằng mật khẩu không được lưu trữ dưới dạng văn bản rõ ràng mà ở dạng băm.


Xin lỗi nếu tôi nhầm, nhưng có cần ghi đè phương thức tạo một cách rõ ràng không? Tôi đã thử chỉ thêm write_only_fields và read_only_fields và nó hoạt động như tôi mong đợi. Bất kì manh mối nào?
vabada

8
@dabad Nếu bạn làm vậy, mật khẩu có thể sẽ được lưu trữ dưới dạng văn bản rõ ràng trong cơ sở dữ liệu, điều mà bạn hoàn toàn không muốn. Dòng duy nhất mà createphương thức tùy chỉnh thêm vào là phương thức Django-native set_passwordđể tạo hàm băm cho mật khẩu.
cpury

33

Tôi thường coi chế độ xem Người dùng giống như bất kỳ điểm cuối API nào khác yêu cầu ủy quyền, ngoại trừ tôi chỉ ghi đè bộ quyền của lớp chế độ xem bằng quyền của riêng tôi cho POST (hay còn gọi là tạo). Tôi thường sử dụng mẫu này:

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

Để có biện pháp tốt, đây là bộ nối tiếp tôi thường sử dụng với nó:

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


2
Tôi đã xem xét tất cả các câu trả lời và có vẻ như mọi người đề nghị thực hiện logic tiết kiệm mô hình trong bộ tuần tự. Tôi nghĩ đây là chống lại chủ trương Django MVVM nơi logic 'điều khiển' nên trong giao diện
Oleg Belousov

27

Tôi đã cập nhật câu trả lời của Cahlan để hỗ trợ các mô hình người dùng tùy chỉnh từ Django 1.5 và trả lại ID của người dùng trong phản hồi.

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)

Bạn đang xử lý mật khẩu ở đây như thế nào?
blueFast

19
NGUY HIỂM Nếu tôi không nhầm, mã này cho phép gửi các giá trị is_superuseris_staff. Các trường được phép phải được chỉ định IMO rõ ràng, như được hiển thị trong các ví dụ khác.
Marcel Chastain

12

@cpury ở trên được đề xuất sử dụng write_only_fieldstùy chọn. Tuy nhiên, điều này không hoạt động đối với tôi trong DRF 3.3.3

Trong DRF 3.0 , write_only_fieldstùy chọn trên ModelSerializer đã được chuyển sang PendingDeprecation và trong DRF 3.2 được thay thế bằng một extra_kwargs chung chung hơn:

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


7

Tất cả các câu trả lời cho đến nay đều tạo người dùng, sau đó cập nhật mật khẩu của người dùng. Điều này dẫn đến hai DB viết. Để tránh ghi thêm DB không cần thiết, hãy đặt mật khẩu của người dùng trước khi lưu:

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
Điểm tốt. Tệ hơn nữa, thực hiện theo hai bước có vẻ như là một mối lo ngại về bảo mật. Nếu không có nó được thực hiện trong một giao dịch nguyên tử, nếu có một lỗi giữa nơi người dùng được tạo ra và người dùng được lưu với một mật khẩu băm sau đó dữ liệu được lưu trong DB là ở dạng cleartext
rottweilers_anonymous

5

Đến bữa tiệc hơi muộn, nhưng có thể giúp ích cho ai đó không muốn viết thêm dòng mã.

Chúng tôi có thể sử dụng superphương pháp để đạt được điều này.

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

tôi có phải đặt một url khác cho đăng ký không?
Bernard 'Beta Berlin' Parah

Đây là một chút bổ sung cho những người muốn ẩn trường mật khẩu: class UserSerializer (serializers.HyperlinkedModelSerializer): password = serializers.CharField (write_only = True, style = {'input_type': 'password', 'placeholder': 'Password' },)
timi95

2

Triển khai dựa trên bộ khung nhìn Python 3, Django 2 & Django REST:

Tệp: 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',)

Tệp 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

Tệp urls.py

from rest_framework.routers import DefaultRouter
from .views import CreateUserView

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

urlpatterns = router.urls
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.