I need for a project a simple backend with the following functionality (all must be done via an API since this will be accessed by another program):
User can not do anything if he is not authenticated
In order to get authenticated the user knows "DEFAULT USER" credentials
2.1 The user tries to log in with the "DEFAULT USER" credentials
2.2 The application creates a new User with random pass and username (or token) and returns it to the user so that he can later use those new credentials to authenticate with the server
Authenticated users will only be able to CREATE 1 entry in the DB or update the entry created by them.
I have been trying to do this for the past few days however, all the django tutorials I managed to find for DJANGO-REST-FRAMEWORK did not help me.
Currently this is what I got:
urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from api import views
from api.serializers import UserSerializer
urlpatterns = [
url(r'^api-classes/$', views.UserStatisticsList.as_view()),
url(r'^api/$', views.userStatistics_list),
url(r'^users/$', views.UserList.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
serializers.py:
from rest_framework import serializers
from api.models import UserStatistics
from django.contrib.auth.models import User
class UserStatisticsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = UserStatistics
fields = ('id','user', 'last_modified', 'statistics_json',)
def create(self, validated_data):
"""
Create and return a new `UserStatistics` instance, given the validated data.
"""
return UserStatistics.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `UserStatistics` instance, given the validated data.
"""
instance.last_modified = validated_data.get('last_modified', instance.last_modified)
instance.statistics_json = validated_data.get('statistics_json', instance.statistics_json)
instance.save()
return instance
class UserSerializer(serializers.ModelSerializer):
# userStat = serializers.PrimaryKeyRelatedField(many=False, queryset=UserStatistics.objects.all())
class Meta:
model = User
fields = ('id', 'username',)
# fields = ('id', 'username', 'userStat')
views.py:
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from api.models import UserStatistics
from api.serializers import UserStatisticsSerializer, UserSerializer
from rest_framework import permissions
#api_view(['GET', 'POST'])
def userStatistics_list(request, format=None):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
userStat = UserStatistics.objects.all()
serializer = UserStatisticsSerializer(userStat, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = UserStatisticsSerializer(data=request.data)
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)
from rest_framework import mixins
from rest_framework import generics
class UserStatisticsList(
mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
"""
List all UserStatistics, or create a new UserStatistics.
"""
queryset = UserStatistics.objects.all()
serializer_class = UserStatisticsSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# def get(self, request, format=None):
# userStat = UserStatistics.objects.all()
# serializer = UserStatisticsSerializer(userStat, many=True)
# return Response(serializer.data)
# def post(self, request, format=None):
# serializer = UserStatisticsSerializer(data=request.data)
# 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)
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
models.py:
from django.db import models
from jsonfield import JSONField
from django.contrib.auth.models import User
# Create your models here.
class UserStatistics(models.Model):
last_modified = models.DateTimeField(auto_now_add=True)
statistics_json = JSONField()
user = models.OneToOneField(User, related_name="creator")
class Meta:
ordering = ('last_modified','statistics_json',)
I also tried to implement a custom Authenticator which in my understanding is what gets called when an User wants to Authenticate ... :
import string
import random
from django.contrib.auth.models import User, check_password, ModelBackend
class SettingsBackend(ModelBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name, and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
"""
ANDROID_LOGIN_USERNAME = "android"
ANDROID_LOGIN_PASSWORD = "android"
def authenticate(self, username=None, password=None):
login_valid = (ANDROID_LOGIN_USERNAME == username)
pwd_valid = check_password(password, ANDROID_LOGIN_PASSWORD)
if login_valid and pwd_valid:
# try:
# user = User.objects.get(username=username)
# except User.DoesNotExist:
# return None
# Create a new user. Note that we can set password
# to anything, because it won't be checked; the password
# from settings.py will.
user = User(username=random_string_generator(), password=random_string_generator())
user.save()
return user
return None
def random_string_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def has_perm(self, user_obj, perm, obj=None):
if user_obj.username == settings.ANDROID_LOGIN_USERNAME:
return True
else:
return False
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Can someone please point out what to do next ?
Thank you
Related
Description:
The goal is to update all Spotlight fields on PUT/PATCH (update/partial update) if its status is YELLOW.
If status is RED || GREEN, it should update only its status and ignore any other fields. The workaround presented here is kind of smelly and it produces misleading responses when using PUT.
Is there any Django way to achieve this better than the presented workaround?
Workaround:
if instance.state == instance.STATE_YELLOW:
custom_data = request.data
else:
custom_data = {'state': request.data['state']}
Full code:
from stoplight.filters import StoplightFilter
from stoplight.models import Stoplight
from stoplight.permissions import (
IsSuperuserOrReadOnly
)
from stoplight.serializers import StoplightSerializer
from rest_framework import status
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
class StoplightViewSet(GenericViewSet, CreateModelMixin, ListModelMixin, RetrieveModelMixin):
"""
API endpoint for Stoplights
"""
queryset = Stoplight.objects.all()
serializer_class = StoplightSerializer
filter_class = StoplightFilter
search_fields = ('name',)
permission_classes = (IsSuperuserOrReadOnly,)
def update(self, request, *args, **kwargs):
"""
Updates Stoplight state
"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
if instance.state == instance.STATE_YELLOW:
custom_data = request.data
else:
custom_data = {'state': request.data['state']}
serializer = self.get_serializer(instance, data=custom_data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data, status=status.HTTP_200_OK)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
class ExampleDestroyView(DestroyAPIView):
serializer_class = PetSerializer
queryset = Pet.objects.all()
lookup_field = "object_id"
def perform_destroy(self, instance):
self.data = {}
self.data['status'] = True
approval()
self.data['msg'] = "It removed"
return self.data
Here is my Sample Class ..... In this I need to Delete an objet.... It's deleting
But I am unable to pass the following Dict As an OutPut
How can I pass the status and a message in a dictionary
Override the destroy(...) method
from rest_framework.generics import DestroyAPIView
from rest_framework.response import Response
from rest_framework import status
class ExampleDestroyView(DestroyAPIView):
serializer_class = PetSerializer
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
data = self.perform_destroy(instance)
return Response(data=data, status=status.HTTP_204_NO_CONTENT)
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
#api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
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)
I am trying to create Rest API using DRF. Wanted to get detail by using UniqueId. I can use PK and get the output but wanna use unique id (token_id in my jobs Model) created in the model field.
Models.py
from django.db import models
from rest_api.util import unique_slug_generator
from django.urls import reverse
# Create your models here.
class Jobs(models.Model):
token_id = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=100)
url = models.URLField()
environment = models.CharField(max_length=100, null=True)
runtestnow = models.BooleanField(default=False)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('token_id', kwargs={'token_id':self.token_id})
class Queue(models.Model):
tokenid = models.ForeignKey(Jobs, on_delete=models.CASCADE)
date = models.DateField(auto_now=True)
def __str__(self):
return self.tokenid
class VM(models.Model):
vm_count = models.IntegerField(default=120)
def __str__(self):
return f"VM Count: {self.vm_count}"
Urls.py
from django.urls import path, include
from . import views
from .views import (RegisterTestMethodView,
RegisterTestMethodViewDetail,
CheckStatusView,
ReleaseTokenView
)
from rest_framework import routers
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
router = routers.DefaultRouter()
router.register('jobs', views.JobsView)
urlpatterns = [
path('', include(router.urls)),
path('registertestmethod/', RegisterTestMethodView.as_view()),
path('registertestmethod/<int:pk>/',
RegisterTestMethodViewDetail.as_view()),
path('checkstatus/<int:pk>', CheckStatusView.as_view()),
path('checkstatus/<token_id>', CheckStatusView.as_view()),
path('releasetoken/<int:pk>', ReleaseTokenView.as_view()),
]
Serializers.py
from rest_framework import serializers
from .models import Jobs
from django.utils.crypto import get_random_string
class JobsSerializers(serializers.HyperlinkedModelSerializer):
token_id = serializers.CharField(default=get_random_string(length=25))
class Meta:
model = Jobs
fields = ('id', 'name', 'url','runtestnow','token_id')
class CheckStatusSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobs
fields = ('id','runtestnow')
class RegisterTestMethodSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobs
fields = ('id', 'name', 'url', 'environment', 'runtestnow', 'token_id')
Views.py
from rest_framework import viewsets, permissions, authentication
from .models import Jobs, VM, Queue
from .serializers import (JobsSerializers,
RegisterTestMethodSerializers,
CheckStatusSerializers)
import json
import datetime
import collections
collections.deque()
#3rd time
from rest_framework import generics
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authentication import (SessionAuthentication,
BasicAuthentication,
TokenAuthentication)
from rest_framework.permissions import IsAuthenticated
from django.utils.crypto import get_random_string
with open('greet/static/greet/static/config.json', 'r') as
data_config:
data_ready = json.load(data_config)
totalVM = int(data_ready['totalVM'])
max_vm = int(data_ready['max_vm_count'])
grid_name = (data_ready['GridNameForDev'])
min_vm = int(data_ready['min_vm_count'])
class RegisterTestMethodView(APIView):
# authentication_classes = [SessionAuthentication,
TokenAuthentication, BasicAuthentication]
# permission_classes = [IsAuthenticated] # No access (not even
read if not authorized)
def get(self, request):
snippets = Jobs.objects.all()
serializer = RegisterTestMethodSerializers(snippets,
many=True)
return Response(serializer.data)
def post(self, request):
queue = VM.objects.all()
id_token = get_random_string(length=25)
if not queue:
queue = VM(vm_count=totalVM)
queue.save()
else:
for queue_obj in queue:
queue = queue_obj
if queue.vm_count > min_vm:
queue.vm_count -= max_vm
queue.save()
request.data["token_id"] = id_token
request.data["runtestnow"] = True
else:
request.data["token_id"] = id_token
request.data["runtestnow"] = False
serializer = RegisterTestMethodSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'TokenId': serializer.data['token_id'],
'RunTestNow': serializer.data['runtestnow'],
'VmCount': queue.vm_count,
'GridName': grid_name, 'Vm_left':
queue.vm_count}, status=status.HTTP_201_CREATED)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
class JobsView(viewsets.ModelViewSet):
queryset = Jobs.objects.all()
serializer_class = JobsSerializers
lookup_field = 'token_id'
class CheckStatusView(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk, token_id):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def get(self, request, token_id):
pk = request.GET.get('pk')
print(pk)
queue = VM.objects.get()
job_list = Jobs.objects.exclude(runtestnow=True)
filtered = Jobs.objects.filter(id=pk)
next_q = job_list.order_by('id').first()
waitlist = 1
return Response(
{"tokenid": token_id, "Runtestnow": False, "VMcount":
queue.vm_count,
'GridName': grid_name, 'waitlist #': waitlist,
'Vm_left':
queue.vm_count}, status=status.HTTP_201_CREATED)
def post(self, request, pk):
queue = VM.objects.get()
vm_count = queue.vm_count
job_list = Jobs.objects.exclude(runtestnow=True)
filtered = Jobs.objects.filter(id=pk)
next_q = job_list.order_by('id').first()
waitlist = int(pk-next_q.id + 1)
if next_q:
print(next_q.id)
if next_q.id == pk and queue.vm_count > min_vm:
queue.vm_count -= max_vm
filtered.update(runtestnow=True)
queue.save()
vm_used = max_vm
else:
filtered.update(runtestnow=False)
queue.save()
vm_used = 0
snippet = self.get_object(pk)
serializer = RegisterTestMethodSerializers(snippet)
return Response({"tokenid": serializer.data["id"],
"Runtestnow": serializer.data['runtestnow'], "VMcount":
vm_used,
'GridName': grid_name, 'waitlist #': waitlist ,
'Vm_left': queue.vm_count},
status=status.HTTP_201_CREATED)
class ReleaseTokenView(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def delete(self, request, pk, format=None):
queue = VM.objects.get()
if not queue:
queue = VM(vm_count=totalVM)
if not self.get_object(pk):
print("Not Method Called...")
return
if queue.vm_count < totalVM :
queue.vm_count += max_vm
queue.save()
elif queue.vm_count + max_vm > totalVM:
queue.vm_count = totalVM
queue.save()
snippet = self.get_object(pk)
snippet.delete()
return Response(data={'Released': True},
status=status.HTTP_204_NO_CONTENT)
I can get information using but I wanna user token_id. I can do that using Serializers as it is in jobs.
If I do
localhost/jobs/xJcn8XxF2g9DmmwQwGS0Em754. # --> I get the output but I
# wanna use and I am aware
#that this will return all CRUD methods but how do I apply the
#business logic in Serializers.
localhost/checkstatus/xJcn8XxF2g9DmmwQwGS0Em754 . # --> I wanna
# apply business logic before getting the output. Which
# returns Response related to the PK as well.
What is the best way to do it?
Do I add it on serializer.py(how) or views.py?
I would appreciate it if you provide any helpful documents.
You should set lookup_field as token_id in your serializer and viewset.
Here is answer Django Rest Framework: Access item detail by slug instead of ID
Actually I was able to do it by some research. It seems like I have to pass a unique id (token_id) in URL and query using the same unique id (token_id) on the views.py. I was aware that there is modelviewset that does it effortlessly as mentioned by Ishak, but I wanted to use APIView and on top of that I wanted some business logic to be added. I probably have to do some more research on how to add logic to modelviewset. Here is my Solution.
Views.py
def get(self, request, token_id):
get_job = Jobs.objects.get(token_id=token_id)
pk = get_job.id
job_list = Jobs.objects.exclude(runtestnow=True)
next_q = job_list.order_by('id').first()
queue = VM.objects.get()
waitlist = int(pk) - int(next_q.id)
if waitlist == 1:
waitlist = 'You are next on the queue. :)'
return Response(
{"tokenid": token_id, "Runtestnow": False, "VMcount":
queue.vm_count,
'GridName': grid_name, 'waitlist #': waitlist, 'Vm_left':
queue.vm_count}, status=status.HTTP_201_CREATED)
Urls.py
path('checkstatus/<token_id>', CheckStatusView.as_view()),
We can always use the slug field, but I really wanted token_id as input. This should work fine for me as of now.
There might be some other way as well. Feel free to share.
I'm using django-rest-framework-simplejwt and was wondering if it's possible to return a token after registering a user?
This post has a solution for another jwt package and I was wondering how I could do something similar for simplejwt?
thanks
I just solved my own question. Let me know if you have any comments. Thanks!
serializers.py
class RegisterUserSerializer(serializers.ModelSerializer):
"""Serializer for creating user objects."""
tokens = serializers.SerializerMethodField()
class Meta:
model = models.User
fields = ('id', 'password', 'email', 'tokens')
extra_kwargs = {'password': {'write_only': True}}
def get_tokens(self, user):
tokens = RefreshToken.for_user(user)
refresh = text_type(tokens)
access = text_type(tokens.access_token)
data = {
"refresh": refresh,
"access": access
}
return data
def create(self, validated_data):
user = models.User(
email=validated_data['email']
)
user.set_password(validated_data['password'])
user.save()
return user
views.py
class UserListView(generics.ListCreateAPIView):
"""Handles creating and listing Users."""
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
serializer = RegisterUserSerializer(data=request.data)
if serializer.is_valid():
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
another possible solution is:
in your view
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.tokens import AccessToken, RefreshToken
#login_required
def index(request):
tokenr = TokenObtainPairSerializer().get_token(request.user)
tokena = AccessToken().for_user(request.user)
return render(request,'myview/index.html', {"refresh" : str(tokenr),"access" : str(tokena)} )
I used #login_required just to be sure we have a request.user authenticated, but you could pass a dict instead
I guess, you can do something like this :
def custom_registration_view(request):
//code to validate & register your user
payload = jwt_payload_handler(user)
return HttpResponse(jwt_encode_handler(payload), 201)
The payload_handler, encode_handler and decode_handler you can specify in settings file.
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"})