Як зареєструвати користувачів у фреймворку Django REST?


78

Я кодую REST API за допомогою Django REST framework . API буде бекендом соціального мобільного додатка. Після підручника я можу серіалізувати всі свої моделі, і я можу створювати нові ресурси та оновлювати їх.

Я використовую AuthToken для автентифікації.

Моє запитання:

Отримавши /usersресурс, я хочу, щоб користувач програми мав можливість зареєструватися. Отже, чи краще мати окремий ресурс, наприклад, /registerабо дозволити анонімним користувачам POST на /usersновий ресурс?

Крім того, деякі вказівки щодо дозволів були б чудовими.


Відповіді:


49

Я продовжив і створив власний власний вигляд для обробки реєстрації, оскільки мій серіалізатор не очікує показу / отримання пароля. Я зробив URL-адресу відмінною від ресурсу / users.

Мій url conf:

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
хотів зазначити, що причина, через яку Калан використовує init_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

1
@yossi Рішення полягає у використанні CAPTCHA. Дроселювання не вирішує проблему повністю.
Перрелл

чи є спосіб вставити ім’я користувача як електронну пошту в серіалізовані дані?
Hardik Gajjar

93

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

Чому ви використовуєте user.set_password замість того, щоб встановлювати параметр ключового слова пароля в UserModel.objects.create ()?
personjerry

Ah nevermind Я бачу, ти не використовував create_user, який обробляє хешування паролів
personjerry

як можна додати додаткові поля до реєстрації?
uber

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метод гарантує, що пароль зберігається не в чистому тексті, а як хеш.


Вибачте, якщо я помиляюся, але чи потрібно це явно, щоб замінити метод створення? Я спробував просто додати write_only_fields і read_only_fields, і це спрацювало так, як я очікував. Будь-які підказки?
vabada

8
@dabad Якщо ви це зробите, пароль, ймовірно, збережеться в чистому тексті в базі даних, чого ви абсолютно не хочете. Єдиний рядок, createякий додає спеціальний метод, - це власний метод Django set_passwordдля генерації хешу для пароля.
cpury

33

Зазвичай я поводжуся з переглядом користувача так само, як і з будь-якою іншою кінцевою точкою API, яка вимагає авторизації, за винятком того, що я просто перекриваю дозвіл класу подання, встановлений моїм власним для POST (aka 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


2
Я подивився всі відповіді, і, схоже, всі пропонують зробити логіку збереження моделей у серіалізаторі. Я вважаю, що це суперечить керівним принципам Django MVVM, де логіка "контролера" повинна бути на увазі
Олег Белоусов

27

Я оновив відповідь Калана для підтримки користувацьких моделей користувачів з Django 1.5 і повернув ідентифікатор користувача у відповідь.

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)

Як ви обробляєте пароль тут?
blueШвидкий

19
НЕБЕЗПЕКА Якщо я не помиляюся, цей код дозволяє подання is_superuserта is_staffзначення. Дозволені поля слід чітко вказувати IMO, як показано в інших прикладах.
Марсель Честейн

12

@cpury вище запропонував використовувати write_only_fieldsопцію. Однак у DRF 3.3.3 це не спрацювало для мене

У ФПІ 3.0write_only_fields варіант на ModelSerializer був переміщений в PendingDeprecation і в ФПІ 3.2 замінений більш загальним extra_kwargs:

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


7

Поки що всі відповіді створюють користувача, а потім оновіть пароль користувача. Це призводить до двох записів БД. Щоб уникнути зайвого непотрібного запису в БД, встановіть пароль користувача перед його збереженням:

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-адресу для реєстрацій?
Бернард 'Бета Берлін' Парах

Ось додатковий біт для тих, хто хоче залишити поле пароля прихованим: клас UserSerializer (serializers.HyperlinkedModelSerializer): password = serializers.CharField (write_only = True, style = {'input_type': 'password', 'placeholder': 'Password' },)
timi95

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.