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"})
Related
I'm developing my backend on Django. I wrote a code for user registration under RegistrationView class, and it worked fine before. I had to incorporate email confirmation in it, which also worked fine on its own. However, after all of that was done, I added some conditions to ensure proper working, but I get the "string='Incorrect type. Expected pk value, received BoundField.'" and "Forbidden: /api/v1.0/user/register-patient/" error.
Also, to clarify the workflow. Each user is assigned a JWT token while signing up. This token is different from the token sent to verify email. I named the latter "verification_token".
So a user signs up, their 'is_active' field is set to False until they click on the link received in their email. Sendgrid email API was used to send the email. The email is only sent if the patient_serializer data is stored in the db.
When the user clicks on the link received through email, their 'is_active' field is set to True, and they can now log in.
Also, the actual sendgrid API key is a part of the code, I removed it here.
My models.py file:
from django.db import models
from django.contrib.auth.models import User
class VerificatonToken(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='token')
verification_token = models.CharField(
max_length=256, blank=True, null=True)
My serializers.py file:
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework.validators import UniqueValidator
from rest_framework_jwt.settings import api_settings
from .models import VerificatonToken
class TokenSerializer(serializers.ModelSerializer):
class Meta:
model = VerificatonToken
fields = ('user', 'verification_token',)
class PatientSerializer(serializers.ModelSerializer):
token = serializers.SerializerMethodField()
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
username = serializers.CharField(
required=True,
max_length=32,
validators=[UniqueValidator(queryset=User.objects.all())]
)
first_name = serializers.CharField(
required=True,
max_length=32
)
last_name = serializers.CharField(
required=True,
max_length=32
)
# DOB = serializers.DateField(
# required=True
# )
# gender = serializers.CharField(
# required=True
# )
# address = serializers.CharField(
# required=True,
# max_length=60
# )
# contactNo = serializers.IntegerField(
# required=True,
# max_length=11
# )
password = serializers.CharField(
required=True,
min_length=8,
write_only=True
)
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
def get_token(self, obj):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(obj)
token = jwt_encode_handler(payload)
return token
class Meta:
model = User
fields = (
'token',
'username',
'password',
'first_name',
'last_name',
'email',
'is_active',
# 'DOB',
# 'address',
# 'contactNo',
'id'
)
My views.py file:
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import PatientSerializer, TokenSerializer
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
import uuid
import hashlib
from .models import VerificatonToken
from rest_framework.permissions import AllowAny
class TestView(APIView):
def get(self, request, format=None):
print("API called")
return Response("You did it!", status=200)
# Registering User & Sending Email Confirmation
class RegisterView(APIView):
permission_classes = [AllowAny]
def post(self, request, format=None):
print("Registering User")
user_data = request.data
user_data['is_active'] = False
print(request.data)
patient_serializer = PatientSerializer(data=user_data)
if patient_serializer.is_valid(raise_exception=False):
patient_serializer.save()
salt = uuid.uuid4().hex
hash_object = hashlib.sha256(
salt.encode() + str(patient_serializer.data['id']).encode())
verification_token = hash_object.hexdigest() + ':' + salt
verification_token_serializer = TokenSerializer(
data={'user': patient_serializer['id'], 'token': verification_token})
if verification_token_serializer.is_valid(raise_exception=False):
verification_token_serializer.save()
message = Mail(
from_email='allinonehealthapp22#gmail.com',
to_emails=user_data['email'],
subject='Please Confirm Your Email Address',
html_content=f"Hi {user_data['username']}!\
<br><br>\
Welcome to All-in-one Health App. To finish signing up, please click \
<a href='http://localhost:8000/api/v1.0/user/verify-patient/{verification_token}'>HERE</a>\
")
try:
sg = SendGridAPIClient(
'<Sendgrid API KEY>')
response = sg.send(message)
print(response)
return Response(patient_serializer.data, status=200)
except Exception as e:
print('ERROR', e)
else:
print(patient_serializer.errors,
verification_token_serializer.errors)
return Response({'msg': 'error'}, status=403)
# RegisterPatientView() provides customized link to VerifyPatientView() in email to patient
# compares the token provided to verify the email
class PatientVerificationView(APIView):
def get(self, request, pk, format=None):
print("Verifying Patient", pk)
tokenObj = VerificatonToken.objects.filter(token=pk).first()
user = User.objects.filter(id=tokenObj.user.id).first()
if user:
patient_serializer = PatientSerializer(
user, data={'is_active': True})
if patient_serializer.is_valid(raise_exception=False):
patient_serializer.save()
return Response('Patient email verified', status=200)
return Response(status=404)
class PatientLoginView(APIView):
# convert user token to user data
def get(self, request, format=None):
if request.user.is_authenticated == False or request.user.is_active == False:
return Response('Invalid credentials', status=403)
user = PatientSerializer(request.user)
print(user.data)
return Response("Testing", status=200)
# authenticate user data
def post(self, request, format=None):
print("Login class")
# Looking for user's email or username
user_obj = User.objects.filter(email=request.data['username']).first(
) or User.objects.filter(username=request.data['username']).first()
# if user is valid, returns user data in credentials
if user_obj is not None:
credentials = {
'username': user_obj.username,
'password': request.data['password']
}
user = authenticate(**credentials)
# if user is valid and active, logs user in
if user and user.is_active:
user_serializer = PatientSerializer(user)
print("Login successful")
return Response(user_serializer.data, status=200)
return Response("Invalid credentials", status=403)
Postman request:
Command line:
EDIT: My TokenSerializer is basically the one giving an error. The Patiet_Serializer is working fine because the user info goes into the database with the 'in_active' field set to false. I can't figure out how to fix it.
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 can`t update users because Django gives me this error in postman:
AttributeError at /profesionales/
Got AttributeError when attempting to get a value for field `user` on serializer `ProfesionalesSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'user'.
Request Method: PUT
Request URL: http://127.0.0.1:8000/profesionales/
Django Version: 1.11.6
Python Executable: C:\Users\Ismael\AppData\Local\Programs\Python\Python36\python.exe
Python Version: 3.6.3
Here is my code:
view.py
#Listar todos los profesionales o crear uno
#profesionales/
class ProfesionalesList(APIView):
def get_object(self, pk):
try:
return User.objects.get(username=pk)
except User.DoesNotExist:
raise Http404
def get(self, request ):
usuarios = Profesionales.objects.all()
usuarioSerializer = ProfesionalesSerializer(usuarios, many=True)
return Response(usuarioSerializer.data)
def post(self, request):
profesionalSerializer = ProfesionalesSerializer(data=request.data)
if profesionalSerializer.is_valid():
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
else:
return Response(profesionalSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
def put(self, request, *args, **kwargs):
instance = self.get_object(request.data.get('user').get('username'))
profesionalSerializer = ProfesionalesSerializer(instance, data=request.data)
if profesionalSerializer.is_valid():
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
else:
return Response(profesionalSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializer(serializers.Serializer):
username = serializers.CharField()
first_name = serializers.CharField(allow_blank=True)
last_name = serializers.CharField(allow_blank=True)
email = serializers.CharField(allow_blank=True)
class Meta:
fields = ('username', 'first_name', 'last_name', 'email')
def create(self, validated_data):
user = User.objects.create(**validated_data)
return user
class ProfesionalesSerializer(serializers.Serializer):
user = UserSerializer()
numColegiado = serializers.CharField(allow_blank=False)
class Meta:
fields = ('user', 'numColegiado')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profesional = Profesionales.objects.create(user=user, **validated_data)
return profesional
def update(self, instance, validated_data):
num_colegiado = validated_data.get('numColegiado')
user_data = validated_data.pop('user')
user = User.objects.get(**user_data)
profesionales = user.profesionales
if num_colegiado:
profesionales.numColegiado = num_colegiado
profesionales.save()
return instance
model.py
class Profesionales(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dni = models.CharField(max_length=9, blank=True, default='')
numColegiado = models.CharField(max_length=8, blank=True, default='')
valoracionMedia = models.FloatField(blank=True, default=0)
numVotos = models.IntegerField(blank=True, default=0)
def __str__(self):
return self.numColegiado
Ok, I have it - but to be honest, you should rethink your API design.
The problem is that - there is no problem -I mean from yout code I cannot reproduce the error. It is probably I assumed the User model - if you can paste the user model definition would be great (or if this is a standard django user - also mention that).
So firstly I would change the serializers to model serializers:
serializers.py
class UsernameValidator(object):
def set_context(self, serializer_field):
"""
This hook is called by the serializer instance,
prior to the validation call being made.
"""
# Determine the existing instance, if this is an update operation.
self.instance = getattr(serializer_field.parent, 'instance', None)
if not self.instance:
# try to get user from profesionales:
root_instance = getattr(serializer_field.root, 'instance', None)
self.instance = getattr(root_instance, 'user', None)
def __call__(self, value):
if self.instance and User.objects.filter(username=value).exclude(id=self.instance.id).exists():
raise ValidationError('Username already exists.')
if not self.instance and User.objects.filter(username=value).exists():
raise ValidationError('Username already exists.')
class UserSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=128, validators=[UsernameValidator()])
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class ProfesionalesSerializer(serializers.ModelSerializer):
user = UserSerializer()
numColegiado = serializers.CharField(allow_blank=False)
class Meta:
model = Profesionales
fields = ('user', 'numColegiado')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profesional = Profesionales.objects.create(user=user, **validated_data)
return profesional
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
userSerializer = UserSerializer(user, data=user_data)
if userSerializer.is_valid(raise_exception=True):
userSerializer.save()
num_colegiado = validated_data.get('numColegiado')
if num_colegiado:
instance.numColegiado = num_colegiado
instance.save()
return instance
As you can note - I've added the UsernameValidator which is pretty important for API to work properly 0- it basically search for existing user instance and check if username exists or not;
I've also changed the update method - now it is using the UserSerializer explicite; Also corrected some bugs - returning validated_data instead of instance and so on.
At the end views.py:
class ProfesionalesList(APIView):
def get_object(self, pk):
try:
return User.objects.get(username=pk)
except User.DoesNotExist:
raise Http404
def get(self, request ):
usuarios = Profesionales.objects.all()
usuarioSerializer = ProfesionalesSerializer(usuarios, many=True)
return Response(usuarioSerializer.data)
def post(self, request):
profesionalSerializer = ProfesionalesSerializer(data=request.data)
if profesionalSerializer.is_valid(raise_exception=True):
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
def put(self, request, *args, **kwargs):
user = self.get_object(request.data.get('user').get('username'))
profesionalSerializer = ProfesionalesSerializer(user.profesionales, data=request.data)
if profesionalSerializer.is_valid(raise_exception=True):
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_200_OK)
I've shorten the code - using the raise_exception in is_valid method.
Actually - sorry for not following the stackoverflow rules - and do not provide an answer for your actual problem - but I strongly believe that analyzing the example you can figure it out. If you have any more questions - please ask.
On your class ProfesionalesSerializer, you have defined user as an instance of UserSerializer. Instead, user should be a field. You can't use serializers as "fields".
Edit: Ignore this. Turns out you can. See here: http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
(thanks to #opalczynski)
I am trying, through a CustomUser model and its serializer to register new users and to display them.
Here is my model:
class CustomUser(models.Model):
user = models.OneToOneField(User)
additional_field = models.CharField(max_length=30, default='', blank=True)
My serializer (from Django REST Framework Creating custom user):
class CustomUserSerializer(serializers.ModelSerializer):
additional_field = serializers.CharField(source='customuser.additional_field')
class Meta:
model = User
fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email', 'additional_field')
def create(self, validated_data):
profile_data = validated_data.pop('customuser', None)
user = super(CustomUserSerializer, self).create(validated_data)
self.create_or_update_profile(user, profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('customuser', None)
self.create_or_update_profile(instance, profile_data)
return super(CustomUserSerializer, self).update(instance, validated_data)
def create_or_update_profile(self, user, profile_data):
profile, created = CustomUser.objects.get_or_create(user=user, defaults=profile_data)
if not created and profile_data is not None:
super(CustomUserSerializer, self).update(profile, profile_data)
An my views, respectively to display and register a user:
def get_all_users(request):
all_friends = CustomUser.objects.all()
serial = CustomUserSerializer(all_friends, many=True)
json_data = JSONRenderer().render(serial.data)
return HttpResponse(json_data, content_type='application/json')
#api_view(['POST'])
def register_new_user(request):
if request.method == 'POST':
print('POST request !')
print(request.data)
serializer = CustomUserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print('save friend!')
return HttpResponse(status.HTTP_201_CREATED)
else:
print(serializer.errors)
return HttpResponse(status.HTTP_403_FORBIDDEN)
While I am able to register a user with the following piece of code:
import requests
import json
json_data = {'username': 'mylogin99', 'password': 'mypassword', 'email': 'mymail99#wanadoo.fr', 'additional_field' : 'myaddfield'}
r = requests.post('http://127.0.0.1:8000/register_new_user', json=json_data)
I am unable to display the registered users, as I get the following error:
AttributeError at /getAllUsers
Got AttributeError when attempting to get a value for field `username` on serializer `CustomUserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `CustomUser` instance.
Original exception text was: 'CustomUser' object has no attribute 'username'.
I do not understand why the username field does not refer to the username field of the User model.
The best way I could come up with was to implement a as_json method in the CustomUser model:
def as_json(self):
return dict(
id=self.user.id, username=self.user.username,
password=self.user.password, email=self.user.email,
additional_field=self.additional_field)
Thus, to retrieve all the registered users, I've modified the get_all_user method:
#api_view(['GET'])
def get_all_users(request):
# retrieve all CustomUser objects
all_friends = CustomUser.objects.all()
lst = [friend.as_json() for friend in all_friends]
json_data = JSONRenderer().render(lst)
return HttpResponse(json_data, content_type='application/json')
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