I need to call an API function after validating a form with is_valid(). This API call can still throw exceptions which in turn may kind of invalidate a field in the form.
How can I do that? I'm looking for something like that:
def smstrade(request):
if request.method == "POST":
form = SomeForm(request.POST)
if form.is_valid():
try:
api_call(...)
except SomeException:
form["field"].set_valid(False)
Bit late, but you can invalidate the form and add display the appropriate messages by setting form._errors
>>> f.is_valid()
True
>>> f._errors['my_field'] = ['This field caused a problem']
>>> f.is_valid()
False
>>> str(f)
... <ul class="errorlist"><li>This field caused a problem</li></ul>
I needed to do this with FormView.form_valid() methods and models with unique fields
def form_valid(self, form):
obj = User(**form.cleaned_data)
try:
obj.save()
except IntegrityError:
form._errors['username'] = ['Sorry, already taken']
return super(MyView, self).form_invalid(form)
return super(MyView, self).form_valid(form)
It is better to override the clean method for the field you are interested in and add your logic there. That way, you can output the appropriate error message as well.
Sounds like you just need a variable to store a valid state outside of the form object.
def smstrade(request):
if request.method == "POST":
form = SomeForm(request.POST)
valid = form.is_valid()
if valid:
try:
api_call(...)
except SomeException:
valid = False
if valid: # still valid?
print "VALID!"
But really it seems like you should be putting this in the form itself, so that you only need to call is_valid() once. The only complication would be if you needed access to the request object.
class MyForm(forms.Form):
def clean(self):
cd = super(MyForm, self).clean()
try:
api_call
except Exception:
raise forms.ValidationError("API Call failed")
return cd
# view..
if form.is_valid():
print "api call success and the rest of the form is valid too."
In case you really want to trigger the form validation by calling is_valid a second this is the way you can do it (Django 1.4)
form = MyForm(data)
form.is_valid()
# ...
form._errors = None
form.is_valid() # trigger second validation
Related
Edit: I'm not asking how to avoid the model fields being validated; I understand why it's required. I'm asking how I could skip over only the checks I wrote when I override the form's clean() method. I want the cleaned_data without it going through the other validation I wrote.
Let's say I have a ModelForm with the options to "save" and "Archive" (i.e. save for later) which only appear in the post_data if they are clicked. If the user clicks "save", the form would go through all the validation (including some custom logic in form.clean()) like normal. If the user clicks "archive" none of the custom validation in the form.clean() should run but the data should be saved.
In forms.py, I tried defining my form.clean() method as something like this:
def clean(self, full_clean=True):
cleaned_data = super().clean()
if full_clean:
# All my validation
...
...
return cleaned_data
Then in my views.py:
...
...
if request.method == "POST":
post_data = request.POST
if post_data.get('save') != None: # Do all validation
if form.is_valid():
form.save()
cleaned_form = form.cleaned_data
...
...
else: # If not valid
# Do something with the errors
...
elif post_data.get('archive') != None: # Don't do any validation
form.is_valid()
cleaned_form = form.clean(full_clean=False)
...
...
form.save()
...
The problem is, the form.clean() method is called when form.is_valid() is called so all the custom validation is still executed. I've also tried making full_clean default to False instead but then it won't validate properly when I want it to. If I remove the second form.is_valid() call, the cleaned_data doesn't get created and throws an error. Any suggestions would be much appreciated.
Override the init() and set a new field based on the post_data.
def __init__(self, *args, **kwargs):
archived = kwargs.pop('archived', None)
super(MyForm, self).__init__(*args, **kwargs)
self.fields['archived'] = forms.BooleanField(initial=archived, required=False)
and then actually add to your form
form = MyForm(request.post, archived=post_data.get('archived'))
In your custom clean() method, do a check for your self.fields.get('archived').
def clean(self):
if not self.fields.get('archived']:
# Do your validation
else it just skips over the custom validation.
I'm working on a django project where during registration, a user can submit a code to get special discounts. The validation of the discount codes is already working nicely, but I'm missing one beautifying aspect: After the user submits an invalid code I want to empty out the input field; i.e.:
def validate_code(value):
# check code for validity or raise ValidationError
class CodeForm(forms.Form):
code = forms.CharField(validators=[validate_code])
# in my views.py
def code_verification_view(request):
if request.method == 'POST':
form = CodeForm(request.POST)
if form.is_valid():
# proceed with given code
else:
# modify form to return an empty html input for the code field
# this is where I'm stuck
form.fields['code'].value = ''
# ... render the form in a template
The end result should be a form with an empty input field, but the validation errors showing. The behavior should be similar to how password input fields are emptied if the form verification fails.
EDIT: I solved the problem, but in a very hacky way:
see https://stackoverflow.com/a/46564834/8572938
I'd appreciate a proper solution that does not rely on accessing protected members of the form.
the key is to reset form variable
form = CodeForm(None)
in your code
def code_verification_view(request):
if request.method == 'POST':
form = CodeForm(request.POST)
if form.is_valid():
# proceed with given code
else:
form = CodeForm(None)
Just render your template, if your form is not valid, it will show error, In case if it is valid process your data
def code_verification_view(request):
if request.method == 'POST':
form = CodeForm(request.POST)
if form.is_valid():
// process your data
else:
form.data['field'] = None
return render(request, template_name, {'form': form})
Make a field validation in your form definition:
class CodeForm(forms.Form):
code = forms.CharField(validators=[validate_code])
def clean_code(self):
code = self.cleaned_data(code)
error = # some of your process
if error:
self.fields['code'] = None
raise forms.ValidationError('...')
else:
return code
And remove the else part in your view, instead you want to do something else. If you just want to display the form with error, the raise forms.ValidationError will do it.
You can in django form add a clean_<field_name> to control each field as you like.
More info here
I found a way that works, but it's quite dirty:
old_form = CodeForm(request.POST)
form = CodeForm()
if old_form.is_valid():
# ...
else:
form._errors = old_form._errors
# pass form into the rendering context
This way, I get a clean form with the preserved errors.
While it does the job, it is clearly an ugly hack.
I'm having some trouble grokking Django forms and validation.
#views.py:
def create(request):
if request.method == 'POST':
form = CreateDocumentForm(request.POST)
if form.is_valid():
doc = Document.objects.create(name=form.cleaned_data['name'])
#snip
#forms.py:
class CreateDocumentForm(forms.ModelForm):
name = forms.CharField()
def clean_name(self):
cleaned_name = self.cleaned_data['name']
rgx = re.compile('^(\w|-|\.)+$')
if rgx.match(cleaned_name) == None:
raise ValidationError("invalidchars")
return cleaned_name
The logic is working properly, but I don't know how to tell which kind of VaidationError was raised. Also - This is handled by an Ajax request, so I won't be using templating in the repsonse. I need to get the status of what failed in this view.
thx
You generally won't see the ValidationErrors themselves. If you call form.is_valid, then the errors that occur during validation are all collected and returned to you as a dictionary, form.errors
You can check that dictionary for errors relating to any specific field. The result for any field with errors should be the string value of any ValidationErrors that were raised for that field.
In your view, then, if form.is_valid() returns False, then you can do this:
if 'name' in form.errors:
for err_message in form.errors['name']:
# do something with the error string
I thought it looks trivial, and was surprised.
What I have
I have a Django model + form (ModelForm).
My user fills in the form, and on my view I have the usual:
if request.POST:
form = myForm(request.POST)
if request.method == "POST" and form.is_valid():
result = form.save(commit=False)
Now I need to heavily manipulate some fields of the form (in the "result" object) and I want to check the forms is_valid() again before saving.
The Problem
I tried to create a new form using the "result" object (that is a ModelForm object) using a dictionary (as suggested here)
result_dictionary = dict((x.name, getattr(result, x.name)) for x in result._meta.fields)
or
result_dictionary = model_to_dict(result, fields=[field.name for field in result._meta.fields])
plus
testForm = myForm(initial=result_dictionary)
but it doesn't pass is_valid() and does'nt give any errors!
The fields are passed OK to the new form...
Any ideas?
Sometimes, looking in the Django source can be really helpful, here's BaseForm.is_valid():
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not bool(self.errors)
So if there are no errors, is_valid() returns false because you haven't bound the form, you haven't given the form anywhere to look for data. Try using the form.data dictionary instead, something like this:
if request.POST:
form = myModel(request.POST)
if request.method == "POST" and form.is_valid():
form.data['field1'] = 'Changing this'
form.data['field2'] = 34
testform = myModel(data=form.data)
I am checking a user's submitted email address against two lists from the database -- a list of authorized domains and a list of authorized email addresses. Currently, if neither are found, it is rainsing a DoesNotExist exception. How would I handle this if neither are found?
Here is the code I currently have in views.py --
def register(request):
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
try:
email_list = EmailList.objects.get(domain=(cd['email'].split('#')[1]))
except:
email_list = EmailList.objects.get(email=cd['email'])
# I also need another except if neither works for the validator.
network= Network.objects.get(network=email_list.network)
User.objects.create(name=cd['name'], email=cd['email'], network=network)
return HttpResponseRedirect ('/user/view/')
else:
form = UserForm()
return render_to_response('register.html',{'form':form}, context_instance = RequestContext(request))
Nested try/except blocks. And don't use a bare except; catch only the exceptions you can handle.
You can nest try/except:
try:
email_list = EmailList.objects.get(domain=(cd['email'].split('#')[1]))
except:
try:
email_list = EmailList.objects.get(email=cd['email'])
except:
...do something else
If you are just using these try/except to do a simple test, it's worthwhile to wrap the try/except:
def get_or_none(model, **kwargs):
try:
return model.objects.get(**kwargs)
except model.DoesNotExist:
return None
Which gives you slightly more readable code:
if get_or_none(EmailList, domain='domain.com'):
...do something
elif get_or_none(EmailList, domain='domain.com'):
...do something
else:
...do something
As Ignacio mentioned in his answer you should always be explicit and catch only the exceptions you intend.