I am creating a login method using Django-rest-Knox. I have created a custom user in app using AbstractBaseUser.
This is my views.py
class LoginView(GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data) # Load request body to serializer
serializer.is_valid(raise_exception=True) # Validate data in serializer and raise an error if one is found
user = serializer.validated_data # Get the validated data from the serializer
token = AuthToken.objects.create(user)[1] # Create an Authentication Token for the user
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data, # Get serialized User data
"token": token
})
Serializer.py
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
# class Meta:
# model = User
# fields = ('username', 'password')
def Validate(self, data):
user = authenticate(**data)
if user:
return user
raise serializers.ValidationError("Incorrect Credentials")
This is my custom user model
class User(AbstractBaseUser):
_id = models.AutoField
email = models.EmailField(verbose_name='email', max_length=255, unique=True)
username = models.CharField(verbose_name='username', max_length = 100, unique=True)
name = models.CharField(max_length = 100)
date_joined = models.DateTimeField(verbose_name="date-joined", auto_now_add=True)
last_login = models.DateTimeField(verbose_name="last-login", auto_now=True)
category = models.CharField(max_length=50, default= "teacher")
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_teacher = models.BooleanField(default=False)
is_parent = models.BooleanField(default=False)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email',]
objects = UserManager()
def __str__(self):
# return self.email
return self.username
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
I am getting an Value error when submitting login as Cannot assign "OrderedDict([('username', 'api_test'), ('password', 'test#123')])": "AuthToken.user" must be a "User" instance.
I think somewhere Django/Knox is not able to get user and user.is_active in my LoginSerializer. I have initiated my user model from initial migrations.
Any help to resolve this issue would be great. Thanks.
In this part of your code :
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data, # Get serialized User data
"token": token
})
You need to replace the token by an instance of the user on which this token belong so for example :
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data, # Get serialized User data
"token": User.objects.get(token=token)
})
Related
I'm building a login and signup api with django rest frame work.
I created a custom user, and I'm using django-rest-knox library for authentication.
I'm getting error "AuthToken.user" must be a "User" instance.
Custom User Definition
class User(AbstractBaseUser):
email = models.EmailField(verbose_name='email_address', max_length=255, unique=True,)
full_name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
staff = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
phone_number = PhoneNumberField(region='NG', blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['full_name', 'phone_number'] # Email & Password are required by default.
def get_full_name(self):
# The user is identified by their email address
return self.full_name
def get_short_name(self):
# The user is identified by their email address
return self.email
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
return self.staff
#property
def is_admin(self):
"Is the user a admin member?"
return self.admin
objects = UserManager()
Register Serializer
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'full_name', 'email', 'phone_number', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(validated_data['email'], validated_data['full_name'], validated_data['phone_number'], validated_data['password'])
return user
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()
token = AuthToken.objects.create(user)[1]
return Response({
"User": UserSerializer(user, context=self.get_serializer_context()).data,
"Token": token
})
I have three types of users in CustomUser model each provided with an integer value. Also, I have made a separate view for login for vendor.
I registered a user as vendor from default django admin. But I am unable to login as a vendor when I call the login as vendor api. I get the error message saying this is not a seller account.
My models:
class CustomUser(AbstractUser):
username = None
first_name = models.CharField(max_length=255, verbose_name="First name")
last_name = models.CharField(max_length=255, verbose_name="Last name")
email = models.EmailField(unique=True)
Type_Choices = (
(1, 'Customer'),
(2, 'Vendor'),
(3, 'Admin'),
)
user_type = models.IntegerField(choices=Type_Choices, default=1)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["first_name", "last_name"]
objects = CustomUserManager()
def __str__(self):
return self.email
My serializers:
class CustomLoginSerializer(LoginSerializer):
username = None
email = serializers.EmailField(required=True)
password = serializers.CharField(style={"input_type": "password"},)
user_type = serializers.IntegerField()
My views:
class VendorLoginUserView(LoginView):
permission_classes = [AllowAny]
serializer_class = CustomLoginSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = CustomLoginSerializer(data=data)
data['user_type'] = request.user.user_type
user_type = data['user_type']
if user_type is 2:
serializer.is_valid(raise_exception=True)
new_data = serializer.data
user = serializer.validated_data["user"]
serializer = self.get_serializer(user)
token, created = Token.objects.get_or_create(user=user)
# return response.Response(new_data, status=status.HTTP_200_OK)
return response.Response({"token": token.key,
"serializer.data": serializer.data},
status=status.HTTP_200_OK)
else:
message = "This is not a seller account"
return Response({'message':message,},
status=status.HTTP_400_BAD_REQUEST)
My urls:
path("api/vendorlogin/",views.VendorLoginUserView.as_view(), name='api-vendorlogin'),
Also, do I need to make a separate register view for vendor??
I am using jwt auth system When I fill the field it returns jwt token easily but if i want to create a profile with this token it says
CustomUser has no attribute 'objects'
my users/models.py
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=40, unique=True)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
date_joined = models.DateTimeField(default=timezone.now)
object = UserManager()
USERNAME_FIELD = 'email'
def save(self, *args, **kwargs):
super(CustomUser, self).save(*args, **kwargs)
return self
and user/views.py
class CreateUserAPIView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
user = request.data
serializer = UserSerializer(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
try:
email = request.data['email']
password = request.data['password']
user = CustomUser.object.get(email=email, password=password)
if user:
try:
payload = jwt_payload_handler(user)
token = jwt.encode(payload, settings.SECRET_KEY)
user_details = {}
user_details['id'] = "%s" % (user.id)
user_details['token'] = token
user_logged_in.send(sender=user.__class__,
request=request, user=user)
return Response(user_details, status=status.HTTP_200_OK)
except Exception as e:
raise e
else:
return Response(status=status.HTTP_403_FORBIDDEN)
except KeyError:
res = {'error': 'please provide a email and a password'}
return Response(res)
with this code I am getting a token but with this token I cannot create a profile
profile/models.py
class Profile(TimeModel):
user = models.OneToOneField('user.CustomUser', on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
birthdate = models.DateTimeField(null=True)
image = models.ImageField(upload_to='path', null=True)
def __str__(self):
return self.user.email
here what i have tried so far. I tried to look for solution but could not get any and none of them solved my problems. How can I solve this problems. Any help would be appreciated! Thank you!
P.S
In the serializers file there is no additional overridden functions it just includes model name and fields of Profile model
You're using a custom manager and you assign that to object instead of objects in your CustomUser model.
class CustomUser(AbstractBaseUser, PermissionsMixin):
...
objects = UserManager()
...
Also, your queryset should use CustomUser.objects.get(...) Another thing I've realized is you're overriding the save method inside your CustomUser but you're not doing anything specific there, so there's no need to override that method.
I think you've a typo in your code - it should be objects:
user = CustomUser.objects.get(email=email, password=password)
I was wondering if anyone knows why is it that DRF doesn't capitalise the first letter of the user object in the error message and is there a simple way to correct this?
Error message for create user:
{
"password": [
"This field is required."
],
"email": [
"user with this email already exists."
]
}
models.py
class User(AbstractBaseUser, PermissionsMixin):
created = models.DateTimeField(auto_now_add=True)
email = models.EmailField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __str__(self):
return self.email
serializers.py
class RegisterUserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ('id', 'password', 'email')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = models.User(email=validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
views.py
class UserListView(generics.ListCreateAPIView):
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
serializer = RegisterUserSerializer(data=request.data)
if serializer.is_valid():
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The easiest way to do that is
class User(AbstractBaseUser, PermissionsMixin):
created = models.DateTimeField(auto_now_add=True)
email = models.EmailField(max_length=255, unique=True,error_messages={
'unique': "User with this email already exists.",
},)
...
To answer your question:
why is it that DRF doesn't capitalise the first letter of the user object
If not explicitly provided , DRF constructs the unique error message like this:
unique_error_message = unique_error_message % {
'model_name': model_field.model._meta.verbose_name,
'field_label': model_field.verbose_name
}
And for the django's user model, verbose name is:
>>> from django.contrib.auth.models import User
>>> User.Meta.verbose_name
'user'
I have a custom user model
class User(AbstractUser):
username = None
email = models.EmailField( unique=True)
phone = models.CharField( max_length=15)
is_pro = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['phone']
objects = UserManager()
#property
def token(self):
"""
Allows us to get a user's token by calling `user.token` instead of
`user.generate_jwt_token().
The `#property` decorator above makes this possible. `token` is called
a "dynamic property".
"""
return self._generate_jwt_token()
def _generate_jwt_token(self):
"""
Generates a JSON Web Token that stores this user's ID and has an expiry
date set to 60 days into the future.
"""
import jwt
from datetime import datetime, timedelta
from django.conf import settings
dt = datetime.now() + timedelta(days=60)
token = jwt.encode({
'id': self.pk,
'exp': int(dt.strftime('%s'))
}, settings.SECRET_KEY, algorithm='HS256')
return token.decode('utf-8')
Now I try make SignIn API with Django Rest Framework using this tutorial https://thinkster.io/tutorials/django-json-api/authentication
serializer.py
class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=128,
min_length=8,
write_only=True
)
token = serializers.CharField(max_length=255, read_only=True)
class Meta:
model = User
fields = ['email', 'phone', 'password', 'token']
def create(self, validated_data):
# Use the `create_user` method we wrote earlier to create a new user.
return User.objects.create_user(**validated_data)
views.py
class RegistrationAPIView(APIView):
# Allow any user (authenticated or not) to hit this endpoint.
permission_classes = (AllowAny,)
serializer_class = RegistrationSerializer
def post(self, request):
user = request.data.get('user', {})
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Then I create a new user an error occured "This field is required." for all my User fields, like email, phone, password.
Screenshot picture http://joxi.ru/12MOMkZt4eeBEr
This is because you set this fields not nullable and required inside User model. To fix it you can add blank=True arguments to the fields which may be blank, like phone:
class User(AbstractUser):
username = None
email = models.EmailField(unique=True)
phone = models.CharField(max_length=15, blank=True)
is_pro = models.BooleanField(default=False)
After this run makemigrations and migrate to apply changes at DB level.
UPD
In view you need to get data from request.data directly:
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
request.data doesn't contain user key, so request.data.get('user', {}) return empty dict.