How to populate two models using single Form - django

class CustomAccount(models.Model):
user = models.OneToOneField("auth.User")
role = models.CharField(max_length = 50, default = 'student', choices=APPROVAL_CHOICES)
balance = models.FloatField( default = 0 )
timezone = models.CharField(max_length = 200)
def __str__(self):
return self.user.username +" ["+ self.role + "]"
class CustomAccountForm(forms.ModelForm):
username = forms.CharField(max_length=30 )
email = forms.EmailField(max_length=255 )
password1 = forms.CharField(label= "Password",widget=forms.PasswordInput())
password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput , help_text="Enter the same password as above, for verification.")
def save(self, commit= True):
user = User.objects.create_user(username = self.cleaned_data['username'], email = self.cleaned_data['email'] , password = self.cleaned_data['password1'])
user.save()
self.user = user
return super(CustomAccountForm, self).save(commit=commit)
def clean_username(self):
username = self.cleaned_data["username"]
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError("A user with that username already exists.")
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError("The two password fields didn't match.")
return password2
def clean_email(self):
email = self.cleaned_data["email"]
try:
User.objects.get(email=email)
except User.DoesNotExist:
return email
raise forms.ValidationError("A user with that emailaddress already exists.")
class Meta:
model = CustomAccount
exclude = ['balance','user']
I want to create Custom account in Django Admin section using single form which has fields of auth.User and CustomAccount Model. I have getting error IntegrityError at /admin/mylogin/customaccount/add/
NOT NULL constraint failed: mylogin_customaccount.user_id

Since you are getting a not null error, I think you need to specify that the field is not required. Here is an example from one of my apps. This says that the form field is not required.
class arunModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
myuser = kwargs.pop('user', None)
super(arunModelForm, self).__init__(*args, **kwargs)
self.fields['nanoadded'].required = False
self.fields['response'].required = False
I also include this in my models so that my model fields can be null, like this:
class arun(models.Model):
auser = models.CharField(max_length=50, null=True)
adate = models.DateField(null=True)
atime = models.TimeField(null=True)
If I still get not null errors after I do both of these things, I fill the field with a place holder value to allow me to further investigate the problem.

You have removed the user from the form and in the save method, you have assigned self.user in save method. So when saving the form, the user attribute is not used for CustomAccountForm and the user field for CustomAccount is None.
Your save method should look like:
def save(self, commit= True):
user = User.objects.create_user(username =self.cleaned_data['username'], email = self.cleaned_data['email'] , password = self.cleaned_data['password1'])
user.save()
obj = super(CustomAccountForm, self).save(commit=False)
obj.user = user
obj.save()

Related

Django Form not saving for Custom User Model

I have a register user form which is doing all the validation as expected. However, it is not saving. I am not able to figure out the reason. How do I debug it ? Any help ? I am a newbie to forms and formviews any good document with example would really help me.
class RegisterForm(forms.ModelForm):
phone_number = forms.IntegerField(required=True)
password1 = forms.CharField(widget=forms.PasswordInput())
password2 = forms.CharField(widget=forms.PasswordInput())
country_code = forms.IntegerField()
#schools = school.objects.all()
#school_name = forms.ModelChoiceField(queryset=school.objects.distinct())
MIN_LENGTH = 4
class Meta:
model = User
fields = ['username','country_code','phone_number', 'password1', 'password2',
'full_name' ]
def clean_phone_number(self):
phone_number = self.data.get('phone_number')
print(phone_number)
if User.objects.filter(phone_number=phone_number).exists():
raise forms.ValidationError(
_("Another user with this phone number already exists"))
if len(phone_number) == 10 and phone_number.isdigit():
pass
else:
raise forms.ValidationError(
_("Invalid Phone Number"))
return phone_number
def save(self, *args, **kwargs):
print("saving")
user = super(RegisterForm, self).save(*args, **kwargs)
user.set_password(self.cleaned_data['password1'])
print('Saving user with country_code', user.country_code)
user.save()
return user
Views.py
class RegisterView(SuccessMessageMixin, FormView):
template_name = 'register-2.html'
form_class = RegisterForm
success_message = "One-Time password sent to your registered mobile number.\
The verification code is valid for 10 minutes."
def form_valid(self, form):
full_name=self.request.POST["full_name"]
user = form.save()
print(user.id)
username = self.request.POST['username']
password = self.request.POST['password1']
user = authenticate(username=username, password=password)
kwargs = {'user': user}
self.request.method = 'POST'
print("User created")
The print in clean_phone_number works however, save does not work
I had issue in the my form. One of the field was disabled and the value was not captured because of that.
However to identify that I used
def form_invalid(self,form):
# Add action to invalid form phase
messages.error(self.request, form.errors)
return self.render_to_response(self.get_context_data(form=form))

How to check password against previously used passwords in django

I have the following model for storing previously used hashed passwords:
class PasswordHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
password = models.CharField(max_length=128, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
In the change password form I want check if the new password the user is changing to has not been used the past 5 times.
Here is my form validation:
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
user_id = None
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
hashed_password = make_password(password1)
password_histories = PasswordHistory.objects.filter(
user=user,
password_hashed_password
)
if password_histories.exists():
raise forms.ValidationError('That password has already been used')
return password2
The problem is that the passwords are different every time, even when I attempt the same plain text password over and over again. Therefore:
if password_histories.exists():
Never returns true.
How can I compare past passwords if they are always different due to salt?
Thanks
The .set_password function indeed does not return anything, it simply sets the password. Like you say however, the hashing is based on a (random) salt, and thus the hash will be different each time. Therefore you should use the .check_password(…) function [Django-doc], to verify if it somehow matches a hashed variant:
from django.contrib.auth.hashers import check_password
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
password_histories = PasswordHistory.objects.filter(
user=user
)
for pw in password_histories:
if check_password(password2, pw.password):
raise forms.ValidationError('That password has already been used')
return password2
So if we found a hashed password that matches the given raw password, we can return the password. If by the end of the for loop, we did not find any such password, we can return password2, otherwise we raise an errro.

Django display forms.ValidationError at template

My Django application uses a token based password reset mechanism, but I'm currently not able to display any error message at my template if "user is not None and str(form_token) == str(user.reset_token)" is not given
-> Error if my input is a none existing user:
File "/app/App_Accounts/views.py", line 280, in reset_password
user = form.save()
File "/app/App_Accounts/forms.py", line 315, in save
user = User.objects.get(user=form_user)
App_Accounts.models.User.DoesNotExist: User matching query does not
exist.
views.py:
def reset_password(request):
if request.method == 'POST':
form = TokenPasswordResetForm(request.POST)
if form.is_valid():
user = form.save() # line 280
....
forms.py:
class TokenPasswordResetForm(forms.Form):
error_messages = {
'password_mismatch': _('Password fields didn’t match, please try again'),
'user_or_token_mismatch': _('Unable to find user or token match'),
}
user = forms.CharField(
required=True,
strip=False,
)
reset_token = forms.CharField(
required=True,
strip=False
)
new_password1 = forms.CharField(
label=_("New Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
)
...
def __init__(self, *args, **kwargs):
super(TokenPasswordResetForm, self).__init__(*args, **kwargs)
self.fields['user'].label = 'Username:'
...
def save(self, commit=True):
"""Save the new password."""
form_user = self.cleaned_data.get('user')
form_token = self.cleaned_data.get('reset_token')
user = User.objects.get(user=form_user) # line 315
if user is not None and str(form_token) == str(user.reset_token):
password = self.cleaned_data["new_password1"]
user.set_password(password)
user.reset_token_memorized = False
user.reset_token = reset_token_generator()
if commit:
user.save()
return user
raise forms.ValidationError(
self.error_messages['user_or_token_mismatch'],
code='user_or_token_mismatch',
)
models.py
class User(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.CharField(verbose_name='Username', unique=True)
...

Field validation in a Django form

I have a Django form consisting of a email and name field. I want to validate the name to have more than 8 characters. I have used the following code. But it is not working.
class SignUpForm(forms.ModelForm):
class Meta:
model=SignUp
fields=('email','name')
def emailValidation(self):
name=self.cleaned_data.get('name')
if len(name) <=8:
raise forms.ValidationError("name cannot be less than 8")
models.py
class SignUp(models.Model):
name=models.CharField(max_length=200)
email=models.EmailField()
timestamp=models.DateTimeField(auto_now_add=True, auto_now=False)
updated=models.DateTimeField(auto_now=True,auto_now_add=False)
def __unicode__(self):
return self.name
views.py
def home(request):
form=SignUpForm(request.POST or None)
if form.is_valid():
instance=form.save(commit=False)
instance.save()
print instance.timestamp
return render(request, 'home.html',{'form':form})
In your SignUpForm, in the function emailValidation, you haven't returned 'name'. Also a major mistake is that you have to name the function clean_(field_name) and NOT emailValidation.
This should do it I guess:
class SignUpForm(forms.ModelForm):
class Meta:
model=SignUp
fields=('email','name')
def clean_name(self):
name=self.cleaned_data.get('name')
if len(name) <=8:
raise forms.ValidationError("name cannot be less than 8")
return name
You need to use correct name for your validation method. Django forms will call methods with the format clean_<fieldname>.
Also you seem to be confused about which field you are validating; your email validation method should be called clean_email and should access the email value via form.cleaned_data['email'], and the name one should be called clean_name and access form.cleaned_data['name'].
Something like this may give you some guidance.
class RegistrationForm(forms.ModelForm):
"""
Form for registering a new account.
"""
firstname = forms.CharField(label="First Name")
lastname = forms.CharField(label="Last Name")
phone = forms.CharField(label="Phone")
email = forms.EmailField(label="Email")
password1 = forms.CharField(label="Password")
password2 = forms.CharField(label="Password (again)")
min_password_length = 8
class Meta:
model = User
fields = ['firstname', 'lastname', 'phone', 'email', 'password1', 'password2']
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError(u'Email "%s" is already in use! Please log in or use another email!' % email)
return email
def clean_password1(self):
" Minimum length "
password1 = self.cleaned_data.get('password1', '')
if len(password1) < self.min_password_length:
raise forms.ValidationError("Password must have at least %i characters" % self.min_password_length)
else:
return password1
def clean(self):
"""
Verifies that the values entered into the password fields match
NOTE: Errors here will appear in ``non_field_errors()`` because it applies to more than one field.
"""
cleaned_data = super(RegistrationForm, self).clean()
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("Passwords didn't match. Please try again.")
return self.cleaned_data
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user

UserProfile Registration erases all rows

I have created a user registration view and this is it:
def register(request):
if request.method == 'POST':
data = request.POST.copy() # so we can manipulate data
# random username
data['username'] = hashlib.md5( data['email'] ).hexdigest()
data['username'] = data['username'][0:30]
#data['username'] = ''.join([choice(letters) for i in xrange(30)])
form = RegisterForm(data)
if form.is_valid():
new_user = form.save()
#UserProfile.objects.create(user=new_user)
return HttpResponse("Thanks for Registering")
else:
form = RegisterForm()
return render_to_response("CTUser/register.html", { 'form': form, })
When I uncommented the UserProfile line:
#UserProfile.objects.create(user=new_user)
the email and password are saved correctly, but when It is there, all the info is erased. Am I doing something wrong here?
here is the UserProfile classs:
class UserProfile(User):
user = models.OneToOneField(User)
#user = models.ForeignKey(User, unique=True)
#profile sub URL
pagelink = models.CharField(max_length=40)
#one or many albums
albums = models.ManyToManyField(Album)
UPDATE(10/19/11):
Here is the registration form function:
class RegisterForm(UserCreationForm):
email = forms.EmailField(label = "Email Address", max_length=75)
class Meta:
model = User
#exclude = ['username',]
fields = ("username", "email")
def clean_email(self):
email = self.cleaned_data["email"]
try:
user = User.objects.get(email=email)
raise forms.ValidationError("This email address already exists. Did you forget your password?")
except User.DoesNotExist:
return email
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user.email = self.cleaned_data["email"]
user.is_active = True # change to false if using email activation
if commit:
user.save()
return user
Check if new_user contains all data you want to save. Paste RegisterForm code.
EDIT
try to change:
user = super(UserCreationForm, self).save(commit=False)
into
user = super(RegisterForm, self).save(commit=False)