How to register users in Django REST framework?

DjangoPython 2.7Django ModelsDjango Rest-Framework

Django Problem Overview


I'm coding a REST API with Django REST framework. The API will be the backend of a social mobile app. 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.

My question is:

Once I have the /users resource, I want the app user to be able to register. So, is it better to have a separate resource like /register or allow anonymous users to POST to /users a new resource?

Also, some guidance about permissions would be great.

Django Solutions


Solution 1 - Django

Django REST Framework 3 allow override create method in serializers:

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.

File 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

Solution 2 - Django

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.

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

Solution 3 - Django

The simplest solution, working in 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.

Solution 4 - Django

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

Solution 5 - Django

I updated Cahlan's answer to support custom user models from Django 1.5 and return the user's ID in the response.

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)

Solution 6 - Django

@cpury above suggested using write_only_fields option. This however did not work for me in DRF 3.3.3

In http://www.django-rest-framework.org/topics/3.0-announcement/#serializers">DRF 3.0 the write_only_fields option on ModelSerializer has been moved to PendingDeprecation and in http://www.django-rest-framework.org/topics/3.2-announcement/">DRF 3.2 replaced with a more generic extra_kwargs:

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

Solution 7 - Django

All of the answers so far create the user, then update the user's password. This results in two DB writes. 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

Solution 8 - Django

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.

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

Solution 9 - Django

A Python 3, Django 2 & Django REST Framework viewset based implementation:

File: 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:

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

from rest_framework.routers import DefaultRouter
from .views import CreateUserView

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

urlpatterns = router.urls

Solution 10 - Django

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. So it is possible to create a password like '1' which must not be acceptable. So I have fixed this major security issue. Here is my solution:

In 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:

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:

Now, if you try with a simple password like '1', this response will be returned automatically:

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

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

Solution 11 - Django

# 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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionchaimView Question on Stackoverflow
Solution 1 - DjangoDunaevsky MaximView Answer on Stackoverflow
Solution 2 - DjangoCahlan SharpView Answer on Stackoverflow
Solution 3 - DjangocpuryView Answer on Stackoverflow
Solution 4 - DjangodigitalfooView Answer on Stackoverflow
Solution 5 - DjangoChrisView Answer on Stackoverflow
Solution 6 - DjangoTadej KrevhView Answer on Stackoverflow
Solution 7 - DjangoyndolokView Answer on Stackoverflow
Solution 8 - DjangoKaran KumarView Answer on Stackoverflow
Solution 9 - DjangoNick Van BerckelaerView Answer on Stackoverflow
Solution 10 - DjangoarnobplView Answer on Stackoverflow
Solution 11 - DjangoTimotheo MhojaView Answer on Stackoverflow