How do I add errors to the top of a form after I cleaned the data? I have an object that needs to make a REST call to an external app (google maps) as a pre-save condition, and this can fail, which means I need my users to correct the data in the form. So I clean the data and then try to save and add to the form errors if the save doesn't work:
if request.method == "POST":
#clean form data
try:
profile.save()
return HttpResponseRedirect(reverse("some_page", args=[some.args]))
except ValueError:
our_form.errors.__all__ = [u"error message goes here"]
return render_to_response(template_name, {"ourform": our_form,},
context_instance=RequestContext(request))
This failed to return the error text in my unit-tests (which were looking for it in {{form.non_field_errors}}), and then when I run it through the debugger, the errors had not been added to the forms error dict when they reach the render_to_response line, nor anywhere else in the our_form tree. Why didn't this work? How am I supposed to add errors to the top of a form after it's been cleaned?
You really want to do this during form validation and raise a ValidationError from there... but if you're set on doing it this way you'll want to access _errors to add new messages. Try something like this:
from django.forms.util import ErrorList
our_form._errors["field_name"] = ErrorList([u"error message goes here"])
Non field errors can be added using the constant NON_FIELD_ERRORS dictionary key (which is __all__ by default):
from django import forms
errors = my_form._errors.setdefault(forms.forms.NON_FIELD_ERRORS, forms.util.ErrorList())
errors.append("My error here")
In Django 1.7 or higher, I would do:
form.add_error(field_name, "Some message")
The method add_error was added in 1.7. The form variable is the form I want to manipulate and field_name is the specific field name or None if I want an error that is not associated with a specific field.
In Django 1.6 I would do something like:
from django.forms.forms import NON_FIELD_ERRORS
errors = form._errors.setdefault(field_name, form.error_class())
errors.append("Some message")
In the code above form is the form I want to manipulate and field_name is the field name for which I want to add an error. field_name can be set to NON_FIELD_ERRORS to add an error not associated with a specific field. I use form.error_class() to generate the empty list of error messages. This is how Django 1.6 internally creates an empty list rather than instantiate ErrorList() directly.
You should raise the validationerror.
Why not put the verification within the form's clean method
class ProfileForm(forms.Form):
def clean(self):
try:
#Make a call to the API and verify it works well
except:
raise forms.ValidationError('Your address is not locatable by Google Maps')
that way, you just need the standard form.is_valid() in the view.
You're almost there with your original solution. Here is a base Form class I built which allows me to do the same thing, i.e. add non-field error messages to the form:
from django import forms
from django.forms.util import ErrorDict
from django.forms.forms import NON_FIELD_ERRORS
class MyBaseForm(forms.Form):
def add_form_error(self, message):
if not self._errors:
self._errors = ErrorDict()
if not NON_FIELD_ERRORS in self._errors:
self._errors[NON_FIELD_ERRORS] = self.error_class()
self._errors[NON_FIELD_ERRORS].append(message)
class MyForm(MyBaseForm):
....
All my forms extend this class and so I can simply call the add_form_error() method to add another error message.
I'm not sure how horrible of a hack this is (I've only really worked on two Django projects up until this point) but if you do something like follows you get a separate error message that is not associated with a specific field in the model:
form = NewPostForm()
if something_went_horribly_wrong():
form.errors[''] = "You broke it!"
If the validation pertains to the data layer, then you should indeed not use form validation. Since Django 1.2 though, there exists a similar concept for Django models and this is certainly what you shoud use. See the documentation for model validation.
Related
I have a web app that allows users to edit previously submitted data.
I'm currently processing PUT requests by manually updating the data.
I would like to use my forms to validate input but I run into the issue of the other required fields.
For instance, if a user updates a date field and I validate it with my form, it errors out as I'm missing other required fields like name, location, etc since the form was designed to be filled out all at once.
What is the best way to use my forms to validate input but conditionally allow required fields if the request is a PUT or POST with model Forms.
If you're getting errors for missing fields you're not using the modelform correctly, sounds like you are not passing it the existing instance to work on.
You need to use a pattern like this:
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
def my_view(request, obj_id):
my_object = get_object_or_404(MyModel, pk=obj_id)
update_form = MyModelForm(instance=my_object, data=request.PUT or None)
if update_form.is_valid():
return HttpResponse(status=204) # empty success response
else:
return render(...) # render update_form.errors somehow
And be sure to include the object id in the url that you send your PUT requests to
I've some custom views in django-admin linked to my change_form.
All works well, but now I'd want to raise a ValidationError from my custom views and consequently get the flash in django-admin that prints the msg of ValidationError, that is the same that occurs if I raise it in model.clean().
an example of custom view that I use:
#site.admin_view
def send_transaction_mail(request, obj_id, typ):
order = Order.objects.get(id=obj_id)
if typ == 'SHIPMENT':
send_order_confirm(order)
else:
raise Exception("Something goes wrong sending transaction mail")
return HttpResponseRedirect(request.META['HTTP_REFERER'])
is there a way? Thank you
Not sure I understood what you want well:
You have a view, by definition a public page. You want it to display an error message in the admin pages (by definition privates page) ? It's odd. But if you want so.
To display an error in the admin pages, use the Django Message Framework. It's what is in use to display the yellow rows with errors/notifications on the top of the pages.
from django.contrib import messages
messages.error(request, "Something goes wrong sending transaction mail");
Indeed, validation errors an only displayed with forms. And thus, they are to be raised only in the clean() method of a form, a formset, or a field.
Is there any way to launch custom errors in the admin site like that?:
Currently I throw the error with
raise forms.ValidationError('error')
but shows the debug error screen
Where are you putting the raise forms.ValidationError('error')?
In your form clean() method is a good place to raise custom errors. You can even do def clean_fieldname() to preform specific validation for one field. From the django docs
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data['recipients']
if "fred#example.com" not in data:
raise forms.ValidationError("You have forgotten about Fred!")
# Always return the cleaned data, whether you have changed it or
# not.
return data
This link also could help
I am using the following custom form validator to ensure that there is not more than one correct entry submitted to my application through an InlineFormSet.
class BaseAnswerFormSet(forms.models.BaseInlineFormSet):
def clean(self):
if any(self.errors):
return
if len([d['correct'] for d in self.forms if d['correct'].value()]) !=1:
raise forms.ValidationError("There must be one and only one correct answer")
return
This is working, as the form object that is presented will return False when evaluated as .is_clean() but there is no error returned. Here is what it shows up as when I use pdb in the view that handles the POST:
(Pdb) answerformset.is_valid()
False
(Pdb) answerformset.errors
[{}, {}, {}]
Shouldn't the raise forms.ValidationError("There must be one... create an error entry? I know that each of the empty dicts in the answerformset.errors list is for each of the answer forms, but I thought that there would be a non_field_error or something like that?
How can I get this clean function to return an error that I can display in a template? How can I add a non_field_error to this?
Please read Custom Formset Validation. Formset custom errors can be accessed using non_form_errors:
answerformset.non_form_errors()
I'm using django-registration for a project of mine.
I'd like to add some extra contextual data to the template used for email activation.
Looking into the register view source, I cannot figure out how to do it.
Any idea ?
From what I remember, you need to write your own registration backend object (easier then is sounds) as well as your own profile model that inherits from RegistrationProfile and make the backend use your custom RegistrationProfile instead (This model is where the email templates are rendered and there is no way to extend the context, so they need to be overwritten)
A simple solution is to rewrite the send_activation_email
So instead of
registration_profile.send_activation_email(site)
I wrote this in my Users model
def send_activation_email(self, registration_profile):
ctx_dict = {
'activation_key': registration_profile.activation_key,
'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
'OTHER_CONTEXT': 'your own context'
}
subject = render_to_string('registration/activation_email_subject.txt',
ctx_dict)
subject = ''.join(subject.splitlines())
message = render_to_string('registration/activation_email.txt',
ctx_dict)
self.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
And I call it like this
user.send_activation_email(registration_profile)
I don't get what it is your problem but the parameter is just in the code you link (the last one):
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='registration/registration_form.html',
extra_context=None)
That means you can do it from wherever you are calling the method. Let's say your urls.py:
from registration.views import register
(...)
url(r'/registration/^$', register(extra_context={'value-1':'foo', 'value-2':'boo'})), name='registration_access')
That's in urls.py, where usually people ask more, but, of course, it could be from any other file you are calling the method.