How to serialize a custom user model(inherited from AbstractUser class) in django rest framework?

2,102 views
Skip to first unread message

Shishir Jha

unread,
May 8, 2020, 7:11:00 AM5/8/20
to Django users

I was following a tutorial on django rest framework in which we create a new user using register api route(/api/auth/register) and login the registered user using login api route(/api/auth/login). In this tutorial,for token authentication, django-rest-knox has been used and the default User model provided by django has been used for user accounts. I am trying to do something similar in my own app but I am using custom user model which has been inherited from AbstractUser class provided by django and also I am using Django Token authentication provided by rest_framework.authtoken.

Following are the code and their results for serializers, Apiviews from the tutorial:

Serializers look like this:

from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate

# User Serializer
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'email')

# Register Serializer
class RegisterSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'email', 'password')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'])

        return user

# Login Serializer
class LoginSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def validate(self, data):
        user = authenticate(**data)
        if user and user.is_active:
            return user
        raise serializers.ValidationError("Incorrect Credentials")

Now using these serializers API views were created like this:

# Register API
class RegisterAPI(generics.GenericAPIView):
    serializer_class = RegisterSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        return Response({
            "user": UserSerializer(user, context=self.get_serializer_context()).data,
            "token": AuthToken.objects.create(user)[1]
        })

# Login API
class LoginAPI(generics.GenericAPIView):
    serializer_class = LoginSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data
        return Response({
            "user": UserSerializer(user, context=self.get_serializer_context()).data,
            "token": AuthToken.objects.create(user)[1]
        })

# Get User API
class UserAPI(generics.RetrieveAPIView):
    permission_classes = [
        permissions.IsAuthenticated,
    ]
    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user

If I test the /api/login route after registering the user on postman, It returns the following Response:

{
    "user": {
        "id": 1,
        "username": "django",
        "email": "d...@g.com"
    },
    "token": "e65df26c73d38480df70be484e4c3c27ab08ab48a3a63c8df831ed3a155f089c"
}

Now when I check the UserSerializer above in django shell, it returns the expected output as shown below:

(InteractiveConsole)
>>> from accounts.serializers import UserSerializer
>>> serializer = UserSerializer()
>>> serializer
UserSerializer():
    id = IntegerField(label='ID', read_only=True)
    username = CharField(help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[<django.contrib.auth.validators.UnicodeUsernameValidator object>, <UniqueValidator(queryset=User.objects.all())>])
    email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
>>> 

In my project I followed same approach but with Custom User model named Employee. My CustomUserManager class looks like this:

from django.contrib.auth.base_user import BaseUserManager

class EmployeeManager(BaseUserManager):

    def create_user(self,email,password, **extra_fields):

        if not email:
            raise ValueError("Email must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user
    def create_superuser(self,email,password,**extra_fields):
        extra_fields.setdefault('is_staff',True)
        extra_fields.setdefault('is_superuser',True)
        extra_fields.setdefault('is_active',True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff set to True')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser set to True')
        return self.create_user(email,password,**extra_fields)

Custom User Model Class:

from django.db import models
from django.contrib.auth.models import AbstractUser
from .managers import EmployeeManager
from django import utils

# Create your models here.
class Employee(AbstractUser):
    username = models.CharField(max_length=20, null=True, blank=True)
    # username=None
    email = models.EmailField(verbose_name='email address',max_length=255,unique=True)
    first_name = models.CharField(max_length=100)
    middle_name = models.CharField(max_length=100,null=True,blank=True)
    last_name = models.CharField(max_length=100)
    mobile_no = models.CharField(max_length=15,null=True,blank=True)
    designation = models.CharField(max_length=50,null=True,blank=True)
    manager = models.ForeignKey('self',models.SET_NULL,blank=True,null=True)
    created_at = models.DateTimeField(default=utils.timezone.now, verbose_name='created at')
    date_joined = models.DateTimeField(blank=True,null=True)


    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = EmployeeManager()

    def __str__(self):
        return self.email

Now using this custom user model I also created similar serializers as in tutorial like LoginSerializer for /login route and EmployeeSerializer to return /user route. Also I did not create RegisterSerializer or RegisterAPI because I will be using users registered by admin. Only admin can add or delete users. So my serializer classes look like:

from rest_framework import serializers
from .models import Employee
from django.contrib.auth import get_user_model
from django.contrib.auth import authenticate

# User Serializer
class EmployeeSerializer(serializers.ModelSerializer):
    model = Employee #get_user_model()
    fields = '__all__'

# Login Serializer
class LoginSerializer(serializers.Serializer):
    email = serializers.EmailField()
    password = serializers.CharField()

    def validate(self, data):
        user = authenticate(**data)
        if user and user.is_active:
            return user
        raise serializers.ValidationError("Incorrect Credentials")

Now if I want to inspect my EmployeeSerializer in django shell same as UserSerializer above, I get the following error:

(InteractiveConsole)
>>> from accounts.serializers import EmployeeSerializer
>>> serializer = EmployeeSerializer()
>>> serializer
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/serializers.py", line 537, in __repr__
    return representation.serializer_repr(self, indent=1)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/utils/representation.py", line 79, in serializer_repr
    for field_name, field in fields.items():
AttributeError: 'str' object has no attribute 'items'
>>> 

Before explaining the consequence of this issue let me show my APIviews:

class LoginAPI(generics.GenericAPIView):
    serializer_class = LoginSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data
        token, _ = Token.objects.get_or_create(user=user)
        return Response({
            "user": EmployeeSerializer(user, context=self.get_serializer_context()).data,
            "token": token.key
        })

Now in my case when I try to login the user I get the following Issue:

Traceback (most recent call last):
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/shishir/Projects/Unirac/performance_management/performance_management/accounts/api.py", line 18, in post
    "user": EmployeeSerializer(user, context=self.get_serializer_context()).data,
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/serializers.py", line 562, in data
    ret = super().data
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/serializers.py", line 260, in data
    self._data = self.to_representation(self.instance)
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/serializers.py", line 514, in to_representation
    for field in fields:
  File "/home/shishir/Projects/Unirac/performance_management/uniracEnv/lib/python3.6/site-packages/rest_framework/serializers.py", line 375, in _readable_fields
    for field in self.fields.values():
AttributeError: 'str' object has no attribute 'values'
[08/May/2020 13:27:12] "POST /api/auth/login HTTP/1.1" 500 18538

I know this problem has something to do with my CustomUser model and EmployeeSerializer. But I don't what is wrong with my implementation. And I need this EmployeeSerializer for /api/user route. So, can anyone tell me what is wrong with my implementation?

Reply all
Reply to author
Forward
0 new messages