Django: how to validate the complete form - django

I have a form which has two fields for Integers:
class DemoForm(forms.Form):
b_one = forms.IntegerField(
error_messages={
'required':'Please enter a valid number.'
},
label = 'NumberOne',
required = True,
help_text = 'e.g. 266492'
)
b_two = forms.IntegerField(
error_messages={
'required':'Please enter a valid number.'
},
label = 'NumberTwo',
required = True,
help_text = 'e.g. 262865',
)
and I am validating these fields as
def clean_b_one(self):
self.validate_form(self.cleaned_data['b_one'])
def clean_b_two(self):
self.validate_form(self.cleaned_data['b_two'])
Now what I want to do is in validate_form I check, if these numbers exists in database, else raising forms.ValidationError
But what I also want to do some other validations when these form fields are valid, basically some check on the form based on input and raise some custom errors, where can I add logic? or what is the best way of doing it?

You can do individual field verifying in clean_b_one like you have, and raise ValidationErrors if something doesn't fit. Or override the clean method to do cross-field checking. General documentation to be found here.

Related

Make a form that pulls choices from table in DB and then allows user to change to different foreign keys

So I have a user model with the following columns:
username = models.CharField(db_column='Username',max_length=32,unique=True)
email = models.CharField(db_column='Email',max_length=255)
password = models.CharField(db_column='Password',max_length=128)
prosthodontist = models.ForeignKey('Prosthodontist',on_delete=models.SET_NULL,null=True)
I'm trying to make a dropdown that allows the user to change their Prosthodontist value through django forms. It can't be a static list cause it has to always have every available Prosthodontist as they get added.
Just for show this is what I have so far along the lines of the form:
class ChangeProsthodontistForm(forms.ModelForm):
class Meta:
model = User
fields = ('prosthodontist',)
prosthodontist = forms.ChoiceField(
label = "Prosthodontist",
widget = forms.Select(
attrs={
'id':'prosthodontist',
},
),
choices=()
)
Please help me with this cause I'm really confused I feel like I could be able to iterate through the entries with a for loop but I feel like there has to be a better way through Django.
You answer is ModelChoiceField.
prosthodontist = forms.ModelChoiceField(
# ...
queryset = Prosthodontist.objects.all(),
# ...
)

Conditional validation based on two inputs

I have a model with two fields:
class Books(model.Models)
status = models.IntegerField(
null = False, blank = False, default = None,
choices = [(1,'in stock'),(2,'out of stock'),(3,('other, specify below')]
)
other = models.TextField(
blank = True, null = True,
)
I then use a RadioSelect in my form widget to render status and all is well with the world.
However, I want to make other required if option 3 in status is selected. How can I do this server side via form validation?
E.g. the form should fail validation if choice 3:'other' is selected and other remains blank or null.
If you want to validate your data in the form, you should override the clean method and add your conditions there:
class YourForm(forms.Form): # or a ModelForm
#...
def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get("status")
other = cleaned_data.get("other")
if status == 3 and (not other):
raise forms.ValidationError('Provide other field')
return data

How do you validate a formset with a dynamically updating Choicefield?

I am using formset_factory to manage a couple identical forms on my page. In each form, there is a pair of chained dropdowns. DropdownA has an onchange event that request options for dropdownB (AJAX). This all works fine but when I go to submit my forms via a POST request, they all fail the forms.is_valid() check. Printing the errors of the submitted formset reveals why:
[{'DropdownB ': ['Select a valid choice. Like is not one of the available choices.']}, {'DropdownB ': ['Select a valid choice. < is not one of the available choices.']}]
There are two errors, one for each form. They are both complaining that the choice sent for DropdownB is not one of the available ('Like' and '<' respectfully).
Now, because I want to only populate DropdownB with certain choices based on what DropdownA selected, I purposefully defined DropdownB (a choicefield) with 0 choices.
DropdownB = ()
DropdownB = forms.ChoiceField(choices=op_choices, required=False)
How do I specify to the server what the valid choices are BASED ON what DropdownA's value is?
I tried to simplify this problem in the abstract above, but if you want the full code of the form, here you go:
class UnifiedSingleSearchBar(forms.Form):
# Dict to categorize field types
type_dict = {
'DateField': 'Numeric',
'DateTimeField': 'Numeric',
'AutoField': 'Numeric',
'CharField': 'String',
'BooleanField': 'Bool',
}
operation_dict = {'Numeric':
(
('>', '>'),
('>=', '>='),
('<', '<'),
('<=', '<='),
('=', '='),
('>-<', 'Between'),
('0', 'IS null'),
('1', 'IS NOT null'),
),
'String':
(
('Like', 'Like'),
('Is', 'Is')
),
'Bool':
(
('True', 'True'),
('False', 'False')
)
}
searchabel_field_choices = ()
# To create the "field" dropdown, we loop through every field in the model and note its type.
for field in Mymodel._meta.fields:
tuple = (
(field.name, field.name), # signifies a nested tuple
)
searchabel_field_choices = searchabel_field_choices + tuple
searchabel_field_choices = searchabel_field_choices + (('', '--------'),)
shared_attrs = {
'autocomplete': 'off',
'class': 'form-control datetimepicker-input',
}
searchable_field = forms.ChoiceField(choices=searchabel_field_choices, required=False)
op_choices = () # Should always start with an empty operations list since field has not yet been chosen
operation = forms.ChoiceField(choices=op_choices, required=False)
# 2 is usually only ever used if a range is being specified
# Numeric
date1 = forms.DateField(required=False, widget=DatePicker(attrs=shared_attrs))
date2 = forms.DateField(required=False, widget=DatePicker(attrs=shared_attrs))
datetime1 = forms.DateTimeField(required=False, widget=DateTimePicker(attrs=shared_attrs))
datetime2 = forms.DateTimeField(required=False, widget=DateTimePicker(attrs=shared_attrs))
integer = forms.IntegerField(required=False)
# Bool
bool = forms.BooleanField(required=False)
# String
string = forms.CharField(required=False)
The answer Here fixed my problem. Each time you loop over a form in your formset, you read the value in DropdownA specified for that form in the request, and use that information to set the valid choices for DropdownB, again per that specific form.
EDIT: Adding extra info for those that still need clarification.
So after receiving the forms from the client, somewhere before you validate them with forms.is_valid() you would for loop over them, inspect the value specified for dropdownA and update the choices for dropdownB.
Most likely you are populating the dropdowns with values from your DB, so this usually makes these checks and updates easy. first you would see if the value specified by the client for dropdownA is a valid member of the database (gotta make sure no funny business went on)
model1_record = model1.objects.get(<dropdownA val>)
then assuming dropdownA is found in the DB, you are probably trying to limit the valid dropdownB choices to records associated to the one you identified already. You do this by updating dropdownB's queryset like so form.fields['dropdownB'].queryset = model2.objects.filter(model1FK=model1_record)
After doing that for all forms, now you can call forms.is_valid() and you shouldn't get that invalid choices error (well, if that ddB is indeed related to ddA).

How to validate number in django

I am learning Django,looked into django validation but the below type i want.searched in google no result.
In my app,their are two character fields,i want it to be validate so that the conditons are,
1.Either any one of the field is entered.
2.It should validate the entered data are integer.
that means,both fields are not mandatory,but any one is mandatory and that mandatory field should accept number only.
How to do it in django.
class MyForm(forms.Form):
field_one = forms.IntegerField(required=False)
field_two = forms.IntegerField(required=False)
def clean(self):
cleaned_data = self.cleaned_data
field_one = cleaned_data.get('field_one')
field_two = cleaned_data.get('field_two')
if not any([field_one, field_two]):
raise forms.ValidationError(u'Please enter a value')
return cleaned_data
Using an IntegerField will validate that only numeric characters are
present, covering your blank space use case.
Specifying required=False on both fields allows either field to be left blank.
Implementing clean() on the form gets you access to both fields.
.get() will return None if the key isn't found, so the use of
any([field_one, field_two]) will return true if at least one of the
values in the list isn't None. If neither value is found, the
ValidationError will be raised.
Hope that helps you out.

Django get_FIELD_display

I am trying to access data.get_age_display in my email template. I can't seem to get the display of this. I am not sure what I am doing wrong, I've using get_FIELD_display numerous times before but passed as context to a normal template. Is there something different with forms?
class RequestForm(forms.Form):
ADULT = 1
SENIOR = 2
STUDENT = 3
AGE_GROUP = (
(ADULT, 'Adult'),
(SENIOR, 'Senior'),
(STUDENT, 'Student'),
)
name = forms.CharField(max_length=255)
phone = forms.CharField(max_length=15)
age = forms.ChoiceField(choices=AGE_GROUP)
details = forms.CharField(widget=forms.Textarea())
def save(self):
order = Order(
name = self.cleaned_data['name'],
phone = self.cleaned_data['phone'],
age = self.cleaned_data['age'],
details = self.cleaned_data['details'],
)
order.save()
template = loader.get_template('request_email.txt')
# send over the order object in an email extracted so they can handle the ticket order
context = Context({
'data': order,
})
#import pdb; pdb.set_trace()
email_subject = 'Request Tickets'
mail_managers(email_subject, template.render(context))
in my request_email.txt all I am doing is {{ data.get_age_display }} any ideas?
Jeff
You haven't shown the code for the Order model that you're creating. Are you sure that the age field on the model has choices set?
Any reason you're not using a ModelForm? You're creating an Order object within the form's save() method, but not returning it. A modelform would do that for you, as well as removing the need to redeclare the fields for the form.
I know this is coming WAAAAAY later than the question being posted but here's my answer for completeness and anyone else who might benefit from it :-)
I'm going to assume that in AGE_GROUP, ADULT, SENIOR and STUDENT are integers. Your form cleaning will NOT automatically clean the string contained in the POST and return an integer. So in this code:
context = Context({
'data': order,
})
you would think order.age is referring to an integer but that is, in fact, incorrect. It's burned me a few times before because this will correctly save the integer to the physical table, but the order instance still has the string representation of the age field.
You could do one of two things:
1. Clean this in the field:
clean_age(self):
return int(self.cleaned_data['age'])
or create a new field type:
def MyChoiceField(forms.ChoiceField):
def clean(self, value):
if not value:
if self.required:
raise forms.ValidationError(self.error_messages['required'])
return None
else:
return None
return int(value)
link that to the form field:
age = MyChoiceField(choices=AGE_GROUP)
and then you'll be able to apply this logic to any other such choice field in future. Personally, I find the latter approach the best one and I stick all my custom field types into a form_utils file so that I can use them everywhere. Another gotcha is that forms.charField doesn't automatically strip the entered text and you can use this approach to fix that too.