I have made a model with its own password field. This is wholly separate from the User object. I'm using the django.contrib.auth.hashers library for this.
In the create method for this model (overwriting a generic CreateListAPI view)
def create(self, request, *args, **kwargs):
data = request.data
data['password'] = make_password(data['password'])
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
This stores a hashed password in my database as expected, but when I try to authenticate with the password
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
try:
# Grabs the 'name' parameter from the URL
obj = queryset.get(name=self.kwargs['name'])
except Group.DoesNotExist:
raise Http404
print(self.request.data['password']) # raw password string
print(obj.password) # encoded password from database
if check_password(self.request.data['password']), obj.password):
raise Http404
obj.user_set.add(self.request.user)
self.check_object_permissions(self.request, obj)
return obj
check_password returns False. However, passing in the encoded password as the raw string password works. So hashing the password works, but not comparing the raw password to it after the fact.
if not check_password(self.request.data['password'], obj.password):
raise Http404
change the line to the above code
Related
I have a user update api that is supposed to update the user's details. I have used set_password() to encrypt the password and from my print statements, it seems to encrypt fine. However when I save the updated user, the password still saves as plain text. What am I missing here ?
print(user.password) gives me
pbkdf2_sha256$216000$26YKhuRQ4i4S$HnfbowEjappYtP7nbbMZJXcjLi13sWPpj1EqjEbUutI=
Yet when I return user it maintains the password as plain text.
class UserDetail(APIView):
def get_user(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
def put(self, request, pk, format=None):
user = self.get_user(pk)
data = request.data
new_password = data["password"]
user.set_password(new_password)
user.save()
print(user.password)
serializers = UserSerializer(user, request.data)
if serializers.is_valid():
serializers.save()
return Response(serializers.data)
else:
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)
So I have this view, I passed the request through the context to the serializer so I can use it to get the user
def create(self, request, *args, **kwargs):
""" Handle member creation from invitation code. """
serializer = AddMemberSerializer(
data=request.data,
context={'circle': self.circle, 'request': request}
)
serializer.is_valid(raise_exception=True)
member = serializer.save()
data = self.get_serializer(member).data
return Response(data, status=status.HTTP_201_CREATED)
In the serializer I did this but it does not work,I get "KeyError: 'user'", I ran a debugger and when I tried to call the get_user method it says "TypeError: get_user() missing 1 required positional argument: 'obj'"
user = serializers.SerializerMethodField()
def get_user(self, obj):
request = self.context.get('request', None)
if request:
return request.user
So what am I missing? I looked up other implementations of this field and none of them seem very different of mine, so I would really apreciate it if someone explains to me why it is not working.
Also if there is a more efective way to get the user into a field (Need it to run a user_validate method on it)
Try:
user = serializers.SerializerMethodField()
def get_user(self, obj):
return obj.user_id
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
The issue here is easy to fix and the reason I open it is that I don't understand what's happening.
Here's the code from django-rest-auth:
class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.
Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# Create a serializer with request.data
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
It's the whole class and it surely works well.
I make a copy with a small change - instead of serializer.is_valid(raise_exception=True) I expand it:
if serializer.is_valid():
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
else:
return Response(
{"error": "Something's wrong."},
status=status.HTTP_400_BAD_REQUEST
)
When I run it I get
*AssertionError: 'PasswordResetView' should either include a `queryset` attribute, or override the `get_queryset()` method.*
The only difference is in using serializer.is_valid(raise_exception=True) (Ok) or just serializer.is_valid() (fail).
Can somebody explain it to me? It's not the first time I see such an issue.
Of course I can write an empty def get_quesryset(): return None to fix it.
But the original code doesn't have get_queryset or get_object or anything and works fine.
Thanks!
Edit: here's my code, so that you can see it's really the same as original.
class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.
Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# Create a serializer with request.data
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
else:
return Response(
{"error": "Something's wrong."},
status=status.HTTP_400_BAD_REQUEST
)
I've been struggling to pass some extra data form my Form class to my views. For a password recovery an user has to fill in an username or e-mail address. When cleaning, the username and password are checked if one of them exists in the database:
def clean(self):
username = self.cleaned_data.get("username")
email = self.cleaned_data.get("email")
if username:
try:
user = User.objects.get(username__iexact=username, is_active=True) # <- to view
except User.DoesNotExist:
raise forms.ValidationError(
self.error_messages["invalid_username"],
code="invalid_username"
)
elif email:
try:
user = User.objects.get(email__iexact=email, is_active=True) # <- to view
except User.DoesNotExist:
raise forms.ValidationError(
self.error_messages["invalid_email"],
code="invalid_email"
)
else:
raise forms.ValidationError(
self.error_messages["empty_form"],
code="empty_form"
)
return self.cleaned_data
When the form has been validated, I want to send the user data to the view. This to separate the send_email logics away from the form and being able to add some information to the context, so it can be rendered in the template.
So in my FormView, if the form is valid, I want to be able to use the user object retrieved in the Form.
Currently I have attempted quite some 'answers' of other SO questions and examples of the web. But I keep getting AttributeErrors, KeyErrors, 'WSGIRequest' object does not support item assignment.
The last attempt I made was to overwrite the init in my Form, and the get_form_kwargs in my view:
Form
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(RecoverPasswordForm, self).__init__(*args, **kwargs)
def clean(self):
....
self.request["user"] = User.objects.get(username__iexact=username, is_active=True)
View
def get_form_kwargs(self, **kwargs):
kwargs = super(RecoverPassword, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
Which leads to the following error
'WSGIRequest' object does not support item assignment
Can somebody give me an explanation of what I'm doing wrong, and push me in the right direction to solve this problem? Thanks in advance!
From what I understand, I think you are trying to send an email with reset password link to the user from your view after validating the username/email in your form. Next to add some info to the context, so it can be rendered in the template.
So this can done by overriding the form_valid method in your class like:
def form_valid(self, form):
username = form.cleaned_data['username']
email = form.cleaned_data['email']
if username: user = User.objects.get(username__iexact=username, is_active=True)
else: user = User.objects.get(email__iexact=email, is_active=True)
send_email(user)
data = {'info':'give your info here'}
render(request, 'success.html', data)
Learn more here
Update:
to can access the user object from the form do like:
def clean():
...
self.cleaned_data["user"] = User.objects.get(username__iexact=username, is_active=True)
In your form_valid
def form_valid(self, form):
user = form.cleaned_data['user']
send_email(user)
data = {'info':'give your info here'}
render(request, 'success.html', data)