ValidationError HTTP Code for 'user already exist' - python

I'm doing a login app for my Django project using Django Rest Framework.
At registration, in my serializer, i'm checking if user already exist by doing this:
def create(self, validated_data):
"""
Create an user but validate datafirst(checking if exist,
if mail is right,not already in-use, and hash password).
"""
queryset = PictureUser.objects.filter(username=validated_data['username'])
try:
queryset[0]
raise serializers.ValidationError('The username is already used.')
except IndexError:
print ("User don't exist.")
user = PictureUser.objects.create(
email = validated_data['email'],
username = validated_data['username'],
password = make_password(validated_data['password']),
)
user.save()
return user
This work pretty well. But the HTTP code sent is 400 when the user already exist. How can I change it to 409 or 422 to be more accurate on what went wrong ? (400 error would imply many other errors.)
Must I return an HttpResponse instead?
Thanks.

You can't do that in the serializer.
Maybe you can have something like this:
view.py
class YourView(APIView):
def post(self, request, format=None):
serializer = YourSerializer(data=request.data)
if serializer.is_valid():
return Response({'yourinfo':'something'}, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_409_CONFLICT) # change it to
serializer.py
class YourSerializer(serializers.ModelSerializer):
# Override this if you want. Default Django Auth
username = serializers.CharField()
password = serializers.CharField()
class Meta:
model = User
def validate(self, attrs):
username = attrs['username']
password = attrs['password']
# do your validations here
if everything_ok:
return attrs
else:
raise serializers.ValidationError("User does not belong to API group")

Related

dj allauth get password when its resetted

I need to provide the raw password of an allauth user to a third party provider when he resets his password. So everytime when the password gets resetted I call the #receiver(password_reset). However, then the password was already salted. I need to get the raw password data to realise the password change also at an external service. How would get the new "raw" password, which wasn't already salted or how could I desalt it?
from allauth.account.signals import password_reset
from django.dispatch import receiver
#receiver(password_reset)
def password_change_callback(sender, request, user, **kwargs):
#run third party api call containing the new password
If what you want is to get the new password a user inputed while changing his/her password. What you can do is that on the post request you store the value of the new password the user inputed in the form in a variable. This way after you have reseted the password you can still access the raw password since you saved it in a variable before resetting. So something like the below:
new_password = request.POST.get("new_password")
# your code to set the new password goes here
and after that you can still access the new_password variable and do whatever you want with it.
class ChangeUserPasswordView(UpdateAPIView):
queryset = User.objects.filter(is_active=True)
permission_classes = (IsAuthenticated,)
serializer_class = ChangePasswordSerializer
def get_object(self, *args, **kwargs):
return self.request.user
Then for the serializer
from django.contrib.auth.password_validation import validate_password
class ChangePasswordSerializer(serializers.ModelSerializer):
new_password = serializers.CharField(
write_only=True, required=True, validators=[validate_password]
)
confirm_password = serializers.CharField(write_only=True, required=True)
old_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = ("old_password", "new_password", "confirm_password")
def validate_old_password(self, value):
user = self.context["request"].user
if not user.check_password(value):
raise serializers.ValidationError(
{"old_password": "Old password is not correct"}
)
return value
def validate(self, attrs):
if attrs["new_password"] != attrs["confirm_password"]:
raise serializers.ValidationError(
{"password": "Password fields didn't match."}
)
return attrs
def update(self, instance, validated_data):
instance.set_password(validated_data["new_password"])
instance.save()
return instance
In your user model add function
from django.contrib.auth.hashers import check_password
def check_password(self, raw_password, *args, **kwargs):
return check_password(raw_password, self.password)
If you want a reset password, where you don’t want to add the old password you can replace your serilalizer code with the below.
class ChangePasswordSerializer(serializers.ModelSerializer):
new_password = serializers.CharField(
write_only=True, required=True, validators=[validate_password]
)
confirm_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = ("new_password", "confirm_password")
def validate(self, attrs):
if attrs["new_password"] != attrs["confirm_password"]:
raise serializers.ValidationError(
{"password": "Password fields didn't match."}
)
return attrs
def update(self, instance, validated_data):
instance.set_password(validated_data["new_password"])
instance.save()
return instance
Here in the serializer validate function you have access to the user's inputed password using
attrs["new_password"]

Sequentially validating individual attributes with Django Rest Framework

I am very new to Django Rest Framework and am confused about coding the following task: when a user on the front-end enters an email address, without any other user information, the API should determine whether or not the email already exists. Likewise, when the user enters a username, without any other user information, the API should determine whether or not the username already exists. This is so the user has feedback on whether their email or username is valid before proceeding to the next sign up stage.
I am aware of the existence of validators, but I don't understand how to use them on partial data.
Here is my serializers.py class
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
username = serializers.CharField(min_length=2)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField(min_length=2)
last_name = serializers.CharField(min_length=2)
class Meta:
model = CustomUser
fields = ('pk', 'email', 'username', 'password', 'created_at', 'first_name', 'last_name')
extra_kwargs = {'password': {'write_only': True}}
def validate_email(self, value):
if CustomUser.objects.filter(email=value).exists():
raise serializers.ValidationError("An account already exists with that email address.")
return value
def validate_username(self, value):
if CustomUser.objects.filter(username=value).exists():
raise serializers.ValidationError("An account aready exists with that username.")
return value
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
And here is my views.py class
class CustomUserCreate(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = ()
def post(self, request, format='json'):
"""
Receives an HTTP request. Serializes and saves a user object.
"""
serializer = CustomUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CustomUserGet(APIView):
def get_user(self, username):
try:
return CustomUser.objects.get(username=username)
except CustomUser.DoesNotExist:
raise Http404
def get(self, request, format='json'):
user = self.get_user(request.user.username)
serializer = CustomUserSerializer(user)
return Response(serializer.data)
class LogoutAndBlacklistRefreshTokenForUserView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request):
try:
refresh_token = request.data['refresh_token']
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
CustomUser is simply an implementation of AbstractUser.
Again, this successfully validates when a user is created with complete data. However, I want to validate on partial data, such as a lone email address and lone username without the presence of other attributes such as first_name, last_name, etc. What is the proper way of approaching this?

Can't create a custom User model in django

I have a custom passwordless user model built in django 1.11. user model looks like this
class User(models.Model):
email = models.EmailField(primary_key=True)
REQUIRED_FIELDS = []
USERNAME_FIELD = 'email'
is_anonymous = False
is_authenticated = True
It's a custom user model and depends on a custom auth backend, given like this
class PasswordlessAuthenticationBackend():
def authenticate(self, uid):
try:
token = Token.objects.get(uid=uid)
return User.objects.get(email=token.email)
except User.DoesNotExist:
return User.objects.create(email=token.email)
except Token.DoesNotExist:
return None
def get_user(self, email):
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
The auth is registered and working fine. The token is just this
class Token(models.Model):
email = models.EmailField()
uid = models.CharField(default=uuid.uuid4, max_length=40)
The problem is, when I try to call auth.login in my TestCase, it always throws this error:
ValueError: The following fields do not exist in this model or are m2m fields: last_login
What are m2m fields? How and where do I specify this last_login?
Edit:
The failing test looks like this:
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.contrib import auth
from accounts.models import Token
User = get_user_model()
email = 'test#testing.com'
class UserModelTestcase(TestCase):
def test_user_is_valid_with_email_only(self):
user = User(email=email)
user.full_clean()
def test_email_is_primary_key(self):
user = User(email=email)
self.assertEqual(user.pk,email)
def test_links_user_with_auto_generated_uid(self):
token1 = Token.objects.create(email=email)
token2 = Token.objects.create(email=email)
self.assertNotEqual(token1.uid, token2.uid)
def test_no_problem_with_auth_login(self):
user = User.objects.create(email=email)
user.backend = ''
request = self.client.request().wsgi_request
auth.login(request, user) #should not raise, fails!
this link Error about Django custom authentication and login?
has an in-depth explanation about your problem an has multiple solutions you can pick.

Serializer field validation and how to check if value exist?

I have 2 models, User and UserProfile. UserProfile model has OneToOneField with User model. Here I am trying to update both the models in a single request.
Request Payload:
{'email': ['xxx#gmail.com'], 'first_name': ['Nalin'], 'last_name': ['Dobhal'],}
I have created serializer for both models.
serializers.py
class UserAccountSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, read_only=True)
mobile = serializers.IntegerField(read_only=True)
email = serializers.EmailField(required=False, read_only=False)
username = serializers.CharField(read_only=True)
class Meta:
model = User
fields = ("id", "mobile", 'email', "username",)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserAccountSerializer(required=False, read_only=False)
# other fields
class Meta:
model = UserProfile
fields = ("user", # other fields)
def update(self, instance, validated_data):
# validated data doesn't have email here, that's why getting value from self.initial_data
if self.initial_data.get("email"):
instance.user.email = self.initial_data.get("email")
instance.user.save()
instance.save()
return instance
views.py
class UserAccountSettingsAPI(generics.RetrieveUpdateAPIView):
http_method_names = ["options", "get", "put", "patch"]
permission_classes = (IsAuthenticated,)
authentication_classes = (TokenAuthentication,)
serializer_class = UserProfileSerializer
def get(self, request, *args, **kwargs):
# some processing
def update(self, request, *args, **kwargs):
profile = UserProfile.objects.select_related("user").get(user_id=request.user.id)
serializer = self.get_serializer(profile, data=request.data)
if serializer.is_valid(raise_exception=False):
serializer.save()
# some other processing only to set key value for context.
return Response(context)
I would like to perform some validation before updating user's email. So my question is where to perform that validation? And is there any better way of doing this? I tried to add def validate_email(self, email): in UserAccountSerializer but it is not getting executed. So I want to make sure that email does not belong to another user and if email exists, I would like to send some custom error message to client.
I have removed unnecessary code.
Try adding this code
create or update in views
if User.objects.filter(email=self.request.data['email']).exists():
return Response({"error": "This email id already exists."})
You could try raising a ValidationError which is part of the serializers library:
from rest_framework import serializers
def update(self, instance, validated_data):
# If `self.request.data["email"]` works
if User.objects.filter(email=self.request.data["email"]).exists():
raise serializers.ValidationError("This email already exists.")
# If `self.request.data["email"]` doesn't work
new_email = self.initial_data.get("email"):
if new_email and User.objects.filter(email=new_email).exists():
raise serializers.ValidationError("This email already exists.")
# Save and return the modified instanced
or if you unique=True to the email field, it will raise an IntegrityError so you don't need to do any further checks. You simply catch the error and handle it yourself.
I assume you want email to be unique. Then you should add unique=True to your user model's email field.
class YourUserModel(AbstractUser):
email = models.EmailField(unique=True)
After you make email a unique field, your database will not allow to add another entry with the same email and it will raise IntegrityError. You can catch this error and return a better error message to your user. Like this:
try:
if serializer.is_valid(raise_exception=False):
serializer.save()
except IntegrityError:
return Response(data={'message':'that email is in use'}, status=HTTP_400_BAD_REQUEST)

Checking if username exists in Django

I am working on a Django project where users will be able to change their usernames along with their first and last name in one form. In forms.py, I am trying to find out if the user exists. If so, it should display an error.
The problem is that if user wants to change his first and last name and leaves his username in the input, it raises a validation error. Obviously, that username already exists.
Is there a way to check if it equals the username of currently logged user and avoid displaying the error?
class ChangeNameForm(forms.ModelForm):
username = forms.CharField(max_length=30)
first_name = forms.CharField(max_length=255)
last_name = forms.CharField(max_length=255)
def clean_username(self):
username = self.cleaned_data['username']
try:
user = User.objects.get(username=username)
except user.DoesNotExist:
return username
raise forms.ValidationError(u'Username "%s" is already in use.' % username)
When ModelForms are bound to a model object, they have an attribute called 'instance', which is the model object itself. In your view, when request.method == 'POST', you're probably creating the form instance like this:
form = ChangeNameForm(request.POST, instance=request.user)
If that's the case, you can access the logged user from the form methods, and your validation method can be something like this:
def clean_username(self):
username = self.cleaned_data['username']
try:
user = User.objects.exclude(pk=self.instance.pk).get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError(u'Username "%s" is already in use.' % username)
Consider using the .exists method, for it issues a faster query to your database than if you try to retrieve all the user information with the .get method. And the code gets a little cleaner too:
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.exclude(pk=self.instance.pk).filter(username=username).exists():
raise forms.ValidationError(u'Username "%s" is already in use.' % username)
return username
Optionally, you can also follow these guidelines when raising the ValidationError.
I can't test this code right now, so I apologize if there's anything wrong.
You can write function to check the username if exists like this:
#ggorlen, thanks! Update:
from django.contrib.auth.models import User
def username_exists(username):
return User.objects.filter(username=username).exists()
from django.contrib.auth.models import User
def username_exists(username):
if User.objects.filter(username=username).exists():
return True
return False
This is how I managed to make it work (assuming you have a logged in user):
forms.py
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.models import User
class MyUserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs):
super(MyUserChangeForm, self).__init__(*args, **kwargs)
del self.fields['password']
class Meta:
model = User
fields = ('username', 'first_name')
views.py
def home(request):
if request.method == 'POST':
form = MyUserChangeForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
else:
form = MyUserChangeForm(instance=request.user)
return render(request, 'change_user.html', {"form": form})

Categories

Resources