What is the proper way to change custom user password? - django

I am working on my Django (DRF) application. A have a CustomUser model
class CustomAccountManager(BaseUserManager):
def create_superuser(self, email, user_name, password, **other_fields):
...
def create_user(self, email, user_name, password, **other_fields):
if not email:
raise ValueError(_('You must provide an email address'))
email = self.normalize_email(email)
user = self.model(email=email, user_name=user_name, **other_fields)
user.set_password(password)
user.save()
return user
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
user_name = models.CharField(max_length=150, unique=True) # Full name
phone_number = models.CharField(max_length=20, unique=True)
...
I have created a custom way to change password.
I am sending current_password, new_password_verify and new_password_verify in body parameter.
My solution is working, but looks bulky
What is the proper way to implement password change in Django?
class CustomUserViewSet(viewsets.ModelViewSet):
def update(self, request: Request, *args, **kwargs):
instance: CustomUser = self.get_object()
serializer = self.get_serializer(
instance, data=request.data, partial=True
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, "_prefetched_objects_cache", None):
instance._prefetched_objects_cache = {}
return Response({
"is_password_updated": self.update_password(request, instance), # <-------UPDATE
"result": serializer.data
})
def update_password(self, request, instance):
"""update password if 'new_password_verify' and 'new_password' are in request"""
if "current_password" in request.data and instance.check_password(request.data["current_password"]) and \
"new_password" in request.data and "new_password_verify" in request.data and \
request.data["new_password"] == request.data["new_password_verify"]:
instance.set_password(request.data["new_password"])
instance.save()
return True
return False

I think you can extract the validating logic from the view to make it more modularized by defining the serializer.
class ChangePasswordSerializer(serializers.Serializer):
current_password = serializers.CharField(trim_whitespace = False, validators=[validate_password]),
new_password = serializers.CharField(trim_whitespace = False, validators=[validate_password])
new_password_verify = serializers.CharField(trim_whitespace = False)
def validate(self, attrs):
if attrs.get('new_password') != attrs.get('new_password_verify'):
serializers.ValidationError('Password and confirm password do not match')
return attrs
def validate_password(value):
# you can set your own validating logic here if you want to
# for example, like validations for length or regex
pass
Of course, you don't need to upload new_password_verify data, and check that part in the frontend then the new_password_verify field and validate method is not necessary and the code will be simpler.
class CustomUserViewSet(viewsets.ModelViewSet):
def update(self, request: Request, *args, **kwargs):
...
return Response({
"is_password_updated": self.update_password(request, instance)
"result": serializer.data
})
def update_password(self, request, instance):
serializer = ChangePasswordSerializer(data = request.data)
if serializer.is_valid():
input_data = serializer.validated_data
cur_password = input_data.get('current_password')
new_password = input_data.get('new_password')
if instance.check_password(cur_password):
instance.set_password(new_password)
instance.save()
return True
return False

Related

how to add more attributes to what an api returns

I am trying to write an API using django rest framework in which, you give a username and a password and in return you get an AuthToken or in other words you login. now I want this API to also return some fields like the email of the user along with the AuthToken. so if the authentication was successful, the get an authToken and the user's email. Can anyone help me on how I could be able to do this by adding or changing a bit of my code?
These are my models:
class UserManager(BaseUserManager):
def createUser(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Email Not Found!!!')
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def createSuperUser(self, email, password):
user = self.createUser(email, password)
user.isAdmin = True
user.isSuperUser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
isSuspended = models.BooleanField(default=False)
isAdmin = models.BooleanField(default=False)
emailActivation = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
objects = UserManager()
USERNAME_FIELD = 'username'
These are my serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('username','email', 'password', 'name')
extra_kwargs = {'password': {'write_only': True, 'min_length': 8}}
def create(self, validated_data):
return get_user_model().objects.createUser(**validated_data)
def update(self, instance, validated_data):
password = validated_data.pop('password', None)
user = super().update(instance, validated_data)
if password:
user.set_password(password)
user.save()
return user
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(trim_whitespace=False)
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
user = authenticate(
request=self.context.get('request'),
username= username,
password= password
)
if not user:
msg = 'Authentication Failed.'
raise serializers.ValidationError(msg, code='authentication')
attrs['user'] = user
return attrs
And finally, these are my views:
class CreateUserView(generics.CreateAPIView):
serializer_class = UserSerializer
class CreateTokenView(ObtainAuthToken):
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
class ManageUserView(generics.RetrieveAPIView):
serializer_class = UserSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
return self.request.user
create a new serializer inside serializer.py
from rest_framework.authtoken.models import Token as DefaultTokenModel
class TokenSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = DefaultTokenModel
fields = ('key', 'user',)
add this function in views.py
def get_token_response(user):
serializer_class = TokenSerializer
token, _ = DefaultTokenModel.objects.get_or_create(user=user)
serializer = serializer_class(instance=token)
return Response(serializer.data, status=status.HTTP_200_OK)
now override post method of CreateTokenView
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
return get_token_response(user)
For what i understand you just want to return the toekn and the email of the user right? I used this class based view to login users using token authentication.
from rest_framework.authtoken.views import ObtainAuthToken
class UserLoginView(ObtainAuthToken):
def post(self, request, **kwargs):
serializer = self.serializer_class(data=request.data,
context={
'request':request
})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response(
{
'token':token.key,
'email':user.email,
}
)

Unable to login because Django saving passwords in plain text but

class UserManager(BaseUserManager):
def create_user(self, username, email, password=None):
if username is None:
raise TypeError('User should have a username')
if email is None:
raise TypeError('User should have an email')
user = self.model(
username=username,
email=self.normalize_email(email)
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password=None):
if password is None:
raise TypeError('Password should not be none')
user = self.create_user(username, email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(
max_length = 255,
unique = True,
db_index = True
)
email = models.EmailField(
max_length = 255,
unique = True,
db_index = True
)
is_verified = models.BooleanField(default = False)
is_staff = models.BooleanField(default = True)
is_active = models.BooleanField(default = True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = UserManager()
def __str__(self):
return self.email
def tokens(self):
refresh = RefreshToken.for_user(self)
return {
'refresh': str(refresh),
'access': str(refresh.access_token)
}
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length = 255,
min_length = 6,
write_only = True
)
class Meta:
model = User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}
def validate(self, attrs):
email = attrs.get('email', '')
username = attrs.get('username', '')
if not username.isalnum():
raise serializers.ValidationError(
"Username should contain only alphanumeric characters"
)
return attrs
def create(self, validated_data):
return User.objects.create_user(**validated_data)
class RegisterView(generics.GenericAPIView):
permission_classes = (AllowAny,)
serializer_class = RegisterSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(
serializer.data,
status=status.HTTP_201_CREATED
)
Currently I'm using django 3.1.3 and djangorestframework 3.12.2. While I'm able to login with superuser and getting tokens properly but couldn't login with staff users. From Django admin I have seen only superuser has hashed password and all other users have plain text password.
You do not set the password with user.password, that is just a text field on a model and works like any other. You must either:
preferred: Call user.set_password(value)
backup: Calculate the password manually using make_password before saving
You already have a create method so we'll work with that first.
from django.contrib.auth.hashers import make_password
def create(self, validated_data):
pwd = validated_data.pop("password")
user = User.objects.create(**attrs)
user.set_password(pwd)
user.save(updated_fields=["password"])
return user
# or you could replace it in validated_data
def create(self, validated_data):
validated_data["password"] = make_password(validated_data["password"])
return User.objects.create(**attrs)
You have a validation routine, but it is global. If you make it specific to the password field then you can calculate it there and leave create alone. People don't usually do that, because it is standard to have a "confirm password" field as well.
def validate_password(self, value):
# ...
return make_password(value)
Now if you want to add a confirm_password field then you are going to need to a validate(self, attrs) anyway to compare the two fields, so I would recommend against this last method.

django.db.utils.IntegrityError: UNIQUE constraint failed: authentication_user.email

I am trying create user through an API, But i am struck on above error.
Below are the code of the User and its manager. Here, I am creating custom user model.
class UserManager(BaseUserManager):
def create_user(self,username,email, password=None):
if username is None:
raise TypeError('Users should have a Username')
if email is None:
raise TypeError('Users should have a Email')
user = self.model(username=username,email=self.normalize_email)
user.set_password(password)
user.save()
return user
def create_superuser(self,username,email, password=None):
if password is None:
raise TypeError('Password should not be none')
user = self.create_user(username, email, password)
user.save()
return user
class User(AbstractBaseUser,PermissionsMixin):
username = models.CharField(max_length=255, unique=True, db_index=True)
email = models.EmailField(max_length=255,unique=True,db_index=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects= UserManager()
def __str__(self):
return self.email
Below is serializers.py file.
class RegisterSerializers(serializers.ModelSerializer):
password = serializers.CharField(max_length=68, min_length=6, write_only=True)
class Meta:
model = User
fields = ['email','username','password']
def validate(self, attrs):
email = attrs.get('email','')
username = attrs.get('username','')
if not username.isalnum():
raise serializers.ValidationError('The username should only contain alphanumeric character')
return attrs
def create(self, validated_data):
return User.objects.create_user(**validated_data)
Here is POST request in views.py
class RegisterView(generics.GenericAPIView):
serializer_class = RegisterSerializers
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
I am new to drf. Kindly help me out, thanks.

How to return user details in response after login in django rest_framework

Hi I am beginner in Django Here I want user email and name in response after user login using api. Thanks in advance for your help.
models.py
I want user email and user name in response only getting auth token in response
class UserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError('Users must have an email address')
if not password:
raise ValueError('Users must have a password')
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
user = self.create_user(email,password)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
serializers.py
I want user email and user name in response only getting auth token in response
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
class Meta:
model = get_user_model()
fields = ('email', 'password', 'name')
extra_kwargs = {'password': {'write_only': True, 'min_length': 6}}
def create(self, validated_data):
user = User.objects.create_user('name',validated_data['email'],
validated_data['password'],)
return user
class AuthTokenSerializer(serializers.Serializer):
email = serializers.CharField()
password = serializers.CharField(
style = {'input_type':'password'},
trim_whitespace = False
)
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
user = authenticate(
request = self.context.get('request'),
username = email,
password = password
)
if not user:
msg = _('Unable to authenticate with provided crenditial')
raise serializers.ValidationError(msg, code = 'authorization')
attrs['user'] = user
return attrs
urls.py
I want user email and user name in response only getting auth token in response
path('api/login/',views.CreateTokenView.as_view(),name='token'),
View file for creating view and I dont now how to return user from Auth token. I want user email and user name in response only getting auth token in response
views.py
class UserCreate(APIView):
"""
Creates the user.
"""
def post(self, request, format='json'):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
token = Token.objects.create(user=user)
json = serializer.data
json['token'] = token.key
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CreateTokenView(ObtainAuthToken):
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
def post(self, request, *args, **kwargs):
response = super(CreateTokenView, self).post(request, *args, **kwargs)
token = Token.objects.get(key=response.data['token'])
return Response({'token': token.key})
I guess what you can do is maybe once you recieve a valid token for the user, you could query for the user using the user's email from the request data.
def post(self, request, *args, **kwargs):
response = super(CreateTokenView, self).post(request, *args, **kwargs)
token = Token.objects.get(key=response.data['token'])
user_email = request.data.get('email', None)
user = User.objects.get(email=user_email)
return Response({'name': user.name, 'email': user.email, 'token': token.key})
Hope this helps!
I know this is an old post but I ended up searching for this.
For the new comers:
There's an example on Django REST Framework, available here
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})

Django Rest Framework custom user model PUT Request

I'm having trouble while sending PUT request for my custom user model
On sending PUT request to /api/provider/1/, where 1 is pk, with put data phone_number = 1234567890 I get {"email": ["This field is required."] }
If I send email and phone_number, I get {"email": ["Provider with this email already exists."]}
Model
class ProviderManager(BaseUserManager):
def _create_user(self, email, password, is_staff, is_superuser, **extra_fields):
'''Creates and saves a User with the given email and password'''
now = timezone.now()
if not email:
raise ValueError('Email can\'t be empty')
email = self.normalize_email(email)
user = self.model(email=email, is_active=True, is_superuser=is_superuser,
is_staff=is_staff, last_login=now, date_joined=now,
**extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password, phone_number, **extra_fields):
extra_fields['phone_number'] = phone_number
return self._create_user(email, password, False, False, **extra_fields)
def create_user(self, email, password, phone_number, **extra_fields):
extra_fields['phone_number'] = phone_number
return self._create_user(email, password, True, True, **extra_fields)
class Provider(AbstractBaseUser):
name = models.CharField(max_length=256, default='')
email = models.EmailField(max_length=256, unique=True, db_index=True)
phone_number = models.CharField(max_length=32, unique=True)
language = models.CharField(max_length=8, default='')
currency = models.CharField(max_length=8, default='')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['phone_number',]
objects = ProviderManager()
class Meta:
verbose_name = 'user'
verbose_name_plural = 'users'
def __unicode__(self):
return self.email
def get_full_name(self):
return self.name
def get_short_name(self):
return self.name
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email, [self.email])
Serializer
class ProviderSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('id', 'name', 'email', 'phone_number', 'language', 'currency',)
View
#api_view(['GET', 'PUT', 'DELETE'])
def provider_detail(request, provider_id):
'''Read, update or delete a provider'''
try:
provider = User.objects.get(pk=provider_id)
except User.DoesNotExist:
return Response(status=HTTP_404_NOT_FOUND)
print('GOT PROVIDER ' + provider.email)
if request.method == 'GET':
serializer = ProviderSerializer(provider)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = ProviderSerializer(provider, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
provider.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
When doing a PUT you need to submit all the fields for the model only a PATCH would allow you to update by passing one field
There is partial parameter which you can pass to serializer, to indicate, that it is a partial update.
From the doc:
http://www.django-rest-framework.org/api-guide/serializers/#partial-updates