why comes a keyerror instead of a validation error when one field is empty? The fields should be required=True by default
class form(forms.ModelForm):
adminAccount = forms.CharField()
adminPassword = forms.CharField(widget=forms.PasswordInput)
def userCheck(self, user, password):
# do something
def clean(self):
self.userCheck(self.cleaned_data['adminAccount'],
self.cleaned_data['adminPassword'])
It is your code that is raising the KeyError here:
self.userCheck(self.cleaned_data['adminAccount'],
self.cleaned_data['adminPassword'])
Because you're trying to access self.cleaned_data[field] when field was not posted.
The documentation provides an example that explains how to validate data that depends on more than one field. According to the examples you should do something like:
cleaned_data = super(form, self).clean()
adminAccount = cleaned_data.get('adminAccount')
adminPassword = cleaned_data.get('adminPassword')
if adminAccount and adminPassword:
# proceed with your validation
return cleaned_data
Also, remember that Form.clean() must return the cleaned_data dict.
Related
Django form the cleaned_data field is None.
This field has not passed the validation.
I want to change the value of this field.
Is there another way to get the non-valid fields?
def clean(self):
cleaned_data = super(Form, self).clean()
print(cleaned_data.get('info')) <---- It is None
return cleaned_data
If cleaned_data is None, it should be because your existing form fields have not been validated or there is no data in them.
You can try something like this:
class Form1(forms.Form):
# Your fields here
def clean(self):
if self.is_valid():
return super(forms.Form, self).clean() # Returns cleaned_data
else:
raise ValidationError(...)
EDIT: Taking note of what #Alasdair said - the following approach is better:
You could consider changing the value of 'info' beforehand, i.e. in the view, like so, instead of overriding the form's clean() method:
# In the view
data = request.POST.dict()
data['info'] = # your modifications here
form = Form1(data)
if form.is_valid():
...
I have a simple model form, to which I've added a simple checkbox:
class OrderForm(forms.ModelForm):
more_info = models.BooleanField(widget=forms.CheckboxInput())
def clean(self):
if 'more_info' not in self.cleaned_data:
self.instance.details = ""
class Meta:
model = Order
fields = ('details', 'address', ) # more fields
But this does not work and the 'details' fields is still updated by the user value even if the checkbox is not selected (and the if block is executed, debugged). I've also tried changing self.cleaned_data['details'] instead of self.instance.details but it does not work either.
This is not so important, by in the client side I have a simple javascript code which hide/show the details field if the checkbox is selected.
class OrderForm(forms.ModelForm):
more_info = models.BooleanField(required=False)
def clean(self):
cleaned_data = super().clean()
if not cleaned_data['more_info']:
cleaned_data['details'] = ''
return cleaned_data
From Customizing validation:
This method [clean()] can return a completely different dictionary if it wishes, which will be used as the cleaned_data.
Also:
CheckboxInput is default widget for BooleanField.
BooleanField note:
If you want to include a boolean in your form that can be either True or False (e.g. a checked or unchecked checkbox), you must remember to pass in required=False when creating the BooleanField.
Instead of updating cleaned_data, try overriding the save method instead
def save(self, force_insert=False, force_update=False, commit=True, *args, **kwargs):
order = super(OrderForm, self).save(commit=False)
if not self.cleaned_data.get('more_info', False):
order.details = ""
if commit:
order.save()
return order
Additionally, if you want to use the clean method you need to call super's clean first.
def clean(self):
cleaned_data = super(BailiffAddForm, self).clean()
if not cleaned_data.get('more_info', False):
...
return cleaned_data
I have a model that the clean that raises a ValidationError in the model, and then I customized the form in the admin. The "myfield" is a ForeingKey.
class MyModel(models.Models):
myfield = models...
def clean(self):
if check_something_on(self.myfield):
raise ValidationError("Technical Error in myfield")
def save(self, *kwargs):
self.clean()
super(MyModel, self).save(*kwargs)
I expect that by the shell system or other methods, the myfield field is always OK.
Then, I added in the admin a form that looks like this:
class MyModelAdminForm(forms.ModelForm):
myfield = form...
def clean(self):
cleaned_data = super(MyModelAdminForm, self).clean()
myfield = cleaned_data['myfield']
if check_something_on(myfield):
forms.ValidationError("User helping Error in myfield")
return cleaned_data
What happens is that in the admin page, I end up with both messages, the technical error and the user helping error...
Is there any way to prevent this?
Thanks
When overwriting a form clean method how do you know if its failed validation on any of the fields? e.g. in the form below if I overwrite the clean method how do I know if the form has failed validation on any of the fields?
class PersonForm(forms.Form):
title = Forms.CharField(max_length=100)
first_name = Forms.CharField(max_length=100)
surname = Forms.CharField(max_length=100)
password = Forms.CharField(max_length=100)
def clean(self, value):
cleaned_data = self.cleaned_data
IF THE FORM HAS FAILED VALIDATION:
self.data['password'] = 'abc'
raise forms.ValidationError("You have failed validation!")
ELSE:
return cleaned_data
Thanks
You can check if any errors have been added to the error dict:
def clean(self, value):
cleaned_data = self.cleaned_data
if self._errors:
self.data['password'] = 'abc'
raise forms.ValidationError("You have failed validation!")
else:
return cleaned_data
BONUS! You can check for errors on specific fields:
def clean(self, value):
cleaned_data = self.cleaned_data
if self._errors and 'title' in self._errors:
raise forms.ValidationError("You call that a title?!")
else:
return cleaned_data
If your data does not validate, your
Form instance will not have a
cleaned_data attribute
Django Doc on Accessing "clean" data
Use self.is_valid().
Although its old post, if you want to apply validations on more than 1 field of same form/modelform use clean(). This method returns cleaned_data dictionary.
To show the errors to users you can use add_error(<fieldname>, "your message") method. This will show the errors along with the field name rather on top of the form. The example is shown below:
add_error() automatically removes the field from cleaned_data dictionary, you dont have to delete it manually. Also you dont have to import anything to use this.
documentation is here
def clean(self):
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
msg = 'passwords do not match'
self.add_error('password2', msg)
return self.cleaned_data
If you just want validation on single field of form/modelform use clean_<fieldname>(). This method will take the values from cleaned_data dictionary and then you can check for logical errors. Always return the value once you are done checking logic.
def clean_password(self):
password = self.cleaned_data['password']
if len(password)<6:
msg = 'password is too short'
self.add_error('password', msg)
return password
Here is a simple example of overriding clean() in django.forms.Form and also using django-braces for AnonymousRequiredMixin to require that only anonymous users visit the Loing Page:
class LoginView(AnonymousRequiredMixin, FormView):
"""
Main Login. And Social Logins
"""
template_name = 'core/login.html'
form_class = LoginForm
success_url = reverse_lazy('blog:index')
def get_success_url(self):
try:
next = self.request.GET['next']
except KeyError:
next = self.success_url
return next
def form_valid(self, form):
cd = form.cleaned_data
user = auth.authenticate(username=cd['login_username'],
password=cd['login_password'])
if user:
auth.login(self.request, user)
messages.info(self.request, 'You are logged in.')
return super(LoginView, self).form_valid(form)
In a custom Form, how does one validate a Model's field's uniqueness (i.e., has unique=True set)?
I know that django's ModelForm automatically performs a validate_unique() function that is called within the BaseModelForm's clean() method -- so, when using ModelForm, this will be handled correctly (as it is in the Admin).
However, I am creating my own form from scratch and wonder how I can go about handling this myself? I think my biggest stumbling block is knowing which object is attached to the form when the data is being cleaned ...
Some code:
class UserProfile(CreatedModifiedModel):
user = models.ForeignKey(User, unique=True)
display_name = models.CharField('Display Name',max_length=30,
blank=True,unique=True)
class EditUserProfileForm(forms.Form):
display_name = forms.CharField(required=False,max_length=30)
# "notifications" are created from a different model, not the UserProfile
notifications = forms.MultipleChoiceField(
label="Email Notifications",
required=False,
widget=forms.CheckboxSelectMultiple,)
def clean_display_name(self):
# how do I run my own validate_unique() on this form?
# how do I know which UserProfile object I am working with?
# more code follows, including the __init__ which sets up the notifications
Unique validation is hard to get completely right, so I would recommend using a ModelForm anyways:
class EditUserProfileForm(forms.ModelForm):
# "notifications" are created from a different model, not the UserProfile
notifications = forms.MultipleChoiceField(
label="Email Notifications",
required=False,
widget=forms.CheckboxSelectMultiple,)
class Meta:
model = UserProfile
fields = ('display_name',)
Making a form from multiple models is not easy, but in this case you can just add the notifications field onto the ModelForm and pull it out of .cleaned_data as usual:
# view
if request.method == 'POST':
form = EditUserProfileForm(request.POST, instance=user_profile)
if form.is_valid():
user_profile = form.save()
notifications = form.cleaned_data['notifications']
# Do something with notifications.
That's how I would do it, but if you're set on validating unique yourself, you can always do something like:
def clean_display_name(self):
display_name = self.cleaned_data['display_name']
if UserProfile.objects.filter(display_name=display_name).count() > 0:
raise ValidationError('This display name is already in use.')
return display_name
There are two problems I see here. First, you can run into concurrency issues, where two people submit the same name, both pass unique checks, but then one gets a DB error. The other problem is that you can't edit a user profile because you don't have an ID to exclude from the search. You'd have to store it in your __init__ and then use it in the cleaning:
def __init__(self, *args, **kwargs):
...
if 'instance' in kwargs:
self.id = kwargs['instance'].id
...
def clean_display_name(self):
display_name = self.cleaned_data['display_name']
qs = UserProfile.objects.filter(display_name=display_name)
if self.id:
qs = qs.exclude(pk=self.id)
if qs.count() > 0:
raise ValidationError('This display name is already in use.')
return display_name
But at that point you're just duplicating the logic in ModelForms.
Working Example !
I am using email as unique field
In models.py use below code
User._meta.get_field('email')._unique = True
In forms.py use below code
class ProfileForm(forms.ModelForm):
email = forms.EmailField(max_length=255, required=True)
def clean_email(self):
user_id = self.cleaned_data['user'].id
email = self.cleaned_data['email']
obj = User.objects.filter(email=email).exclude(id = user_id)
if obj:
raise forms.ValidationError('User with this email is already exists.Try a new email')