How fix test for change password view (Django REST Framework)? - python

In view, the test does not take the request.data as an element of DRF.
I have this view:
class ChangePasswordView(UpdateAPIView):
serializer_class = ChangePasswordSerializer
model = UserInfo
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
return Response(status=200)
def put(self, request, *args, **kwargs):
self.object = self.request.user
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
if not self.object.check_password(serializer.data.get("old_password")):
return Response(
{"old_password": ["Wrong password"]},
status=400
)
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
return Response("Success", status=200)
return Response(serializer.errors, status=400)
and I have this test method:
def test_change_password(self):
client = APIClient()
self.client.post('/api/user/login/', self.data, follow=True)
self.client.login(username="testuser", password="secret")
request = client.put('/api/user/change_password/', self.new_data)
self.assertEqual(
request, '<Response status_code=200, "application/json">'
)
where data:
def setUp(self):
self.data = {
'username': 'testuser',
'password': 'secret'
}
self.new_data = {
'old_password': 'secret',
'new_password': 'other_secret'
}
User.objects.create_user(**self.data)
Why does the test stop at reqest.data in serializer = self.get_serializer(data=request.data)?

I guess that you are getting the user wrong.
This might solve your issue:
from rest_framework import status
class ChangePasswordView(UpdateAPIView):
serializer_class = ChangePasswordSerializer
model = UserInfo
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
return Response(status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
user = request.user
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
if not self.object.check_password(serializer.data.get("old_password")):
msg = _("Wrong old password.")
raise ValidationError(msg)
user.set_password(serializer.data.get("new_password"))
user.save()
return Response("Success", status=status.HTTP_200_OK)
msg = _('Something went wrong while reading the passwords')
raise ValidationError(msg)
I am using the imported status from rest_framework in order to handle the error codes more easily.
Is there any specific reason you are using put? I have changed it to post. When making this API call (change password), you should use post.
You can easily get the user with request.user and then use the variable user inside put function to set the password and save it.
Regarding the test case, this could be a help and/or work for you. If it doesn't work, you might need to change it a bit.
from rest_framework import status
def test_change_password(self):
client = APIClient()
self.client.post('/api/user/login/', self.data, follow=True)
self.client.login(username="testuser", password="secret")
response = self.post('/api/user/change_password/', self.new_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

Related

DRF Custom Update password got KeyError: 'request'

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

Refactoring views in Django REST framework

I am very new to Python and Django. I have this app that returns 4 different types of transport routes (In the code I only showed two, cause they basically are the same...).
These 4 views use the same class-based views, but only the models' names are different. As they all return the same functionality(get, post, put and delete) I ended up repeating the same code over and over again.
Is there any way I can refactor it simpler?
Any help is appreciated! Thank you :)
views.py
********* tube view ***********
class TubeListView(APIView):
def get(self, _request, format=None):
tubeRoutes = TubeRoute.objects.all()
serialized_with_user = NestedTubeRouteSerializer(tubeRoutes, many=True)
return Response(serialized_with_user.data)
def post(self, request, format=None):
request.data['traveler'] = request.user.id
serializer = TubeRouteSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
class TubeDetailView(APIView):
def get(self, _request, pk, format=None):
tubeRoute = TubeRoute.objects.get(pk=pk)
serialized_with_user = NestedTubeRouteSerializer(tubeRoute)
return Response(serialized_with_user.data)
def put(self, request, pk, format=None):
request.data['traveler'] = request.user.id
tubeRoute = self.get_object(pk)
if tubeRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
updated_serializer = TubeRouteSerializer(tubeRoute)
if updated_serializer.is_valid():
updated_serializer.save()
return Response(updated_serializer.data, status=status.HTTP_200_OK)
return Response(updated_serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
def delete(self, request, pk, format=None):
tubeRoute = self.get_object(pk)
if tubeRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
tubeRoute.delete()
return Response(status=status.HTTP_200_OK)
********* bus view ***********
class BusListView(APIView):
def get(self, _request, format=None):
busRoutes = BusRoute.objects.all()
serialized_with_user = NestedBusRouteSerializer(busRoutes, many=True)
return Response(serialized_with_user.data)
def post(self, request, format=None):
request.data['traveler'] = request.user.id
serializer = BusRouteSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
class BusDetailView(APIView):
def get(self, _request, pk, format=None):
busRoute = BusRoute.objects.get(pk=pk)
serialized_with_user = NestedBusRouteSerializer(busRoute)
return Response(serialized_with_user.data)
def put(self, request, pk, format=None):
request.data['traveler'] = request.user.id
busRoute = self.get_object(pk)
if busRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
updated_serializer = BusRouteSerializer(busRoute)
if updated_serializer.is_valid():
updated_serializer.save()
return Response(updated_serializer.data, status=status.HTTP_200_OK)
return Response(updated_serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
def delete(self, request, pk, format=None):
busRoute = self.get_object(pk)
if busRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
busRoute.delete()
return Response(status=status.HTTP_200_OK)
You should take a look to these class based views in DRF.
For instance, the following code should be enough to replace your first TubeListView:
from rest_framework import generics
class TubeListView(generics.ListCreateAPIView):
queryset = TubeRoute.objects.all()
serializer_class = NestedTubeRouteSerializer
def post(self, request, *args, **kwargs):
request.data['traveler'] = request.user.id
return super().post(self, request, *args, **kwargs)
If you don't need any special behavior, you don't have to redefine get, post, etc methods. But if you need to change data, for instance in your POST method, you can do your stuff and then call the usual behavior of the superclass with super().post(self, request, *args, **kwargs)
I would recommend checking Viewsets in Django Rest Framework, specifically ModelViewset.
The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy() which coincide with the following:
get ----> .retrieve()
list ----> .list()
post ----> .create()
patch ----> .partial_update()
put ----> .update()
delete ----> .destroy()
Let me provide a case using the Bus Views
The Modelviewset form would be:
class BusViewset(ModelViewset):
queryset = BusRoute.objects.all()
serializer_class = NestedBusRouteSerializer
def get_queryset(self):
return self.queryset.filter(
owner__id=self.request.user.id
)
For the permission checks you implemented, Django Rest Framework has a Permissions System that takes care of that. For you own use case, a custom permission would be sufficient.

How to get user object from request in django rest framework?

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)

I return Response with `status=HTTP_404_NOT_FOUND` but find the 201

In the views.py, I return Response with status=HTTP_404_NOT_FOUND:
class CloudServerCreateAPIView(CreateAPIView):
serializer_class = CloudServerCreateSerializer
permission_classes = []
queryset = CloudServer.objects.all()
def perform_create(self, serializer):
return Response(data="There is no data left.", status=HTTP_404_NOT_FOUND, exception=Exception())
serializer.save()
But when I request the API, I get HTTP 201 Created, not the 404:
POST /api/user_productmanage/cloudserver/create/
HTTP 201 Created
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
"expiration_time": "2017-12-11T11:11:11+08:00",
"profile": "asdas",
"buytime": 1,
"availablearea": 2
}
Your 404 logic should not live in the perform_create. Perform create is mainly used to inject data you calculate elsewhere or user specific.
For instance serializer.save(user=self.request.user, date_time=datetime.now()).
Try moving your logic to create or post method. My suggestion would be to move it to post.
class CloudServerCreateAPIView(CreateAPIView):
serializer_class = CloudServerCreateSerializer
permission_classes = []
queryset = CloudServer.objects.all()
def create(self, request, *args, **kwargs):
# Your reason for 404
# if reason:
# # return Response(status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(data=request.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)
def post(self, request, *args, **kwargs):
# Your reason for 404
if reason:
return Response(data="There is no data left.", status=HTTP_404_NOT_FOUND, exception=Exception())
return self.create(request, *args, **kwargs)

Django update api

I ma creating a user profile update api via django:
in urls:
url(r'^/api/users/(?P<user_id>[0-9]+)$', UserView.as_view(), name='user_profile'),
And my view:
class UserView(APIView):
def patch(self, request, user_id):
# logging.info('user Id: %s' % user_id)
logging.info('in patch...')
user = User.objects.get(id=user_id)
serializer = UserSerializer(user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
why patch def not called at all?! (I get 504 method not allowed)
my request is:
patch > http://localhost:8000/api/users/2
When i'm deleting the user_id argument in view, it works, but i need to get the user id in path.
def patch(self, request, user_id):
# logging.info('user Id: %s' % user_id)
logging.info('in patch...')
user = User.objects.get(id=2)
serializer = UserSerializer(instance=user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
You must provide instance eq: serializer = UserSerializer(instance=user, data=request.data, partial=True)
try this.
class UserView(APIView):
def patch(self, request, *args, **kwargs):
# try to get user_id from kwargs.get('user_id', None)

Categories

Resources