I have a separate serializer for updating a user's account, and for some reason whenever I try to use an invalid input its not sending the validation errors as a response, its only sending back the original values that were set. Eg. old username is abc123, if I try to update it to abc123* i want it to throw an error saying its not a proper format but instead it just sends back abc123 as serializer.data. Anybody know why this is happening?
serializer
class UpdateAccountSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=16)
full_name = serializers.CharField(max_length=50)
class Meta:
model = Account
fields = ['username', 'full_name']
def validate_username(self, username):
if Account.objects.filter(username=username).exists():
raise serializers.ValidationError(_("This username is taken."))
if not re.fullmatch(r'^[a-zA-Z0-9_]+$', username):
raise serializers.ValidationError(
_("Usernames must be alphanumeric, and can only include _ as special characters."))
return username
def validate_full_name(self, full_name):
if not re.fullmatch(r'^[a-zA-Z ]+$', full_name):
raise serializers.ValidationError(
_("Invalid name."))
return full_name
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.full_name = validated_data.get(
'full_name', instance.full_name)
instance.save()
return instance
view
class UpdateAccountView(APIView):
def patch(self, request, pk, format=None):
account = Account.objects.filter(id=pk)
if account.exists():
account = account[0]
if request.user == account:
serializer = UpdateAccountSerializer(
account, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer._errors, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_404_NOT_FOUND)
If you're wondering why I'm using separate serializers for registration and updates, its because these fields should be the only ones users are allowed to update freely.
My best guess is its because of these lines (below), but I can't find a fix.
instance.username = validated_data.get('username', instance.username)
instance.full_name = validated_data.get('full_name', instance.full_name)
Your possible solution is to raise-exception. change this lines
if serializer.is_valid():
serializer.save()
to
if serializer.is_valid(raise_exception=True):
serializer.save()
And also don't need to overwrite update method in serializer.
In validated_data.get('username', instance.username) line we are doing from validate_data dictionary object get username if its not present then set this value which is instance.username. Btw one more thing patch request you should response with status code 200-ok not 201-created.
Related
I'm trying to update user info with a serializer, but even if I don't change the user's username or email when submitting, I get the validating error mention in the title. I just want to update info like the first name for example, but if I change its user name into an already existing one, then I want the error to pop up.
Serializer.py
class UpdateUserSerializer(serializers.ModelSerializer):
class Meta:
model = UserAccount
fields = ['username', 'email', 'first_name', 'last_name', 'about', 'password']
extra_kwargs = {"password": {"write_only": True}}
def validate_password(self, value):
user = self.context['request'].user
if not user.check_password(value):
raise serializers.ValidationError('Incorrect Password')
def update(self, instance, validated_data):
instance.username = validated_data.get("username", instance.username)
instance.email = validated_data.get("email", instance.email)
instance.first_name = validated_data.get("first_name", instance.first_name)
instance.last_name = validated_data.get("last_name", instance.last_name)
instance.about = validated_data.get("about", instance.about)
instance.save()
return instance
Views.py
#api_view(['PATCH'])
#permission_classes([IsAuthenticated])
def update_user(request):
if request.method == 'PATCH':
user = request.user
serializer = UpdateUserSerializer(data=request.data, context={'request': request})
data = {}
if serializer.is_valid():
user = serializer.update(user, request.data)
data['user'] = UserSerializer(user).data
else:
data = serializer.errors
return Response(data)
You have to pass the instance to UpdateUserSerializer when initializing it, otherwise is_valid() will think that you are creating a new object, hence the error.
So you can do something like:
#api_view(['PATCH'])
#permission_classes([IsAuthenticated])
def update_user(request):
if request.method == 'PATCH':
user = request.user
serializer = UpdateUserSerializer(
data=request.data, instance=user, context={'request': request}
)
data = {}
if serializer.is_valid():
user = serializer.save()
data['user'] = UserSerializer(user).data
else:
data = serializer.errors
return Response(data)
save() will call update() for you when there is an instance in the serializer so no need to explicitly call it.
When I want to update password received error,
user = self.context['request'].user
KeyError: 'request'
Can someone help me with it?
serializer:
Custom serializer
class UserPasswordChangeSerializer(serializers.Serializer):
old_password = serializers.CharField(required=True)
password = serializers.CharField(required=True)
class Meta:
model = User
fields = ('old_password', 'password')
def validate_old_password(self, data):
user = self.context['request'].user
if not user.check_password(data):
raise serializers.ValidationError(
{'old_password': 'Wrong password.'}
)
return data
def update(self, instance, validated_data):
instance.set_password(validated_data['password'])
return super().update(instance)
My action view
My action view
#action(methods=['patch'], detail=True)
def change_password(self, request, *args, **kwargs):
user = self.get_object()
user.serializer = UserPasswordChangeSerializer(data=request.data)
user.serializer.is_valid(raise_exception=True)
user.serializer.save()
return Response(status=status.HTTP_204_NO_CONTENT)
You need to pass request as context variable to the serializer
#action(methods=['patch'], detail=True)
def change_password(self, request, *args, **kwargs):
user = self.get_object()
user.serializer = UserPasswordChangeSerializer(data=request.data,
context={'request': request})
user.serializer.is_valid(raise_exception=True)
user.serializer.save()
return Response(status=status.HTTP_204_NO_CONTENT)
Reference: Including extra context in Serializer
I'm writing a rest api using Django Rest Framework, I have an endpoint to create objects on POST method and this method is overridden in order to allow bulk adding. However, the object is an "intermediate table" between Pacient and Symptoms and in order to create it I need to provide the pacient object or id and the same for the symptom. I get the Symptom id in the request, so that's not an issue, however the pacient is the authenticated user (who's making the request). Now, how do I edit the create method in the serializer in order to do that?
Here's my view:
class PacienteSintomaViewSet(viewsets.ModelViewSet):
serializer_class = SintomaPacienteSerializer
queryset = SintomaPaciente.objects.all()
permission_classes = (IsAuthenticated, )
http_method_names = ['post', 'get']
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many)
if serializer.is_valid():
sintomas_paciente_lista = [SintomaPaciente(**data) for data in serializer.validated_data]
print(serializer.validated_data)
SintomaPaciente.objects.bulk_create(sintomas_paciente_lista)
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
And this is my serializer:
class SintomaPacienteSerializer(serializers.ModelSerializer):
def create(self, validated_data):
sintoma_paciente = SintomaPaciente.objects.create(
sintoma_id=self.validated_data['sintoma_id'],
paciente_id=THIS NEEDS TO BE FILLED,
data=self.validated_data['data'],
intensidade=self.validated_data['intensidade'],
)
return sintoma_paciente
class Meta:
model = SintomaPaciente
fields = ('id', 'sintoma_id', 'paciente_id', 'intensidade', 'data',
'primeiro_dia', 'ativo')
There is two way.
First one, you can pass your user to serializer inside context, and use it in serializer:
in your view:
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many,context={'user':request.user})
in your serializer you can access this user with self.context['user']
Second way, you don't need to pass user to serializer again. Also If you already override the create method in your View, you don't need to override create method in serializer. I think it is wrong logically. Anyway, you can use your user when create object in view:
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = SintomaPacienteSerializer(data=request.data, many=many)
if serializer.is_valid():
sintomas_paciente_lista = [SintomaPaciente(**data,paciente_id=request.user.id) for data in serializer.validated_data]
print(serializer.validated_data)
....
I want to get the password of curent user from request body and verify it.
I am trying to get user object from request like this:
class UserPasswordUpdateAsAdminViewSet(APIView):
def patch(self, request, pk):
serializer = ResetPasswordAsAdminSerializer(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
serializer.update_password()
return Response(status=status.HTTP_200_OK, data={'success': True})
and below is my serializer:
class ResetPasswordAsAdminSerializer(serializers.ModelSerializer):
new_password = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True)
def validate_password(self, attrs):
self.user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
self.user = request.user
if not self.user or not self.user.check_password(attrs['password']):
raise CustomAPIException(
status_code=status.HTTP_401_UNAUTHORIZED,
message='Invalid password provided',
error_code=None
)
def validate_new_password(self, password):
if not re.match(REGEX['password'], str(password)):
raise CustomAPIException(
status_code=status.HTTP_409_CONFLICT,
message=None,
error_code='password_policy_mismatch'
)
return password
def update_password(self):
self.user.set_password(self.validated_data['new_password'])
self.user.save()
return
class Meta:
model = User
fields = ('password', 'new_password')
But on hitting the api, I am getting the following error:
string indices must be integers
What am I doing wrong?
You don't have to do this in the serializer itself, it can be done in the views pretty easily take a look at this example
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
if not user.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
user.set_password(serializer.data.get('new_password'))
user.save()
return Response({'status': 'password set'}, status=status.HTTP_200_OK)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
My serializer fun is
class change_pwdform(serializers.ModelSerializer):
def update(self, instance, validated_data):
user.set_password(validated_data.get('new_password', new_password))
user.save()
return instance
old_password = serializers.CharField( style={'input_type': 'password'})
new_password = serializers.CharField( style={'input_type': 'password'})
class Meta:
fields = ( 'pty_email','old_password','new_password',)
model = registration
in my model i have only the filed called
password field in model
my view function to change password is,
class Change_Password(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
serializer_class = change_pwdform
def post(self, request, format=None):
email = request.data['pty_email']
pwd = request.data['old_password']
user = registration.objects.filter(pty_email=email, pty_password=pwd)
if user:
user = registration.objects.get(pty_email=email)
request['password'] = request.data['new_password']
print request.data //says quickdict of email , password , new_password, and old_password
serializer = change_pwdform(object, data=request.DATA, partial=True)
if serializer.is_valid():
serializer.save()
data = { "result":"success"}
return Response(data)
error ={"Invalid"}
return Response(error, status=status.HTTP_400_BAD_REQUEST)
This request return a dict ack: success but the password is not yet changed.
Thanks in advance..
You can wright an update function in your serializers like this.Following
def update(self, instance, validated_data):
user.set_password(validated_data.get('new_password', new_password))
user.save()
return instance
Here user is the user object and in your views.py you have to pass the model object like this
change_pwdform = change_pwdform(object, data=request.DATA, partial=True)
This worked for me and you can also refer "http://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-update"