I'm now making user profile update API using drf with RetreiveUpadteAPIView
there is one question that i can't figure out what the solution is.
This is what i want. I wanna update password and update other user data once for all.
changing password is worked as well as i supposed but other user datas (nick_name', 'wannabe', 'profile_img') are not save on DB through this logic below.
When i do self.object.set_password(), self.object.save() first and then do perform_update after. then user datas are well updated on DB. but password is saved without hashed even if i do set_password which is make the password hashed.
How can i fix it..
your best regard
Here is my code below.
#views.py
#permission_classes([IsAuthenticated])
class UpdatePartialUserView(RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserProfileSerializer
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
obj = queryset.get(pk=self.request.user.id)
self.check_object_permissions(self.request, obj)
return obj
def retrieve(self, request, *args, **kwargs):
serializer = UserSerializer(request.user)
return Response(status=status.HTTP_200_OK, data = serializer.data)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object()
serializer = self.get_serializer(request.user, data = request.data, partial=partial)
# serializer = self.get_serializer(self.object, data = request.data, partial=partial)
if not serializer.is_valid(raise_exception=True):
return Response(status=status.HTTP_409_CONFLICT, data = {'message':serializer.errors})
self.perform_update(serializer=serializer)
#make password hashed
self.object.set_password(request.data['password'])
self.object.save()
return Response(status=status.HTTP_202_ACCEPTED, data={"message": "success!"})
#serializers.py
class UserProfileSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
password2 = serializers.CharField(write_only=True, required=True)
old_password = serializers.CharField(write_only=True, required=True)
profile_img = serializers.ImageField(use_url=True, required = False)
def validate(self, attrs):
if attrs.get('password') != attrs.get('password2'):
raise serializers.ValidationError({
"password" : "passwords are not paired."})
return attrs
def validate_old_password(self, value):
#check user
request = self.context.get('request')
if request and hasattr(request, "user"):
user = request.user
if not user.check_password(value):
raise serializers.ValidationError({
"old_password" : "Old password is not correct."
})
return value
class Meta:
model = User
fields = ['nick_name', 'wannabe', 'old_password', 'password', 'password2', 'profile_img']
I think something like that should work.
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object()
serializer = self.get_serializer(request.user, data = request.data, partial=partial)
if not serializer.is_valid(raise_exception=True):
return Response(status=status.HTTP_409_CONFLICT, data = {'message':serializer.errors})
self.object.set_password(new_password)
self.object.update(
nick_name=serializer.data["nickname"],
wannabe=serializer.data["wannabe"],
profile_img=serializer.data["profile_img"],
)
self.object.save()
return Response(status=status.HTTP_202_ACCEPTED, data={"message": "success!"})
Related
I was trying to implement password change based on some recommendations on stackoverflow but something was not working which is really weird for me. After troubleshooting I got idea data the my view is not working. Changing serializer actions to print I see data "serializer.data" is result "{}" even though "print(serializer)" give result.
serializers.py
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(
{
"old_passowrd": _(
"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.")}
)
validate_password(data["new_password1"], self.context["request"].user)
return super().validate(data)
views.py
class ChangePassword(UpdateAPIView):
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
def get_object(self, queryset=None):
return self.request.user
def update(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
# user.set_password(serializer.data.get("new_password1"))
# user.save()
print(serializer.data)
return Response("Success", status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
'write_only=True' fields are not included when serializing the representation. If you want to debug you can print request.data or remove parameter write_only or set it False.
https://www.django-rest-framework.org/api-guide/fields/
I have a registration page that allows a user to sign up. After doing so, I want to call an API and then, save the data to my model (not saving it to a form though). I tried doing this:
models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE, primary_key=True, related_name = 'profile')
address = models.TextField()
birthday = models.DateField()
def __str__(self):
return str(self.user)
views.py:
def signup(request):
if request.method == 'POST':
user_form = UserForm(request.POST)
register_form = RegisterForm(request.POST)
if user_form.is_valid() and register_form.is_valid():
username = user_form.cleaned_data.get('username'),
first_name = user_form.cleaned_data.get('first_name'),
last_name=user_form.cleaned_data.get('last_name'),
email=user_form.cleaned_data.get('email'),
password=user_form.cleaned_data.get('password2'),
birthday = register_form.cleaned_data.get('dob'),
address=register_form.cleaned_data.get('address'),
payload = {'username': username,'first_name': first_name,'last_name': last_name,'email':email,'password':password,'register' : {'birthday': birthday,'address': address}}
response = requests.post('http://127.0.0.1:8000/my_api/',json=payload)
return redirect("home") #re-direct if login is successful
else:
user_form = UserForm()
register_form = RegisterForm()
return render(request, 'users/register.html', {'user_form': user_form, 'register_form': register_form})
class RegisterAPI(APIView):
permission_classes = [AllowAny]
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
content = {'status': 'You are registered'}
return Response(content, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py:
from users.models import Profile
from django.contrib.auth.models import User
class ProfileSerializer(serializers.ModelSerializer):
birthday = serializers.DateField(format="%Y-%m-%d")
class Meta:
model = Profile
fields = ('birthday','address')
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username','first_name','last_name','email', 'password', 'profile')
def create(self, request, validated_data, *args, **kwargs):
register_data = validated_data.pop('profile')
password = validated_data.pop('password', None)
user = User.objects.create(**validated_data)
if password is not None:
user.set_password(password)
user.save()
Profile.objects.create(user = user, **register_data)
return validated_data
However, I am getting this error:
Object of type data is not JSON serializable error in Django
It seems that it's got to do with the birthday. On my template, a user can display the date of birth as 'YYYY-MM-DD'. How can I fix this error?
The create method in your UserSerializer should return a User instance instead of validated_data.
def create(self, request, validated_data, *args, **kwargs):
register_data = validated_data.pop('profile')
password = validated_data.pop('password', None)
user = User.objects.create(**validated_data)
if password is not None:
user.set_password(password)
user.save()
Profile.objects.create(user = user, **register_data)
return user
I have such view for user change password:
class ChangePasswordView(generics.UpdateAPIView):
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
def put(self, request, *args, **kwargs):
data = request.data.copy()
data['user'] = self.request.user
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
user.set_password(serializer.validated_data["new_password"])
user.save()
return Response(status=status.HTTP_204_NO_CONTENT)
And serializer for this view looks like this:
class ChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField()
new_password = serializers.CharField()
new_password_retyped = serializers.CharField()
def validate(self, data):
old_password = data.get('old_password')
new_password = data.get('new_password')
new_password_retyped = data.get('new_password_retyped')
user = data.get('user')
# misc validation checks
data['user'] = user
return data
and my problem is that user object is not being passed to serializer, tried printing it to see content of data inside put:
<QueryDict: {'old_password': ['testpassword'], 'new_password': ['testpassword1'], 'new_password_retyped': ['testpassword1'], 'user': [<User: root>]}>
and inside serializer:
OrderedDict([('old_password', 'testpassword'), ('new_password', 'testpassword1'), ('new_password_retyped', 'testpassword1')])
As you can see, user is missing. First, I thought that it may have something to do with passing object to serializer so I changed data['user'] = self.request.user to data['user'] = self.request.user.username so it would only pass string with username, but with no luck
You cannot pass user to serializer this way because serializers drop data which is not relevent. Try doing something like this.
class ChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField()
new_password = serializers.CharField()
new_password_retyped = serializers.CharField()
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
def validate(self, data):
old_password = data.get('old_password')
new_password = data.get('new_password')
new_password_retyped = data.get('new_password_retyped')
user = self.user
# misc validation checks
data['user'] = user
return data
And pass user to serializer separately.
self.get_serializer(data=data, user=self.request.user)
I have a RegisterForm that inherits from ModelForm with RegisterView that inherits from FormView. If every field data is valid, the user gets successfully created and is redirected to login page. But if there is a validation error, it shows the field error below that field and the form gets refreshed and all the fields data is lost. How to avoid form refreshing so that user need not to fill the details again and again.
forms.py
class RegisterForm(forms.ModelForm, PasswordValidatorMixin):
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField( label='Confirm password', widget=forms.PasswordInput)
class Meta:
model = UserModel
fields = (
'first_name',
'last_name',
'username',
'password1',
'password2',
'current_email',
)
def __init__(self, social_email=None, social_fname=None, social_lname=None,
social_uname=None,*args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
self.current_email = None
self.social_email = social_email
self.social_fname = social_fname
self.social_lname = social_lname
self.social_uname = social_uname
def clean(self, *args, **kwargs):
username = self.cleaned_data.get('username')
self.current_email = self.cleaned_data.get('current_email')
if self.social_email:
self.current_email = self.social_email
if not username:
raise forms.ValidationError({"username":"Username can't be empty"})
if not self.current_email:
raise forms.ValidationError({"current_email":"Email can't be empty"})
qs = UserModel.objects.filter(username=username)
qs_email = UserModel.objects.filter(current_email=self.current_email)
if qs.exists():
raise forms.ValidationError({"username":"Username is already taken"})
if qs_email.exists():
raise forms.ValidationError({"current_email":"Email has already been registered"})
return self.cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
current_email = self.cleaned_data.get('current_email')
password = self.cleaned_data.get('password1')
user.set_password(password)
if self.social_email:
user.is_active = True
user.save()
return user
views.py
class RegisterView(ContextMixin, FormView):
form_class = RegisterForm
template_name = 'accounts/register.html'
title = 'Register'
#method_decorator(sensitive_post_parameters('password'))
#method_decorator(csrf_protect)
#method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
self.kwargs['social_email'] = SOCIAL_USER_EMAIL
self.kwargs['social_fname'] = SOCIAL_USER_FNAME
self.kwargs['social_lname'] = SOCIAL_USER_LNAME
if SOCIAL_USER_EMAIL:
self.kwargs['social_uname'] = SOCIAL_USER_EMAIL.split('#',1)[0]
return super(RegisterView, self).dispatch(*args, **kwargs)
# Passes view kwargs to html
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if SOCIAL_USER_EMAIL:
context['social_email'] = self.kwargs['social_email']
context['social_fname'] = self.kwargs['social_fname']
context['social_lname'] = self.kwargs['social_lname']
context['social_uname'] = self.kwargs['social_uname']
# context['social_image'] = SOCIAL_USER_IMAGE
return context
# Passes view kwargs to form
def get_form_kwargs(self):
kwargs = super(RegisterView, self).get_form_kwargs()
kwargs.update(self.kwargs)
return kwargs
def form_valid(self, form):
form.save()
if not self.kwargs['social_email']:
return render(self.request, 'accounts/success.html', {
'title':"You've registered successfully",
'body':"You've successfully registered at antef! Please verify the link sent at " +
form.current_email
})
return render(self.request, 'accounts/success.html', {
'title':"You've registered successfully",
'body':"You've successfully registered with your " + self.kwargs['social_email'] + " account."})
First, you don't need validation error for empty inputs, just add required = True in your forms.py or in your model.
Second you are not returning anything after validation error, which making your form empty after refresh.
You can also check email and username separately, for better use,
def clean_email(self):
email = self.cleaned_data.get('email')
if your_condition:
raise forms.ValidationError()
return email
def clean_username(self):
username = self.cleaned_data.get('username')
if your_condition
raise forms.ValidationError
return username
I am using a updateapiview to the update my user information. This is my view
class UserUpdateView(generics.UpdateAPIView):
serializer_class = UserUpdateSerializer
def get_queryset(self):
print(self.kwargs['pk'])
return User.objects.filter(pk=self.kwargs['pk'])
def partial_update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, partial=True)
if self.get_object() != request.user:
return Response({'error': 'permission denied'}, status=status.HTTP_400_BAD_REQUEST)
print(request.data['password'])
request.data['password'] = make_password(request.data['password'])
instance = super(UserUpdateView, self).partial_update(request, *args, **kwargs)
return instance
This is my serializer
class UserUpdateSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
def validate_username(self, username):
if User.objects.filter(username=username):
raise serializers.ValidationError('Username already exists!!')
return username
def validate_email(self, email):
if User.objects.filter(email=email):
raise serializers.ValidationError('Email already exists!!')
return email
class Meta:
model = User
fields = ('pk', 'username', 'password', 'email')
read_only_fields = ('pk',)
I am getting the data updated and returned successfully. But I want to add some field like message with content 'successfully updated' on successful updation of the user profile. I searched if there is any way to perform this but can't find the appropriate way to do it (alike get_context_data method in django). So, is there any way to perform the above task ??
Question 2:
How to prevent the self user ( i.e if has a email sample#gmail.com and if user clicks udpate with the same email, it should not raise an error that username already exists, I guess this can be done with self.instance (but not sure how actually to implement it).
Thanks!
I'm not sure what version of DRF you are using but in the latest 3.9.0 UpdateAPIView uses UpdateModelMixin, which returns Response object instead of instance. So you can modify data before returning.
def partial_update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, partial=True)
if self.get_object() != request.user:
return Response({'error': 'permission denied'}, status=status.HTTP_400_BAD_REQUEST)
print(request.data['password'])
request.data['password'] = make_password(request.data['password'])
response = super(UserUpdateView, self).partial_update(request, *args, **kwargs)
response.data['message'] = 'Successfully updated'
return response
Regarding your second question you can exclude the current user using self.instance.
def validate_email(self, email):
if User.objects.filter(email=email).exclude(id=self.instance.id).exists():
raise serializers.ValidationError('Email already exists!!')
return email