How to fix "Key (username)=() already exists." error - python

I want to create a user with type 'learner'using mobile nummber,when i am trying to do this there is an error,
django.db.utils.IntegrityError: duplicate key value violates unique constraint "user_user_username_key"
DETAIL: Key (username)=() already exists.
views.py
class Register(APIView):
def get_permissions(self):
return (AllowAny(),)
def post(self, request, *args, **kwargs):
mobile = request.data.get('mobile')
if mobile:
data = {'mobile': mobile}
serializer = RegisterSerializer(data=data)
if serializer.is_valid(raise_exception = True):
serializer.save()
return Response('sucess')
else:
return Response('error')
serializers.py
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('mobile',)
def create(self, validated_data):
user_data = validated_data.pop('user')
user_data['user_type'] = 'learner'
user_serializer = UserSerializer(data=user_data)
user_serializer.is_valid(raise_exception=True)
user = user_serializer.save()
user.groups.add(validated_data['group'])
user.save()
return user
instance = self.Meta.model(**validated_data)
instance.user = user
instance.save()
return instance

Related

I want to hash my password but some fields should not be required

In django validation when I signup, i don't want to validate some fields because they are not required, but if i am creating user from another page there i want those fields, so how to do unrequired some fields
**Serializers.py**
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "password" ,"first_name", "last_name","email", "User_Type","url","sign_up_count", "facebook", "twitter", "linkedin", "instagram", "telegram", "discord", "snapchat"]
def create(self, validated_data):
user = User(
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
username=validated_data['email'],
User_Type=validated_data['User_Type'],
facebook=validated_data['facebook'],
twitter=validated_data['twitter'],
linkedin=validated_data['linkedin'],
instagram=validated_data['instagram'],
telegram=validated_data['telegram'],
discord=validated_data['discord'],
snapchat=validated_data['snapchat'],
)
user.set_password(validated_data['password'])
user.save()
return user
**view.py**
class CreateUserByAdmin(APIView):
def post(self, request):
data = request.data
data.update(username = data.get('email'))
try:
if User.objects.filter(email=data.get('email')).exists():
return Response({'details' : 'User Already Exists'}, 403)
else:
serializer = UserSerializer(data=data)
print("serializer Data == > ", serializer.data)
if serializer.is_valid():
serializer.save()
return Response({'details' : 'User Created'})
else:
return Response({'details' : 'Somthing went Wrong'}, 403)
except:
return Response({'details' : 'Somthing went Wrong1'}, 403)
**view.py**
def post(self, request):
params = request.query_params.get("refer")
try:
refer_by=User.objects.get(url=params)
data = json.dumps(request.data)
data = json.loads(data)
username=AddUser(request,data)
SendAccountVerifcationEmail(data)
refer_by=User.objects.get(url=params)
refer_to=User.objects.get(username=username)
ReferLink(refer_by,refer_to)
return Response({"ok":"ok"})
except:
data = json.dumps(request.data)
data = json.loads(data)
username=AddUser(request,data)
SendAccountVerifcationEmail(data)
return Response({"ok":"ok"})
**helpers.py**
def AddUser(request,data):
serializer = UserSerializer(data=data)
if serializer.is_valid():
serializer.save()
username=request.data["email"]
# user_email=request.data["email"]
user = User.objects.get(email=username)
user.username = username
user.save()
token, created = Token.objects.get_or_create(user=user)
if username:
return username
else:
return "Ok"
when i signup a account i dont want facebook, telegram and more..., but if i am creating user from another page that time i neet it. so how to set unrequired fields for validate
Currently you have one serializer: UserSerializer, and this serializer requires all fields like linkedin etc.
You should leave it as it is or even rename it to WebsiteRegistrationUserSerializer to be more clear what this serializer is used to.
Next step is to declare AdminRegistrationUserSerializer like that:
class AdminRegistrationUserSerializer (serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "password" ,"first_name", "last_name","email", "User_Type", <all_fields_you_want_to_include>]
def create(self, validated_data):
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
return user
And after that you can change serializer in CreateUserByAdmin to AdminRegistrationUserSerializer

Update user error with Django API rest framework

I can`t update users because Django gives me this error in postman:
AttributeError at /profesionales/
Got AttributeError when attempting to get a value for field `user` on serializer `ProfesionalesSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'user'.
Request Method: PUT
Request URL: http://127.0.0.1:8000/profesionales/
Django Version: 1.11.6
Python Executable: C:\Users\Ismael\AppData\Local\Programs\Python\Python36\python.exe
Python Version: 3.6.3
Here is my code:
view.py
#Listar todos los profesionales o crear uno
#profesionales/
class ProfesionalesList(APIView):
def get_object(self, pk):
try:
return User.objects.get(username=pk)
except User.DoesNotExist:
raise Http404
def get(self, request ):
usuarios = Profesionales.objects.all()
usuarioSerializer = ProfesionalesSerializer(usuarios, many=True)
return Response(usuarioSerializer.data)
def post(self, request):
profesionalSerializer = ProfesionalesSerializer(data=request.data)
if profesionalSerializer.is_valid():
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
else:
return Response(profesionalSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
def put(self, request, *args, **kwargs):
instance = self.get_object(request.data.get('user').get('username'))
profesionalSerializer = ProfesionalesSerializer(instance, data=request.data)
if profesionalSerializer.is_valid():
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
else:
return Response(profesionalSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializer(serializers.Serializer):
username = serializers.CharField()
first_name = serializers.CharField(allow_blank=True)
last_name = serializers.CharField(allow_blank=True)
email = serializers.CharField(allow_blank=True)
class Meta:
fields = ('username', 'first_name', 'last_name', 'email')
def create(self, validated_data):
user = User.objects.create(**validated_data)
return user
class ProfesionalesSerializer(serializers.Serializer):
user = UserSerializer()
numColegiado = serializers.CharField(allow_blank=False)
class Meta:
fields = ('user', 'numColegiado')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profesional = Profesionales.objects.create(user=user, **validated_data)
return profesional
def update(self, instance, validated_data):
num_colegiado = validated_data.get('numColegiado')
user_data = validated_data.pop('user')
user = User.objects.get(**user_data)
profesionales = user.profesionales
if num_colegiado:
profesionales.numColegiado = num_colegiado
profesionales.save()
return instance
model.py
class Profesionales(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dni = models.CharField(max_length=9, blank=True, default='')
numColegiado = models.CharField(max_length=8, blank=True, default='')
valoracionMedia = models.FloatField(blank=True, default=0)
numVotos = models.IntegerField(blank=True, default=0)
def __str__(self):
return self.numColegiado
Ok, I have it - but to be honest, you should rethink your API design.
The problem is that - there is no problem -I mean from yout code I cannot reproduce the error. It is probably I assumed the User model - if you can paste the user model definition would be great (or if this is a standard django user - also mention that).
So firstly I would change the serializers to model serializers:
serializers.py
class UsernameValidator(object):
def set_context(self, serializer_field):
"""
This hook is called by the serializer instance,
prior to the validation call being made.
"""
# Determine the existing instance, if this is an update operation.
self.instance = getattr(serializer_field.parent, 'instance', None)
if not self.instance:
# try to get user from profesionales:
root_instance = getattr(serializer_field.root, 'instance', None)
self.instance = getattr(root_instance, 'user', None)
def __call__(self, value):
if self.instance and User.objects.filter(username=value).exclude(id=self.instance.id).exists():
raise ValidationError('Username already exists.')
if not self.instance and User.objects.filter(username=value).exists():
raise ValidationError('Username already exists.')
class UserSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=128, validators=[UsernameValidator()])
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class ProfesionalesSerializer(serializers.ModelSerializer):
user = UserSerializer()
numColegiado = serializers.CharField(allow_blank=False)
class Meta:
model = Profesionales
fields = ('user', 'numColegiado')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profesional = Profesionales.objects.create(user=user, **validated_data)
return profesional
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
userSerializer = UserSerializer(user, data=user_data)
if userSerializer.is_valid(raise_exception=True):
userSerializer.save()
num_colegiado = validated_data.get('numColegiado')
if num_colegiado:
instance.numColegiado = num_colegiado
instance.save()
return instance
As you can note - I've added the UsernameValidator which is pretty important for API to work properly 0- it basically search for existing user instance and check if username exists or not;
I've also changed the update method - now it is using the UserSerializer explicite; Also corrected some bugs - returning validated_data instead of instance and so on.
At the end views.py:
class ProfesionalesList(APIView):
def get_object(self, pk):
try:
return User.objects.get(username=pk)
except User.DoesNotExist:
raise Http404
def get(self, request ):
usuarios = Profesionales.objects.all()
usuarioSerializer = ProfesionalesSerializer(usuarios, many=True)
return Response(usuarioSerializer.data)
def post(self, request):
profesionalSerializer = ProfesionalesSerializer(data=request.data)
if profesionalSerializer.is_valid(raise_exception=True):
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
def put(self, request, *args, **kwargs):
user = self.get_object(request.data.get('user').get('username'))
profesionalSerializer = ProfesionalesSerializer(user.profesionales, data=request.data)
if profesionalSerializer.is_valid(raise_exception=True):
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_200_OK)
I've shorten the code - using the raise_exception in is_valid method.
Actually - sorry for not following the stackoverflow rules - and do not provide an answer for your actual problem - but I strongly believe that analyzing the example you can figure it out. If you have any more questions - please ask.
On your class ProfesionalesSerializer, you have defined user as an instance of UserSerializer. Instead, user should be a field. You can't use serializers as "fields".
Edit: Ignore this. Turns out you can. See here: http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
(thanks to #opalczynski)

confusion on which serializer to use to update user profile

I have a profile model for user to update. I want to have a rest api for that. I have a profile model which is too big than usual. I am working to update the user profile but I am having confusion on which serializer should i use to update the profile. Here is what i have done till now
I created the serializer for user and user profile.
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('token', 'user','current_location', 'permanent_location', 'dob',
'about_me', 'gender_status', 'create_profile_for', 'marital_status',
'height', 'weight', 'body_type', 'complexion',)
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(required=True)
class Meta:
model = User
fields = ('id', 'profile', 'username', 'email', 'first_name', 'last_name',)
def update(self, instance, validated_data):
# instance = super(UserSerializer, self).update(validated_data)
profile_data = validated_data.pop('profile')
#updating user data
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.email = validated_data.get('first_name', instance.email)
#updating profile data
if not instance.profile:
Profile.objects.create(user=instance, **profile_data)
instance.profile.current_location = profile_data.get('current_location', instance.profile.current_location)
instance.profile.permanent_location = profile_data.get('permanent_location', instance.profile.permanent_location)
instance.profile.weight = profile_data.get('weight', instance.profile.weight)
instance.profile.height = profile_data.get('height', instance.profile.height)
instance.profile.about_me = profile_data.get('about_me', instance.profile.about_me)
instance.profile.create_profile_for = profile_data.get('create_profile_for', instance.profile.create_profile_for)
instance.profile.body_type = profile_data.get('body_type', instance.profile.body_type)
instance.save()
return instance
This is my view
class UserProfile(APIView):
serializer_class = UserSerializer
def get(self, request, token=None, format=None):
"""
Returns a list of profile of user
"""
reply={}
try:
profile_instance = Profile.objects.filter(user=self.request.user)
if token:
profile = profile_instance.get(token=token)
reply['data'] = self.serializer_class(profile).data
else:
reply['data'] = self.serializer_class(profile_instance, many=True).data
except:
reply['data']=[]
return Response(reply, status.HTTP_200_OK)
def post(self, request, token=None, format=None):
"""
update a profile
"""
profile=None
if not token is None:
try:
profile = Profile.objects.get(user=request.user, token=token)
except Profile.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
serialized_data = self.serializer_class(profile, data=request.data, partial=True)
reply={}
if not serialized_data.is_valid():
return error.ValidationError(serialized_data.errors).as_response()
else:
profile = serialized_data.save(user=request.user)
reply['data']=self.serializer_class(profile, many=False).data
return Response(reply, status.HTTP_200_OK)
How should i handle my user profile update?
Updated my UserSerializer with update function
UPDATE with Dean Christian Armada solution
class UserProfile(APIView):
serializer_class = ProfileSerializer
def get(self, request, token=None, format=None):
"""
Returns a list of profile of user
"""
reply={}
try:
profile_instance = Profile.objects.filter(user=self.request.user)
if token:
profile = profile_instance.get(token=token)
reply['data'] = self.serializer_class(profile).data
else:
reply['data'] = self.serializer_class(profile_instance, many=True).data
except:
reply['data']=[]
return Response(reply, status.HTTP_200_OK)
def put(self, request, token=None, *args, **kwargs):
"""
update a profile
"""
print('token', token)
if token:
try:
profile = Profile.objects.get(token=token)
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
serialized_data = self.serializer_class(profile, data=request.data)
reply={}
if serialized_data.is_valid():
profile = serialized_data.save(user=request.user)
reply['data'] = self.serializer_class(profile, many=False).data
return Response(reply, status.HTTP_200_OK)
else:
return Response(serialized_data.errors, status.HTTP_400_BAD_REQUEST)
With your code, i get This QueryDict instance is immutable error
It's a standard to use the PUT request method for updating in your current code and situation it is better to use the ProfileSerializer since it has more fields. Also, the way I see it you only have three fields in the User object that can be changed and those three fields I assume is rarely changed.
views.py
def put(self, request, *args, **kwargs):
"""
update a profile
"""
data = request.data
print data
try:
profile = Profile.objects.get(token=data.get('token'))
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer = self.serializer_class(profile, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_200_OK)
else:
return Response(serializer.errors,
status.HTTP_400_BAD_REQUEST)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Profile
fields = ('user', 'height', 'weight', 'token')
def to_internal_value(self, data):
first_name = data.pop('first_name', None)
last_name = data.pop('last_name', None)
data = super(ProfileSerializer, self).to_internal_value(data)
data['first_name'] = first_name
data['last_name'] = last_name
return data
def update(self, instance, validated_data):
first_name = validated_data.pop('first_name', None)
last_name = validated_data.pop('last_name', None)
user_inst_fields = {}
if first_name:
user_inst_fields['first_name'] = first_name
if last_name:
user_inst_fields['last_name'] = last_name
if user_inst_fields:
User.objects.update_or_create(pk=instance.user.id,
defaults=user_inst_fields)
profile, created = Profile.objects.update_or_create(
token=instance.token, defaults=validated_data)
return profile
Simply just set the user to read only for it to avoid validation

Error when trying to retrieve the registered users

I am trying, through a CustomUser model and its serializer to register new users and to display them.
Here is my model:
class CustomUser(models.Model):
user = models.OneToOneField(User)
additional_field = models.CharField(max_length=30, default='', blank=True)
My serializer (from Django REST Framework Creating custom user):
class CustomUserSerializer(serializers.ModelSerializer):
additional_field = serializers.CharField(source='customuser.additional_field')
class Meta:
model = User
fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email', 'additional_field')
def create(self, validated_data):
profile_data = validated_data.pop('customuser', None)
user = super(CustomUserSerializer, self).create(validated_data)
self.create_or_update_profile(user, profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('customuser', None)
self.create_or_update_profile(instance, profile_data)
return super(CustomUserSerializer, self).update(instance, validated_data)
def create_or_update_profile(self, user, profile_data):
profile, created = CustomUser.objects.get_or_create(user=user, defaults=profile_data)
if not created and profile_data is not None:
super(CustomUserSerializer, self).update(profile, profile_data)
An my views, respectively to display and register a user:
def get_all_users(request):
all_friends = CustomUser.objects.all()
serial = CustomUserSerializer(all_friends, many=True)
json_data = JSONRenderer().render(serial.data)
return HttpResponse(json_data, content_type='application/json')
#api_view(['POST'])
def register_new_user(request):
if request.method == 'POST':
print('POST request !')
print(request.data)
serializer = CustomUserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print('save friend!')
return HttpResponse(status.HTTP_201_CREATED)
else:
print(serializer.errors)
return HttpResponse(status.HTTP_403_FORBIDDEN)
While I am able to register a user with the following piece of code:
import requests
import json
json_data = {'username': 'mylogin99', 'password': 'mypassword', 'email': 'mymail99#wanadoo.fr', 'additional_field' : 'myaddfield'}
r = requests.post('http://127.0.0.1:8000/register_new_user', json=json_data)
I am unable to display the registered users, as I get the following error:
AttributeError at /getAllUsers
Got AttributeError when attempting to get a value for field `username` on serializer `CustomUserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `CustomUser` instance.
Original exception text was: 'CustomUser' object has no attribute 'username'.
I do not understand why the username field does not refer to the username field of the User model.
The best way I could come up with was to implement a as_json method in the CustomUser model:
def as_json(self):
return dict(
id=self.user.id, username=self.user.username,
password=self.user.password, email=self.user.email,
additional_field=self.additional_field)
Thus, to retrieve all the registered users, I've modified the get_all_user method:
#api_view(['GET'])
def get_all_users(request):
# retrieve all CustomUser objects
all_friends = CustomUser.objects.all()
lst = [friend.as_json() for friend in all_friends]
json_data = JSONRenderer().render(lst)
return HttpResponse(json_data, content_type='application/json')

How to update user password in Django Rest Framework?

I want to ask that following code provides updating password but I want to update password after current password confirmation process. So what should I add for it? Thank you.
class UserPasswordSerializer(ModelSerializer):
class Meta:
model = User
fields = [
'password'
]
extra_kwargs = {
"password": {"write_only": True},
}
def update(self, instance, validated_data):
for attr, value in validated_data.items():
if attr == 'password':
instance.set_password(value)
else:
setattr(instance, attr, value)
instance.save()
return instance
I believe that using a modelserializer might be an overkill. This simple serializer & view should work.
Serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
class ChangePasswordSerializer(serializers.Serializer):
model = User
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
Views.py
from rest_framework import status
from rest_framework import generics
from rest_framework.response import Response
from django.contrib.auth.models import User
from . import serializers
from rest_framework.permissions import IsAuthenticated
class ChangePasswordView(UpdateAPIView):
"""
An endpoint for changing password.
"""
serializer_class = ChangePasswordSerializer
model = User
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
obj = self.request.user
return obj
def update(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
# Check old password
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
response = {
'status': 'success',
'code': status.HTTP_200_OK,
'message': 'Password updated successfully',
'data': []
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#Yiğit Güler give a good answer, thanks, but it could be better in some minor points.
As long you don't really works with UpdateModelMixin, but directly with the request user instance, you don't need to use a UpdateAPIView. A simple APIView is enough.
Also, when the password is changed, you can return a status.HTTP_204_NO_CONTENT instead of a 200 with some random content.
By the way, don't forgot to validate your new password before save. It's too bad if you allow "password" at update while you don't at create.
So I use the following code in my project:
from django.contrib.auth.password_validation import validate_password
class ChangePasswordSerializer(serializers.Serializer):
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
def validate_new_password(self, value):
validate_password(value)
return value
And for the view:
class UpdatePassword(APIView):
"""
An endpoint for changing password.
"""
permission_classes = (permissions.IsAuthenticated, )
def get_object(self, queryset=None):
return self.request.user
def put(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = ChangePasswordSerializer(data=request.data)
if serializer.is_valid():
# Check old password
old_password = serializer.data.get("old_password")
if not self.object.check_password(old_password):
return Response({"old_password": ["Wrong password."]},
status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I dont' think the validation should be done by the view as #Yiğit Güler proposes. Here is my solution:
serializers.py
from django.contrib.auth import password_validation
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
class ChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField(max_length=128, write_only=True, required=True)
new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)
new_password2 = serializers.CharField(max_length=128, write_only=True, required=True)
def validate_old_password(self, value):
user = self.context['request'].user
if not user.check_password(value):
raise serializers.ValidationError(
_('Your old password was entered incorrectly. Please enter it again.')
)
return value
def validate(self, data):
if data['new_password1'] != data['new_password2']:
raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")})
password_validation.validate_password(data['new_password1'], self.context['request'].user)
return data
def save(self, **kwargs):
password = self.validated_data['new_password1']
user = self.context['request'].user
user.set_password(password)
user.save()
return user
views.py
from rest_framework import status
from rest_framework.generics import UpdateAPIView
from rest_framework.authtoken.models import Token
class ChangePasswordView(UpdateAPIView):
serializer_class = ChangePasswordSerializer
def update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
# if using drf authtoken, create a new token
if hasattr(user, 'auth_token'):
user.auth_token.delete()
token, created = Token.objects.get_or_create(user=user)
# return new token
return Response({'token': token.key}, status=status.HTTP_200_OK)
After you save the user, you might want to make sure that the user stays logged in (after django==1.7 an user automatically is logged out on password change):
from django.contrib.auth import update_session_auth_hash
# make sure the user stays logged in
update_session_auth_hash(request, self.object)
I think the easiest (when I say easiest, I mean shortest possible and cleaner) solution would be something like:
View class
class APIChangePasswordView(UpdateAPIView):
serializer_class = UserPasswordChangeSerializer
model = get_user_model() # your user model
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
return self.request.user
Serializer class
from rest_framework import serializers
from rest_framework.serializers import Serializer
class UserPasswordChangeSerializer(Serializer):
old_password = serializers.CharField(required=True, max_length=30)
password = serializers.CharField(required=True, max_length=30)
confirmed_password = serializers.CharField(required=True, max_length=30)
def validate(self, data):
# add here additional check for password strength if needed
if not self.context['request'].user.check_password(data.get('old_password')):
raise serializers.ValidationError({'old_password': 'Wrong password.'})
if data.get('confirmed_password') != data.get('password'):
raise serializers.ValidationError({'password': 'Password must be confirmed correctly.'})
return data
def update(self, instance, validated_data):
instance.set_password(validated_data['password'])
instance.save()
return instance
def create(self, validated_data):
pass
#property
def data(self):
# just return success dictionary. you can change this to your need, but i dont think output should be user data after password change
return {'Success': True}
So I decided to override the update function within ModelSerializer. Then get the password of the User instance. Afterwards run the necessary comparisons of making sure old password is the same as the one currently on the user instance via the check_password function and making sure new password and confirm password slot values are the same then proceed to set the new password if true and save the instance and return it.
serializers.py
class ChangePasswordSerializer(ModelSerializer):
confirm_password = CharField(write_only=True)
new_password = CharField(write_only=True)
old_password = CharField(write_only=True)
class Meta:
model = User
fields = ['id', 'username', 'password', 'old_password', 'new_password','confirm_password']
def update(self, instance, validated_data):
instance.password = validated_data.get('password', instance.password)
if not validated_data['new_password']:
raise serializers.ValidationError({'new_password': 'not found'})
if not validated_data['old_password']:
raise serializers.ValidationError({'old_password': 'not found'})
if not instance.check_password(validated_data['old_password']):
raise serializers.ValidationError({'old_password': 'wrong password'})
if validated_data['new_password'] != validated_data['confirm_password']:
raise serializers.ValidationError({'passwords': 'passwords do not match'})
if validated_data['new_password'] == validated_data['confirm_password'] and instance.check_password(validated_data['old_password']):
# instance.password = validated_data['new_password']
print(instance.password)
instance.set_password(validated_data['new_password'])
print(instance.password)
instance.save()
return instance
return instance
views.py
class ChangePasswordView(RetrieveUpdateAPIView):
queryset= User.objects.all()
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
serializer.py
class UserSer(serializers.ModelSerializers):
class meta:
model=UserModel
fields = '__all__'
views.py
class UserView(UpdateAPIView):
serializer_class = serializers.UserSer
queryset = models.User.objects.all()
def get_object(self,pk):
try:
return models.User.objects.get(pk=pk)
except Exception as e:
return Response({'message':str(e)})
def put(self,request,pk,format=None):
user = self.get_object(pk)
serializer = self.serializer_class(user,data=request.data)
if serializer.is_valid():
serializer.save()
user.set_password(serializer.data.get('password'))
user.save()
return Response(serializer.data)
return Response({'message':True})
I want to add another option, in case you have a ModelViewSet. This way you'd probably want to use an #action for the password updating, this way you can still handle every aspect of the user model using the ModelViewSet and still customize the behavior and serializer utilized on this action, and I would also add a custom permission to verify the user is trying to update it's own information.
permissions.py:
from rest_framework import exceptions
from rest_framework.permissions import BasePermission, SAFE_METHODS
from django.utils.translation import gettext_lazy as _
from users.models import GeneralUser
class IsSelf(BasePermission):
def has_object_permission(self, request, view, obj):
if isinstance(obj, GeneralUser):
return request.user == obj
raise exceptions.PermissionDenied(detail=_("Received object of wrong instance"), code=403)
*I'm using my custom user model classGeneralUser
views.py:
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from rest_framework.response import Response
from rest_framework import viewsets
from django.utils.translation import gettext_lazy as _
from users.api.serializers import UserSerializer, UserPwdChangeSerializer
from users.api.permissions import IsSelf
class UserViewSet(viewsets.ModelViewSet):
__doc__ = _(
"""
<Your Doc string>
"""
)
permission_classes = (IsAuthenticated, IsSelf)
serializer_class = UserSerializer
def get_queryset(self):
return GeneralUser.objects.filter(pk=self.request.user.pk)
def get_permissions(self):
if self.action == 'create':
permission_classes = [AllowAny]
else:
permission_classes = [IsAuthenticated]
return [permission() for permission in permission_classes]
# ....
# Your other actions or configurations
# ....
#action(detail=True, methods=["put"])
def upassword(self, request, pk=None):
user = GeneralUser.objects.get(pk=pk)
self.check_object_permissions(request, user)
ser = UserPwdChangeSerializer(user, data=request.data, many=False, context={
"user":request.user
})
ser.is_valid(raise_exception=True)
user = ser.save()
return Response(ser.data, status=status.HTTP_200_OK)
serializers.py:
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import make_password
from django.core import exceptions
from django.contrib.auth.password_validation import validate_password as v_passwords
from rest_framework import serializers
from users.models import GeneralUser
class UserSerializer(serializers.ModelSerializer):
__doc__ = _(
"""
Serializer for User model
"""
)
class Meta:
model = GeneralUser
fields = '__all__'
read_only_fields = ["last_login", "date_joined"]
extra_kwargs = {'password': {'write_only': True}}
def validate_password(self, value: str) -> str:
try:
v_passwords(value, GeneralUser)
return make_password(value)
except exceptions.ValidationError as e:
raise serializers.ValidationError(e.messages)
class UserPwdChangeSerializer(serializers.Serializer):
__doc__ = _(
"""
Serializer for user model password change
"""
)
old_password = serializers.CharField(max_length=128, write_only=True, required=True)
new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)
new_password2 = serializers.CharField(max_length=128, write_only=True, required=True)
def validate_old_password(self, value):
user = self.context['user']
if not user.check_password(value):
raise serializers.ValidationError(
_('Your old password was entered incorrectly. Please enter it again.')
)
return value
def validate(self, data):
if data['new_password1'] != data['new_password2']:
raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")})
v_passwords(data['new_password1'], self.context['user'])
return data
def save(self, **kwargs):
password = self.validated_data['new_password1']
user = self.context['user']
user.set_password(password)
user.save()
return user
I used #Pedro's answer to configure the UserPwdChangeSerializer
With this implementation you'll have a fully functional ModelViewSet for all fields updating and user creation as well as an action for password updating, in which you'll be able to use old password and validate that the new password was inputted correctly twice.
The custom password change will be created inside the url path you use for your users which might be something like:
api/users/<user_pk>/upassword
EDIT: Use capcha or something similar to escape from brute forces...
I did it with my own hacky way!
Might not the best way, but I found it better to understand,,,
**
Feel free to ask if anything seems to be a bouncer and I always encourage questions and feed backs...
**
I created a model for it.
class PasswordReset(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
key = models.CharField(max_length=100)
timestamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Added urls like these...
urlpatterns = [
path("request/", password_reset_request),
path("confirm/", password_reset_confirm),
]
And here we have our views...
#api_view(["POST"])
#permission_classes([AllowAny])
def password_reset_request(request):
# checking username
queryset = User.objects.filter(username=request.POST.get("username"))
if queryset.exists():
user = queryset.first()
else:
return Response({"error": "User does not exists!"})
# Checking for password reset model
queryset = PasswordReset.objects.filter(user=user)
if queryset.exists():
password_reset = PasswordReset.first()
# checking for last password reset
if password_reset.timestamp < timezone.now() - timedelta(days=1):
# password is not recently updated
password_reset.delete()
password_reset = PasswordReset(
user=user,
key="".join(
[choice("!#$_-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") for i in range(99)]
),
)
password_reset.save()
# send email here
subject = "Password reset request"
message = """To reset your password, go to localhost:8000/password_reset/{}""".format(password_reset.key)
from_email = "notes#frozenmatrix.com"
recipient_list = [user.email]
auth_user = "digital.mechanics.00#gmail.com"
auth_password = "mechanicsareawesomeagain"
send_mail(subject, message, from_email, recipient_list, auth_user=auth_user, auth_password=auth_password)
else:
# recent password updated
return Response({"error": "Your password was updated recently, wait before updating it again."})
#api_view(["POST"])
#permission_classes([AllowAny])
def password_reset_confirm(request):
# checking key
queryset = PasswordReset.objects.filter(key=request.POST.get("key"))
if queryset.exists():
password_reset = queryset.first()
if password_reset.timestamp < timezone.now() - timedelta(minutes=30):
# expired
return Response({"error": "Password reset key is expired! Try fresh after some hours."})
else:
# valid
password = request.POST.get("password", "")
if password == "":
# valid key and waiting for password
return Response({"success": "Set a new password"})
else:
# seting up the password
user = password_reset.user
user.set_password(password)
user.save()
return Response({"success": "Password updated successfully."})
else:
# invalid key
return Response({"error": "Invalid key"})

Categories