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.userIf 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 18538I 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?