Django Form Validation don't keep password with ValidationError - django

I have written clean field based validation. To check I enter unmatching passwords this wipe off passwords from both the fields. This can annoy user. I want this like "If error let the user change a bit..don't retype whole password". However username validation keeps the username in the field
after error.Why this is happening?
class signupform(ModelForm):
prmqpass=forms.CharField(widget=forms.PasswordInput())
prmqpass1=forms.CharField(widget=forms.PasswordInput())
class Meta:
model=prmqsignup
exclude=['prmqactivated']
def clean_prmqpass(self):
cd=self.cleaned_data
pwd=cd.get("prmqpass")
if len(str(pwd))<6:
raise ValidationError("Password must be at least of six characters")
if not passstrength(pwd):
raise ValidationError("For stronger security password must contain at least one uppercase,lowercase,number and special character")
return pwd
def clean_prmquname(self):
cd=self.cleaned_data
usrname=cd.get("prmquname")
if len(usrname)<5:
raise ValidationError("Username too short choose more than four characters")
if prmqsignup.objects.exclude(pk=self.instance.pk).filter(prmquname=usrname).exists():
raise ValidationError('Username "%s" is already in use.' % usrname)
return usrname
def clean(self):
cd=self.cleaned_data
pwd=cd.get("prmqpass")
pwd1=cd.get("prmqpass1")
if not pwd==pwd1:
raise ValidationError("Passwords don't match.")
return cd
Why username value(staying in form) and password value(flashing away) after error.See validation logic is same.

From the documentation:
Password input: <input type='password' ...>
Takes one optional argument:
render_value: Determines whether the widget will have a value filled in when the form is re-displayed after a validation error (default is False).
So you need to initialise your inputs like so:
prmqpass = forms.CharField(widget=forms.PasswordInput(render_value=True))

Related

Validation not running in django form

I want to run field validatin on my form, as in form and field validation- using validation in practice.
My form looks like this:
from kapsule.validators import name_zero_min_length, name_max_length
class NameUpdateForm(forms.Form):
name = forms.CharField(
validators=[
name_zero_min_length,
name_max_length
]
)
My validators:
from django.core.exceptions import ValidationError
def name_zero_min_length(name_field):
# Check minimum length
if not len(name_field) > 0:
print('firing zero length')
raise ValidationError(
"My custom error message name must be at least one character"
)
def name_max_length(name_field):
# Check that the name is under the max length
MAX_LENGTH = 200
if len(name_field) > MAX_LENGTH:
print('raising')
raise ValidationError(
"My custom error message name cannot be more than {} characters".format(MAX_LENGTH)
)
My view like this:
def edit_kapsule_name(request, kapsule_pk):
kapsule = Kapsule.objects.get(pk=kapsule_pk)
form = NameUpdateForm(request.POST)
response = {}
print('pre-validation')
if form.is_valid():
print('VALID')
name = form.data.get('name')
kapsule.name = name
kapsule.save(update_fields=['name'])
else:
print('INVALID') # INVALID
print('json') # json
errors = form._errors.as_json()
print(errors) # {"name": [{"message": "This field is required.", "code": "required"}]}
My output is commented in the above code (invalid, and giving a different error that that which I expected).
Why is my custom validation not running?
This seems to match with my model validation (working), and the second reponse here
Well in the comment from your code I can see that the form is invalid and it is complaining about a required field. That might be the cause your validators are not running, according to the docs:
The clean() method on a Field subclass is responsible for running to_python(), validate(), and run_validators() in the correct order and propagating their errors. If, at any time, any of the methods raise ValidationError, the validation stops and that error is raised. This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.
On the other hand, if the field is required, the validation not len(name_field) > 0 has no much sense.
Try calling your validators as part of the clean_name method in your form.

Django form.save() not taking updated form.cleaned_data

I'm lost here and can't figure out what I'm missing, which is probably something stupid, but I need another set of eyes because as far as I can tell this should be working.
What I'm trying to do is allow my users to enter phone numbers in ways they are used to seeing, but then take that input and get a validated international phone number from Twilio and save it. By definition that means it will be in the following format - which is the format I need to have it in the database so that it interacts well with another part of the application:
+17085551212
I've debugged to the point there I know the values are coming in correctly, everything works right if I get an invalid number, etc. For some reason, the updated value is not being passed back to the form when I set form.cleaned_data['office_phone'] prior to the form.save(). So I am getting the original number (708) 555-1212 in the database.
forms.py
class ProfileForm(forms.ModelForm):
office_phone = forms.CharField(max_length=20, label="Office Phone")
views.py
if form.is_valid():
print (form.cleaned_data['office_phone'])
pn = form.cleaned_data['office_phone'].replace(" ","")
try:
response = validator.phone_numbers.get(str(pn))
form.cleaned_data['office_phone'] = str(response.phone_number)
print form.cleaned_data
form.save()
success_message = "Your changes have been saved"
except:
error_message = "The contact phone number you entered is invalid."
console.output
(708) 555-1212
+17085551212
+17085551212
{'office_phone': '+17085551212'}
<tr><th><label for="id_office_phone">Office Phone:</label></th>
<td><input id="id_office_phone" maxlength="20" name="office_phone" type="text" value="(708) 555-1212" /></td></tr>
What am I missing here?
Made an edit: I realise that instead of overriding save, we should instead clean/validate the phone number by using custom validation:
class ProfileForm(forms.ModelForm):
office_phone = forms.CharField(max_length=20, label="Office Phone")
def clean_office_phone(self):
value = self.cleaned_data.get("office_phone")
try:
value = value.replace(" ", "")
response = validator.phone_numbers.get(str(value))
except:
raise ValidationError("The contact phone number you entered is invalid")
return str(response.phone_number)
views.py:
if form.is_valid():
form.save()
success_message = "Your changes have been saved"

Django validate field based on value from another field

I have this django field called is_private indicating whether the posting done by the user is private or not. If the posting is private then a certain field called private_room must be mentioned otherwise a field called public_room is required.
In the clean_private_room and clean_public_room fields I'm doing a check to see the value of is_private. If the room is private then in the clean_public_room method I simply return an empty string "" and the same for clean_private_room otherwise I continue with the validation.
The problem is that checking with self.cleaned_data.get('is_private') is returning different results in those two methods. I tried debugging the code and printed the self.cleaned_data value to the terminal and in one of the methods cleaned data contains one form field and in the other method contains my full posted values.
Here's a part of my code, please read the comments in it to see where I print and what gets printed. I don't know why it's behaving this way.
class RoomForm( forms.ModelForm ):
...
def clean_is_private( self ):
if not 'is_private' in self.cleaned_data:
raise forms.ValidationError("please select the type of room (private/public)")
return self.cleaned_data.get("is_private")
def clean_public_room( self ):
print "<clean_public_room>"
# !!!!!!!!!
# when printing this one I only get one form value which is: public_room
print self.cleaned_data
if self.cleaned_data.get("is_private"):
return ""
# otherwise....
if not self.cleaned_data.get("public_room"):
raise forms.ValidationError(
'you need to mention a public room'
)
return self.cleaned_data[ 'public_room' ]
def clean_private_room( self ):
print "<clean_private_room>"
# !!!!!!!!!
# when printing this one I get all form values: public_room, private_room, is_private
print self.cleaned_data
if not self.cleaned_data.get("is_private"):
return ""
# otherwise....
if not self.cleaned_data.get("private_room"):
raise forms.ValidationError(
'you need to mention a private room'
)
return self.cleaned_data[ 'private_room' ]
Form fields are cleaned in the order they defined in the form. So you just need to put is_private field before the public_room in the fields list.

Django custom validation of multiple fields

for which I want to validate a number of fields in a custom clean method.
I have this so far:
class ProjectInfoForm(forms.Form):
module = forms.ModelChoiceField(
queryset=Module.objects.all(),
)
piece = forms.CharField(
widget=forms.Select(),
required=False,
)
span = forms.IntegerField(
max_value=100,
initial=48
)
max_span = forms.IntegerField(
max_value=100,
initial=0
)
def clean(self):
span = self.cleaned_data['span']
max_span = self.cleaned_data['max_span']
piece = self.cleaned_data.['piece']
# validate piece
try:
Piece.objects.get(pk=m)
except Piece.DoesNotExist:
raise forms.ValidationError(
'Illegal Piece selected!'
)
self._errors["piece"] = "Please enter a valid model"
# validate spans
if span > max_span:
raise forms.ValidationError(
'Span must be less than or equal to Maximum Span'
)
self._errors["span"] = "Please enter a valid span"
return self.cleaned_data
However, this only gives me one of the messages if both clauses invalidate. How can I get all the invalid messages. Also I do not get the field-specific messages - how do I include a message to be displayed for the specific field?
Any help much appreciated.
Store the errors and don't raise them until the end of the method:
def clean(self):
span = self.cleaned_data['span']
max_span = self.cleaned_data['max_span']
piece = self.cleaned_data.['piece']
error_messages = []
# validate piece
try:
Piece.objects.get(pk=m)
except Piece.DoesNotExist:
error_messages.append('Illegal Piece selected')
self._errors["piece"] = "Please enter a valid model"
# validate spans
if span > max_span:
error_messages.append('Span must be less than or equal to Maximum Span')
self._errors["span"] = "Please enter a valid span"
if len(error_messages):
raise forms.ValidationError(' & '.join(error_messages))
return self.cleaned_data
You should write a custom clean_FIELDNAME method in this case. That way, field centric validation errors can later be displayed as such when using {{form.errors}} in your template. The clean method o.t.h. is for validating logic that spans more than one field. Take a look through the link I posted above, everything you need to know about validating django forms is in there.
It happens because you are using raise.
Try replace it by these two line in your code:
del self.cleaned_data['piece']
and
del self.cleaned_data['span']
It appears this has changed in later versions of Django (this seems to work in 2.1 and later):
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error('cc_myself', msg)
self.add_error('subject', msg)
https://docs.djangoproject.com/en/dev/ref/forms/validation/#raising-multiple-errors has more details.

password reset form, own clean method

I want to write my own method for the password reset form to make some tests about the new password (length, characters,...). So I added this class to my forms.py:
class PasswordResetForm(SetPasswordForm):
def clean(self):
if 'newpassword1' in self.cleaned_data and 'newpassword2' in self.cleaned_data:
if self.cleaned_data['newpassword1'] != self.cleaned_data['newpassword2']:
raise forms.ValidationError(("The two password fields didn't match."))
#here the passwords entered are the same
if len(self.cleaned_data['newpassword1']) < 8:
raise forms.ValidationError(("Your password has to be at least 8 characters long"))
if not re.search('\d+', self.cleaned_data['newpassword1']) or not re.search('([a-zA-Z])+', self.cleaned_data['new password1']):
raise forms.ValidationError(("Your password needs to contain at least one number and one character"))
return self.cleaned_data
and in the urls.py I added this:
url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', django.contrib.auth.views.password_reset_confirm, {'template_name':'registration/password_reset_confirm.html',
'set_password_form': PasswordResetForm})
But my own clean method isn't called. Whats wrong with this?
this answer may look stupid and i don't have the justification but i ran in the same problem and i solved it by replacing the
self.cleaned_data['newpassword1']
by
self.cleaned_data.get('newpassword1')
for all the clean_data set:
class PasswordResetForm(SetPasswordForm):
def clean(self):
cleaned_data = self.cleaned_data
if 'newpassword1' in cleaned_data and 'newpassword2' in cleaned_data:
if cleaned_data.get('newpassword1') != cleaned_data.get('newpassword2'):
raise forms.ValidationError(("The two password fields didn't match."))
if len(cleaned_data.get('newpassword1')) < 8:
raise forms.ValidationError(("Your password has to be at least 8 characters long"))
if not re.search('\d+', cleaned_data.get('newpassword1')) or not re.search('([a-zA-Z])+', cleaned_data.get('new password1')):
raise forms.ValidationError(("Your password needs to contain at least one number and one character"))
return cleaned_data
I found the error. It was a mistake in my urls.py. It needed to be:
url(r'^password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',django.contrib.auth.views.password_reset_confirm,{'template_name':'registration/password_reset_confirm.html','set_password_form': PasswordResetForm})
I don't know why I still got an password reset form and not a 404. Maybe someone can tell me. I'm a bit worried that there might still be a possible way to use the standard reset form.