Aaron Presley

← Writing
Add Confirm Password Field to UserSerializer in Django Rest Framework

December 9, 2015

I'm pretty new to the Django Rest Framework in general, but I had a hell of a time figuring out how to add a confirm_password field to my UserSerializer model.

The currently suggested methods seem overcomplicated for what I was wanting to do - which is just confirm and validate the user's password!

Eventually I realized I could just make a new Serializer that only handled my registration fields. I'm not sure if this is the best way to do it, but it seems more straightforward than subclassing a field and making my own.


Here's my eventual solution - I created a UserRegistrationSerializer class that's not tied to a model. This allowed me to add whatever fields I wanted without worrying about adding a non-model field to the UserSerializer.

from rest_framework import serializers, viewsets
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework.decorators import (api_view, permission_classes, list_route)
from django.http import Http404, HttpResponseForbidden
from django.contrib.auth.models import User


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'email', 'first_name', 'last_name',)
        write_only_fields = ('username',)

    def validate(self, data):
        # Making sure the username always matches the email
        email = data.get('email', None)
        if email:
            data['username'] = email
        return data


class UserRegistrationSerializer(serializers.Serializer):
    email = serializers.EmailField()
    first_name = serializers.CharField(required=False)
    last_name = serializers.CharField(required=False)
    password = serializers.CharField()
    confirm_password = serializers.CharField()

    def validate_email(self, email):
        existing = User.objects.filter(email=email).first()
        if existing:
            raise serializers.ValidationError("Someone with that email "
                "address has already registered. Was it you?")
        return email

    def validate(self, data):
        if not data.get('password') or not data.get('confirm_password'):
            raise serializers.ValidationError("Please enter a password and "
                "confirm it.")
        if data.get('password') != data.get('confirm_password'):
            raise serializers.ValidationError("Those passwords don't match.")
        return data


class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @list_route(methods=['post'], permission_classes=[AllowAny])
    def register(self, request):
        # Validating our serializer from the UserRegistrationSerializer
        serializer = UserRegistrationSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # Everything's valid, so send it to the UserSerializer
        model_serializer = UserSerializer(data=serializer.data)
        model_serializer.is_valid(raise_exception=True)
        model_serializer.save()

        return Response(model_serializer.data)

    @list_route(methods=['get'])
    def info(self, request):
        serializer = UserSerializer(request.user)
        return Response(serializer.data)

    def list(self, request):
        user = request.user
        if not user or not user.is_superuser:
            return HttpResponseForbidden()
        return super(UserViewSet, self).list(request)

    def update(self, request, pk=None):
        user = User.objects.filter(id=pk).first()
        if not user or request.user != user:
            return HttpResponseForbidden()
        return super(UserViewSet, self).update(request)

Like I said: I'm new to Django Rest Framework, and this might be dumb. Please let me know if there's a better approach!