Django validate field based on value from another field - django

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.

Related

forms.ValidationError bug?

this is my third day in django and I'm working on my validation form and i came across this weird bug where my validation form didn't do anything so here's the code
class RegisterForm(forms.ModelForm):
email = forms.EmailField(label="E-Mail", error_messages={'required': 'Please enter your name'})
class Meta:
model = LowUser
fields =['fullname', 'email', 'password', 'bio']
widgets = {
'password' : forms.PasswordInput()
}
def clean_fullname(self):
data = self.cleaned_data.get("fullname")
if 'ab' in data:
raise forms.ValidationError('invalid')
else:
return data
if i input "ac" to the fullname it works perfectly fine it adds the input to the database. But if i input "ab" it didn't do anything it doesn't give me any errors nor add the input to my database. And I'm pretty sure my forms.ValidationError is bugging because if i change my raise forms.ValidationError('invalid') to raise NameError('Test') like this
def clean_fullname(self):
data = self.cleaned_data.get("fullname")
if 'ab' in data:
raise NameError('Test')
else:
return data
and i input "ab". It works completely fine and it gave me this page
and I'm using django 2.1.5 if you're wondering i would appreciate any help
thank you in advance
If i input "ac" to the fullname it works perfectly fine it adds the input to the database. But if i input "ab" it didn't do anything it doesn't give me any errors nor add the input to my database.
That is expected behavior, ValidationErrors are used to collect all errors.
The idea is that you raise ValidationErrors. These are all collected, and when one such error is present, form.is_valid() will return False, and the form.errors will contain a dictionary-like object with all the errors. The reason this is done is to collect problems with all fields in one single pass, such that it does not only report the first error of the form data.
Imagine that you have five fields with mistakes, and the form only reports problems with the first field. Then it takes five rounds before all fields are validated. By collecting all errors, it can show multiple ones. You can even return multiple errors on the same field.
For more information, see the raising ValidationError` section of the Django documentation.
Thanks to Willem i realized that the problem was in my views.py.
def registerForm(request):
regisform = RegisterForm()
cntxt = {'mainregisform': regisform, 'tst': ''}
if request.method == 'POST':
regisform = RegisterForm(request.POST)
if regisform.is_valid():
regisform.save()
return render(request, 'userregister.html', cntxt)
i thought that the ValidationError wasn't giving me any errors because usually there's an error message on top of my input box, but it actually did gave me an error. the problem was i define the mainregisform before the regisform got re-rendered therefore i never got error message

Custom admin form error "need more than 1 value to unpack"

I have been adapting this article How to Add Custom Action Buttons to Django Admin to my particular circumstance and things are working pretty well except for this one strange error I am getting. In my forms.py I have defined class DenyForm: (Note the request parameter in form_action is not the usual API request. I have a model class Request.) What should form_action be returning?
class DenyForm(ApproveOrDenyForm):
def form_action(self, request, admin_approver ):
print "forms DenyForm Called."
justification = self.cleaned_data['justification']
request.admin_deny(admin_approver=admin_approver,justification=justification)
return [request]
#return request.admin_deny(admin_approver=admin_approver,justification=justification)
This results in a message of
“‘Please correct the errors below.’ need more than 1 value to unpack”.
I have tried different endings to my form_action method. If my last line is:
return request
I get “‘Please correct the errors below.’ ‘Request’ object is not iterable”.
If the last line is:
return request.admin_deny(admin_approver=admin_approver,justification=justification)
I get this … “‘Please correct the errors below.’ ‘NoneType’ object is not iterable”. That is because admin_deny() does not return anything. What should form_action be returning?
Update: Here is the code that call form_action():
class ApproveOrDenyForm(forms.Form):
justification = forms.CharField(
required=True,
widget=forms.Textarea,
)
def save(self, req, login ):
try:
user = User.objects.filter(login=login).get()
req, action = self.form_action(req, user )
except Exception as e:
error_message = str(e)
self.add_error(None, error_message)
raise
return req, action
When you call form_action you are expecting it to return an iterable of two items - req and action. So you need to ensure that form_action() returns a list or tuple of exactly those two things.
It is not clear to me from the post you linked to, or your code, what the action returned is supposed to be - maybe just a result of the action performed. You need to check that to decide whether your action method needs to return something else.
Something like this should work:
def form_action(self, request, admin_approver ):
justification = self.cleaned_data['justification']
action = request.admin_deny(admin_approver=admin_approver, justification=justification)
# action will be None if admin_deny does not return anything,
# but the code calling this function expects it, so return it anyway.
return (request, action)

django form: pass parameter to is_valid

I have an abstract form and 2 forms using it. In the is_valid method of the abstract form, I check for a condition and that condition depends on the form calling the method.
The line is:
if eval(self.cleaned_data.get("validated_modif")):
According to the form, it should be replaced by one of the following lines:
if act.validated_attendance==0: #MinAttend form
if act.validated<2: #Act form
I have a code that works. But it is very (very) dirty, I am looking for a better idea.
forms.py:
class AbstractModif(forms.Form):
releve_mois_modif=forms.IntegerField(min_value=1, max_value=12)
class Meta:
#abstract form
abstract = True
#check if the searched act already exists in the db and has been validated
def is_valid(self, *args, **kwargs):
# run the parent validation first
valid=super(AbstractModif, self).is_valid()
# we're done now if not valid
if not valid:
return valid
#if the form is valid
Model=Act
fields={}
fields["releve_mois"]=self.cleaned_data.get("releve_mois_modif")
try:
act=Model.objects.get(**fields)
if Model!=Act:
act=act.act
#MinAttend form: check act.validated_attendance=0
#Act form: check act.validated<2
if eval(self.cleaned_data.get("validated_modif")):
self._errors['__all__']=ErrorList([u"The act you are looking for has not been validated yet!"])
return False
except Exception, e:
self._errors['__all__']=ErrorList([u"The act you are looking for doesn't exist in our database!"])
print "exception", e
return False
# form valid -> return True
return True
form_1.py:
class Modif(AbstractModif):
#fake field for the is_valid method
validated_modif=forms.CharField(widget=forms.HiddenInput(), initial="act.validated<2")
form_2.py
class Modif(AbstractModif):
#fake field for the is_valid method
validated_modif=forms.CharField(widget=forms.HiddenInput(), initial="act.validated_attendance==0")
form.html
<!-- hidden field for the is_valid method of the form -->
{{ modif.validated_modif }}
I use eval and the initial value of a hidden field to check the condition. Do you have a nicer solution?
Thank you!
This is an immensely bad idea. You're running eval on input received from the browser. So if I use the browser dev tools to modify the contents of the hidden field to os.system('rm -rf /'), what do you think would happen?
I can't see any need for this at all. You have two form subclasses; why don't you simply put the validation in a method in those subclasses?
class Form1(AbstractForm):
def validate_modif(self, act):
return act.validated < 2
class Form(AbstractForm):
def validate_modif(self, act):
return act.validated_attendance == 0
and you can simply call self.validate_modif(act) to perform the validation.
Note also that you should not be overriding is_valid(), but clean(). And your Meta class does nothing because this is a normal Form, not a ModelForm.

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.

Help understanding a Django view

I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.