Modelform - validation on form field - django

Excuse my newbieness with django, but I am trying to properly validate a formfield named: domainNm
form
class SubmitDomain(ModelForm):
class Meta:
model = Tld #Create form based off Model for Tld
fields = ['domainNm',]
def clean_domainName(self):
cleanedDomainName = self.cleaned_data.get('domainNm')
if Tld.objects.filter(domainNm=cleanedDomainName).count > 1:
errorMsg = u"Sorry that domain is not available."
raise ValidationError(errorMsg)
else:
return self.cleaned_data
## This creates the form.
form = SubmitDomain()
Currently, if I enter in:
abcdefghidfghiqwertyuiopasdfghjklcvbndmsja.com
or
df.com
or
df.com (again)
Both are valid, but they shouldn't be.
It isn't checking if the domain already exists or not (as outlined in the form) - in which it should
I am not getting any errors either.
Any idea what I'm doing wrong?

I see several problems here.
One, your clean method doesn't match the field name. Instead of clean_domainName it needs to be named clean_domainNm. At least as long as that's your model field name.
Two, your comparison if Tld.objects.filter(domainNm=cleanedDomainName).count > 1: is wrong. count is a method - you need to call it.
if Tld.objects.filter(domainNm=cleanedDomainName).count() > 1:
Three, I think your logic is wrong. That will allow the creation of a second instance of a given name, because count() will return 1, meaning the form is valid. I think you want to check the count() against 0, or better yet use exists() instead:
if Tld.objects.filter(domainNm=cleanedDomainName).exists():
Better still, define the field to be unique=True.

Related

How to have dynamically added fields to specific model fields and not all

i am building an application that has a model with three fields Company,Name, position. in the same model i want to have company name as one field while the user can add name and positions for multiple candidates. the reason am trying to do that is because i didnt find any proper way to set automatically select the foreign key based on the company name entered since foreign key is a drop down list and couldnt figure out the way to make foreign key field equal to company name entered.
appreciate help and suggestions if any for the approach i have in mind.
You need two forms (or more usefully one form and one formset). Use form prefixes to make them distinguishable. Pass both to the template, say as selectform and candidate_formset and in the template, use
{{selectform}}
{{candidate_formset}}
The first is a company-select form. It might, for example, be
class CompanySelectForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['company']
The second is a form, or probably a formset, for entering one, or (via a formset) as many candidates as there are to be entered. It will look like
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['name','position']
Now, you use commit=False (docs) to create objects but not save them. First, process CandidateSelectForm, which will give you a Candidate object with a valid company instance, but not save it. Then process the formset of CandidateForm, again with commit=False, which will give you a list of candidate instances with no company, again unsaved. Finally for each candidate in this list, set the company field of every candidate to the one on the object retrieved by CandidateSelectForm and save it.
It will probably be easier to write a plain view function, than messing around with method overrides trying to get the class-based views to process two forms this way.
Edit - added on request.
The view could be modelled on this one in the Django doc. I've made the obvious changes in line with the earlier part of the answer, but it's probably full of errors and I'm not going to debug it further here
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create form instances and populate with data from the request:
cs_form = CompanySelectForm(request.POST, prefix="cs")
cand_form = CandidateSelectForm( request.POST, prefix="cand")
# check whether it's valid:
if cs_form.is_valid() and cand_form.is_valid():
selector = cs_form.save(commit=False)
candidate = cand_form(commit=False)
candidate.company = selector.company
candidate.save()
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
cs_form = CompanySelectForm( prefix='cs')
cand_form = CandidateSelectForm( prefix='cand')
return render(request, 'name.html', {
"select_form": cs_form,
"candidate_form": cand_form,
})
Once you have got this working for a single candidate, you can progress to turning candidate_form into a formset, documented here which will let you enter any number of candidates to be attached to the one selected company.

django model form clean method with foreign key

I try override clean method for model form with foreign key.
Model:
class Doc(Model):
name = CharField()
doc_type = ForeignKey(DictDocType)
Form:
class DocForm(ModelForm):
class Meta:
model = Doc
fields = '__all__'
def clean_doc_type(self)
doc_type_name = self.cleaned_data['doc_type']
try:
DictDocType.objects.get(name=doc_type_name)
except DictDocType.DoesNotExist:
msg = '{0} does not exist in dictdoc {1}.'.format(
doc_type_name, self.cleaned_data['name'])
raise ValidationError(msg)
return name
In the test I get an error:
KeyError: 'name'.
If I remove self.cleaned_data['name'] from msg - I do not get self.cleaned_data['doc_type'].
Where I'm wrong?
You can't cross reference other fields in clean_foo methods, because not all fields' clean_foo methods are called when you are in one of them. There might be some values of the form that are not populated yet, so clean_name() is not yet called when you call clean_doc_type(), thus you don't have self.cleaned_data['name'].
This should be done in clean method. Django doc very explicitly documented this:
By the time the form’s clean() method is called, all the individual
field clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the
fields you are wanting to validate might not have survived the initial
individual field checks.
Also, your clean method doesn't make much sense and not necessary at all. You wouldn't able to choose a foreignkey that doesn't exist in ModelForm. Even if you force the front end to do so, the field would auto fail the validation and give error:
Select a valid choice. foo is not one of the available choices.

Is there a better way to create this Django form?

This particular form has 2 boolean fields, if the first field is No/False, then the next two fields become required. This form does work, but it seems very ugly to me.
Without clean_approved, approved is not actually required, meaning the required=True argument is just documentation, not working code
The self._errors dict code MIGHT (not in this case) overwrite any other errors that were present in that field.
With all the other ways Django makes it easy to make beautiful code, I must be doing something wrong for this Boolean field (that should be required) and the optionally required fields based on the answer to the first field.
Please show me the right way to do this.
class JobApprovalForm(forms.Form):
approved = forms.NullBooleanField(required=True)
can_reapply = forms.NullBooleanField(required=False)
reject_reason = forms.CharField(required=False, widget=forms.Textarea())
def clean_approved(self):
""" Without this, approved isn't really required! """
if self.cleaned_data['approved'] == None:
raise forms.ValidationError(_("This field is required."))
return self.cleaned_data['approved']
def clean(self):
""" Make the other two fields required if approved == False """
if not self._errors and self.cleaned_data['approved'] == False:
required_msg = [_("If this job is not approved, this field is required")]
if self.cleaned_data['can_reapply'] == None:
self._errors['can_reapply'] = self.error_class(required_msg)
if not self.cleaned_data['reject_reason']:
self._errors['reject_reason'] = self.error_class(required_msg)
return self.cleaned_data
While mit might seem a bit excessive you could consider using an inline form.
The outer Form contains your "approved" setting.
the inline form handles your rejection objects.
This will allow you to separate two different concerns. First case: approval yes/no. Second case: rejection handling
Unfortunately I can't provide you with example code right now,
but please have a look at this SO Question to get an initial idea of this concept
Inline Form Validation in Django

Mark a Django Form in a FormSet as ignore

I'm trying to create a FormSet in which the first Form has an extra check to determine if it should be taken into account or not. The object is pretty simply:
class Entry(models.Model):
customer = models.ForeignKey('Customer')
description = models.CharField(max_length=512)
start_time = models.TimeField('start time')
I have multiple of these objects as Form in a FormSet. However, I set the start_time and the customer field with some javascript in the page. Now, when I change one of the other Entry Forms, I get a validation error on the empty one. Is there a way to check in the validation of the code if the form has an object associated with it and the description field is empty, that the set should just ignore this form?
I've tried overriding the clean() method of the Form, but I cannot find how to check the actual description field from there. Also, if I do find that this Form object should be ignored, how do I mark the form as 'ignore me'?
Or should I be doing this from the FormSet itself? If so, the self.forms is an immutable QueryDict, so it looks like I shouldn't modify that one. How else to mark one of the Forms as 'ignore it'?
Any hints are appreciated!

Removing initial value from form

I have a form like this:
class StoreForm(forms.Form):
title = forms.CharField()
link = forms.URLField(verify_exists=True, required=False, initial='http://')
When I fill out the form and leave the link field untouched, I raise an error, because http:// is obviously not a valid link. What would be the best way to remove the initial value to get a valid form?
You can play with the submitted values by overriding clean_foo() method for field.
class StoreForm(forms.Form):
title = forms.CharField()
link = forms.URLField(verify_exists=True, required=False, initial='http://')
def clean_link(self):
data = self.cleaned_data['link']
if data == 'http://':
return ''
else:
return data
Proper way of doing this I think would be extend the default widget and override value_from_datadict method as can be seen here:
Custom widget with custom value in Django admin
Pseudo-form in Django admin that generates a json object on save
You could also override clean() method on Field (extend URLField).
Idea would be to check if value == initial and return None in that case.
Also keep in mind that verify_exists has some security issues as can be seen here:
https://www.djangoproject.com/weblog/2011/sep/09/security-releases-issued/ (Denial of service attack via URLField)