Accessing user object when creating a user in Django - python

I'm using Django 1.10.6 and I'm working on a user registration form. During the validation process in my forms.py I'm trying to call the UserAttributeSimilarityValidator from within the clean method, but I'm having some trouble figuring out what the user object argument is that it requires. The request data is a QueryDict, not an object, so I'm confused as to what object I'm supposed to be using.
Here is the UserAttributeSimilarityValidator class found in the link I provided
class UserAttributeSimilarityValidator(object):
DEFAULT_USER_ATTRIBUTES = ('username', 'first_name', 'last_name', 'email')
def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
self.user_attributes = user_attributes
self.max_similarity = max_similarity
def validate(self, password, user=None):
if not user:
return
for attribute_name in self.user_attributes:
value = getattr(user, attribute_name, None)
if not value or not isinstance(value, string_types):
continue
value_parts = re.split('\W+', value) + [value]
for value_part in value_parts:
if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() > self.max_similarity:
verbose_name = force_text(user._meta.get_field(attribute_name).verbose_name)
raise ValidationError(
_("The password is too similar to the %(verbose_name)s."),
code='password_too_similar',
params={'verbose_name': verbose_name},
)
def get_help_text(self):
return _("Your password can't be too similar to your other personal information.")
Here's my clean method in forms.py
def clean(self):
cleaned_data = super(RegisterForm, self).clean()
password = cleaned_data.get('password')
password_validation.UserAttributeSimilarityValidator().validate(password, USER_OBJET_HERE)
I tried to create a user object by using namedtuple like so
user = namedtuple('user', 'username email')
user_attributes = user(username = self.data.get('username'), email = self.data.get('email'))
password_validation.UserAttributeSimilarityValidator().validate(password1, user_attributes)
When I do this it fails with the following error 'user' object has no attribute '_meta'. Is there an actual user object created by the form that I'm supposed to access, or am I supposed to create it like I attempted?
UPDATE I was way overthinking this and got it to work. I had to manually set the username and email attributes for the User object like so
from django.contrib.auth.models import User
...
User.username = self.data.get('username')
User.email = self.data.get('email')

Related

How to update user details in custom user model in django

i just created a custom user model from abstractuser. I can create user but update is not working showing some error. I am also week in english so idk how to share my problem. In short hand i want to edit email and password of a user.
###This is my user model
class User(AbstractUser):
roles =(
('Admin','Admin'),
('Placement Manager','Placement Manager'),
)
username=models.CharField(max_length=100,null=True,blank=True)
email = models.EmailField(max_length=50, null=True,blank=True)
phone = models.IntegerField(unique=True)
role = models.CharField(max_length=100,choices = roles,null=True,blank=False)
USERNAME_FIELD = 'phone'
REQUIRED_FIELDS = ['email','username','role']
objects=UserManager()
def get_username(self):
return self.email
###This is my view
def editPlacementManager(request):
if request.method=='POST':
name=request.POST.get('name')
phone=request.POST.get('phone')
email=request.POST.get('email')
password=request.POST.get('password')
userid = request.POST.get('pmId')
User.objects.get(id=userid).update(username=name,phone=phone,email=email,password=password,role='Placement Manager')
return redirect('listplacementmanager')
return render(request,"index/placementmanager.html")
### The error is
AttributeError at /editplacementmanager
'User' object has no attribute 'update'
Yh, you'll get that error because the update method is available for only querysets.
So you can do this:
def editPlacementManager(request):
if request.method=='POST':
name=request.POST.get('name')
phone=request.POST.get('phone')
email=request.POST.get('email')
password=request.POST.get('password')
userid = request.POST.get('pmId')
user = User.objects.get(id=userid)
user.name = name
user.phone = phone
user.email = email
# You can continue with whichever field you want
user.save()
# Then finally you save the object with the updated fields
return redirect('listplacementmanager')
return render(request,"index/placementmanager.html")
actually it's a simple thing just hash the password and update.

How to write custom Username validation using Self.clean_username for Profile Update form in Django?

I am a beginner and I'm still learning Django. I am writing my custom form validation for user profile update. My profile update form has -
username
first name
last name
profile image
First I have used default form validation. It is also good but I want to update the error message so I tried to create a custom validation function as I seen in a post in Stack Overflow. Please also check my username validation in forms.py
So what is the error now?
Now I am facing one more issues
If I click on update button without changing anything then it shows can't use this username. {username is set to unique=True in model fields}
If I don't update username and update just first name and last name. It doesn't allow this says same error. "Username is already taken" {because that username is validated and it already taken by current user"}
What exactly I want?
Instead of changing just error message now I want to write my own validation for this. I want if some click on update without changing any data no error message should be shown.
I can update each field independently. No error message should be shown if i don't update username.
My Views
#login_required()
def profile(request):
if request.method=='POST':
u_form = UserUpdateForm(request.POST,request.FILES,instance=request.user)
if u_form.is_valid():
u_form.save()
messages.success(request, f'Profile Updated Succesfully')
redirect('profile/')
else:
u_form = UserUpdateForm(instance=request.user)
notes = UserCreatedNote.objects.filter(user=request.user)
context = {'u_form': u_form,'notes':notes}
return render(request,'profile.html',context)
My models
from django.db import models
from django.contrib.auth.models import User
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
# ///////////////User Manager/////////////////////
# Create your models here.
# overriding the create and superuser funciton
class MyAccountManager(BaseUserManager):
def create_user(self,email,username,password=None):
if not email:
raise ValueError("Users Must Have email Address")
if not username:
raise ValueError("Users Must Have username")
user = self.model(
email=self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,email,username,password):
user = self.create_user(
email = self.normalize_email(email),
username=username,
password=password,
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
# ////////////////////////////////////////////////////////////
def get_profile_image_filepath(self,filename):
return f'profile_images/{self.pk}/{filename}' #pk= primary key
def get_default_profile_image():
return "img/default_profile/default.png"
class KeepSafeUserModel(AbstractBaseUser):
first_name = models.CharField(verbose_name='first_name',max_length=30,default="")
last_name = models.CharField(verbose_name='last_name',max_length=30,default="")
email= models.TextField(verbose_name='email',max_length=60,unique=True,primary_key=True)
username = models.CharField(max_length=30,unique=True)
date_joined = models.DateTimeField(verbose_name="date joined",auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last_login',auto_now=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
profile_image = models.ImageField(max_length=225,upload_to=get_profile_image_filepath,null=True,blank=True,default=get_default_profile_image)
#password field is buil-in
objects = MyAccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
def __str__(self):
return self.username
def has_perm(self,perm,obj=None):
return self.is_admin
def has_module_perms(self,app_Label):
return True
def get_profile_image_filename(self):
return str(self.profile_image)[str(self.profile_image).index('profile_images/{self.pk}/')]
forms.py
class UserUpdateForm(forms.ModelForm):
profile_image = forms.ImageField(required=False,error_messages ={'invalid':("Image files only")},widget=forms.FileInput)
class Meta:
model = KeepSafeUserModel
fields = ['username','first_name','last_name','profile_image']
labels ={
'username':'Username',
'first_name':'First Name',
'last_name':'Last Name',
'profile_image':'Profile Image'
}
def clean_username(self):
username = self.cleaned_data['username']
if not username.isdigit():
users = KeepSafeUserModel.objects.filter(username__iexact=username)
if users:
raise forms.ValidationError("Username has already taken by someone else")
else:
raise forms.ValidationError("Username can't contains only numbers")
return username
The problem is that if you update your KeepSafeUserModel, you will have a hit for KeepSafeUserModel.objects.filter(username__iexact=username): indeed, it will simply match with itself.
You can simply exclude the object with:
def clean_username(self):
username = self.cleaned_data['username']
if not username.isdigit():
users = KeepSafeUserModel.objects.exclude(pk=self.instance.pk).filter(
username__iexact=username
).exists()
if users:
raise forms.ValidationError"Username has already taken by someone else")
else:
raise forms.ValidationError("Username can't contains only numbers")
return username
I think you already pass the instance of user logged in to the form. So you need to make the clean_username(self) validation become like this.
def clean_username(self):
username = self.cleaned_data['username']
if not username.isdigit():
users = KeepSafeUserModel.objects.filter(username__iexact=username)
if users:
if users.username != self.instance.username:
raise forms.ValidationError("Username has already taken by someone else")
else:
raise forms.ValidationError("Username can't contains only numbers")
return username
It will validate the username given and stored is not same

user.save() not working properly when used in overriding serializer class's save() method

what i wanted was to send a post request with body containing email , username , password and password 2.
the serializer class has a save method which has been overriden so that it checks password1 is equal to password2 or not . if equal i wanted a user object to be created with email username and a password . and wanted it to be saved . im using User model of django.
error:
TypeError: Got a TypeError when calling User.objects.create(). This may be because you have a writable field on the serializer class tha
t is not a valid argument to User.objects.create(). You may need to make the field read-only, or override the RegistrationSerializer.create() method to handle this correctly.
Serializer Class:
class RegistrationSerializer(serializers.ModelSerializer):
password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True)
class Meta:
model = User
fields = ['email', 'username', 'password','password2']
extra_kwargs = {'password': {'write_only': True}}
# override one of its method to check if passwords match
def save(self):
user = User()
#cant create user py passing into constructor
user.email=self.validated_data['email']
user.username=self.validated_data['username']
password=self.validated_data['password']
password2=self.validated_data['password2']
if password!=password2:
raise serializers.ValidationError({'password':'password must match'})
user.set_password(password)
user.save()
return user
the view called:
#api_view(['POST', ])
def registration_view(request):
serializer=RegistrationSerializer(data=request.data)
data={}
if serializer.is_valid():
user=serializer.save()
data['response']="successfully created"
data['email']=user.email
data['username']=user.username
else:
data=serializer.errors
return Response(data)
As save method is not good place for validation, You should use validate function when you want control some fields are correct.
class RegistrationSerializer(serializers.ModelSerializer):
password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True)
class Meta:
model = User
fields = ['email', 'username', 'password','password2']
extra_kwargs = {'password': {'write_only': True}}
def validate(self, attrs):
if attrs.get('password') != attrs.get('password2'):
raise serializers.ValidationError({'password':'password must match'})
return attrs
def create(self, validated_data):
password2 = validated_data.pop('password2')
return super().create(validated_data)
If you want looks save function http://www.cdrf.co/3.9/rest_framework.serializers/ModelSerializer.html#save

"Got KeyError when attempting to get a value for field `username` on serializer `UserCreateSerializer`

I have a user register APIView.
view:
class UserCreateAPIView(CreateAPIView):
serializer_class = UserCreateSerializer
permission_classes = [AllowAny]
queryset = User.objects.all()
serializer:
class UserCreateSerializer(ModelSerializer):
"""
User register
"""
class Meta:
model = User
fields = [
'username',
'wechat_num',
'password',
]
extra_kwargs = {
"password":{"write_only":True}
}
def create(self, validated_data):
username=validated_data.pop('username')
wechat_num = validated_data.pop('wechat_num')
password=validated_data.pop('password')
user_obj = User(
username=username,
wechat_num=wechat_num,
)
user_obj.set_password(password)
user_obj.save()
group=getOrCreateGroupByName(USER_GROUP_CHOICES.User)
user_obj.groups.add(group)
return validated_data
when I access this APIView, I will get the error:
KeyError at /api/users/register/
"Got KeyError when attempting to get a value for field username on serializer UserCreateSerializer.\nThe serializer field might be named incorrectly and not match any attribute or key on the dict instance.\nOriginal exception text was: 'username'."
but the database it will create the user success.
all test success:
You are popping all of the fields from validated_data, so they won't be in the dictionary when you finally return it.
username=validated_data.pop('username')
wechat_num = validated_data.pop('wechat_num')
password=validated_data.pop('password')
...
return validated_data
Perhaps you want to change it to:
username=validated_data['username']
wechat_num = validated_data['wechat_num']
password=validated_data.pop('password')
...
return validated_data
After defining your class you have to define those field, that what i did and it worked fine.
class UserCreateSerializer(ModelSerializer):
username = serializer.Cahrfield(max_length)
like this way you have to do.
In mine i had same error.
and i forgot to define password.
From the method
def create(self, validated_data):
you should return the created instance. In your case, it should be
return user_obj

check_password() from a user again

I have the following form. How can I check the password from the user again, before the user can change his email address finally? Even if the user is logged in, I just want to be sure that it is really the user. Just a security thing.
How do I do it with .check_password()?
'EmailChangeForm' object has no attribute 'user'
/home/craphunter/workspace/project/trunk/project/auth/user/email_change/forms.py in clean_password, line 43
from django import forms
from django.db.models.loading import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
super(EmailChangeForm, self).__init__(*args, **kwargs)
self.user = user
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
def __init__(self, username=None, *args, **kwargs):
"""Constructor.
**Mandatory arguments**
``username``
The username of the user that requested the email change.
"""
self.username = username
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_email(self):
"""Checks whether the new email address differs from the user's current
email address.
"""
email = self.cleaned_data.get('email')
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email
I would refactor your code to look something like this:
View:
#login_required
def view(request, extra_context=None, ...):
form = EmailChangeForm(user=request.user, data=request.POST or None)
if request.POST and form.is_valid():
send_email_change_request(request.user,
form.cleaned_data['email'],
https=request.is_secure())
return redirect(success_url)
...
Password validation goes to form:
class EmailChangeForm(Form):
email = ...
old_password = CharField(..., widget=Password())
def __init__(self, user, data=None):
self.user = user
super(EmailChangeForm, self).__init__(data=data)
def clean_old_password(self):
password = self.cleaned_data.get('password', None)
if not self.user.check_password(password):
raise ValidationError('Invalid password')
Extract logic from view:
def send_email_change_request(user, new_email, https=True):
site = cache.get_model('sites', 'Site')
email = new_email
verification_key = generate_key(user, email)
current_site = Site.objects.get_current()
site_name = current_site.name
domain = current_site.domain
protocol = 'https' if https else 'http'
# First clean all email change requests made by this user
qs = EmailChangeRequest.objects.filter(user=request.user)
qs.delete()
# Create an email change request
change_request = EmailChangeRequest(
user = request.user,
verification_key = verification_key,
email = email
)
change_request.save()
# Prepare context
c = {
'email': email,
'site_domain': 'dev.tolisto.de',
'site_name': 'tolisto',
'user': self.user,
'verification_key': verification_key,
'protocol': protocol,
}
c.update(extra_context)
context = Context(c)
# Send success email
subject = "Subject" # I don't think that using template for
# subject is good idea
message = render_to_string(email_message_template_name, context_instance=context)
send_mail(subject, message, None, [email])
Don't put complicated things inside views (such as rendering and sending email).
I feel like you answered your own question : )
The docs on the check_password method are here:
https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser.check_password
success = user.check_password(request.POST['submitted_password'])
if success:
# do your email changing magic
else:
return http.HttpResponse("Your password is incorrect")
# or more appropriately your template with errors
Since you're already passing in request.user into your form constructor (looks like you've overriden __init__ for your own reasons) you could put all of your logic in the form without any trouble.
class MyForm(forms.Form):
# ...
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.user = user
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
update after seeing your forms
OK. The main problem is that __init__ has been defined twice, making the first statement useless. Second problem I see is that we'd be doing multiple queries for user when we really don't have to.
We've strayed from your original question quite a bit, but hopefully this is a learning experience.
I've changed only a few things:
Removed the extra __init__ definition
Changed __init__ to accept a User instance instead of a text username
Removed the query for User.objects.get(username=username) since we're passing in a user object.
Just remember to pass the form constructor user=request.user instead of username=request.user.username
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, user=None, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_password(self):
valid = self.user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
def clean_email(self):
email = self.cleaned_data.get('email')
# no need to query a user object if we're passing it in anyways.
user = self.user
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email
Finally since we're talking about good practice here, I'd recommend following through with Skirmantas suggestions about moving your current view code to a form method so you can simply call myform.send_confirmation_email.
Sounds like a good exercise!
thanks again to Yuji. It works when I don't have in my first def __init__ the variable user. I also added in def clean_password the first 2 lines from the def clean_email
from django import forms
from django.db.models.loading import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class EmailChangeForm(forms.Form):
email = forms.EmailField(label='New E-mail', max_length=75)
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_password(self):
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
valid = user.check_password(self.cleaned_data['password'])
if not valid:
raise forms.ValidationError("Password Incorrect")
return valid
def __init__(self, username=None, *args, **kwargs):
"""Constructor.
**Mandatory arguments**
``username``
The username of the user that requested the email change.
"""
self.username = username
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_email(self):
"""Checks whether the new email address differs from the user's current
email address.
"""
email = self.cleaned_data.get('email')
User = cache.get_model('auth', 'User')
user = User.objects.get(username__exact=self.username)
# Check if the new email address differs from the current email address.
if user.email == email:
raise forms.ValidationError('New email address cannot be the same \
as your current email address')
return email

Categories