I am using django modelforms and formsets. But the problem I am facing is that when the form is not validated, instead of showing the erros on the html page it gives and error in the view.
View `
def create(request):
if request.method=="POST":
foo= fooform(request.POST,prefix="fooform")
if foo.valid():
#do stuff
else:
foo= fooform(prefix="fooform")
return render(request,'property/create.html',{'foo':foo})
`
Problem:
When the form does not validate there is a obvious error in the view where in it unable to find the fooform. I want the html page to show the error-ed fields. What am I doing wrong?
Edit:
I think I have found the main issue. The issue is that the modelform is not applying its form validations at the browser level. Because of this the form is getting submitted even though it is not valid. As a result it is failing the validation and not finding the else part.
How about using this pattern instead:
def create(request):
foo = fooform(request.POST or None, prefix='fooform')
if foo.valid():
#do stuff
return render(request,'property/create.html', {'foo': foo})
Your las line is wrong:
It say:
return render(request,'property/create.html',{'foo':fooform})
And should be:
return render(request,'property/create.html',{'foo':foo})
If this is not the issue, please, post errors messages and form code.
Related
I am building a website and I want various views that will ask the user to request a quote from our page. I want to keep the code as DRY as possible so I am writing a view quote which will receive the quote requests from various views and, if there is a validation error redirect back to the page that made the request. I managed to solve this using the super bad practice 'global variables'. I need a better solution, I would like redirecting to respective view with the current form so I can iterate through the form.errors. Here is my code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
global session_form
session_form = form
return redirect('Main:' + quote_for) # Here I would like to send form instead of storing in global variable`
You can use the HttpResponseRedirect function, and pass as argument the page that made the request.
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
All the META data is store on a dictionary, if you want to learn more check the documentation.
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
If you redirect to the referrer, form.errors will be empty, as redirection is always a GET request.
I can think of two solutions to your problem:
Submit forms asynchronously using JavaScript and so populate the errors
Make all the views containing the form support POST - one way to do this would be to create a base class that inherits from FormView
The second option is a typical way of handling forms in Django - you process both POST and GET inside the same view.
After two days of searching I finally found the answer. Instead of saving form in request.session I just save request.POST and then redirect. Here is the code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
request.session['invalid_form'] = request.POST
return redirect('Main:endview')
def endview(request):
session_form = request.session.pop('invalid_form', False)
if session_form:
form = Quote(session_form)
# render template again with invalid form ;)
Now I can repeat this with all the views I want and just change the what_for input of each form to match the respective view (Like I intended).
I've got a Django view with a form, which I'm POSTing to in a unit test. Here's the general structure of the test:
class ViewTests(TestCase):
form_url = reverse_lazy('myapp:form')
success_url = reverse_lazy('myapp:success')
def test_form_submission_with_valid_data_creates_new_object_and_redirects(self):
attributes = EntryFactory.attributes()
attributes['product'] = ProductFactory() # Entry has a ForeignKey to Product
response = self.client.post(self.form_url, attributes, follow=True)
self.assertEqual(response.status_code, 200)
self.assertRedirects(response, self.success_url)
self.assertTemplateUsed(response, 'myapp/success.html')
However, I can't seem to figure out why the redirect isn't working as expected. I've tried dropping in a import pdb; pdb.set_trace() to see if there are any form errors (response.context['form'].errors), but all I get back is an empty dict. Submitting the form in the browser redirects properly, so I'm not sure why the unit test is failing, and also not sure how to debug it properly since no errors are showing up in the form error dict.
Turns out there were a few things wrong.
First, there was actually a second form on the page (for selection of Product) that I missed. Relatedly, I should have assigned ProductFactory().id to attributes['product'], rather than ProductFactory.
Second, after I changed that bit, there was an issue with the assertRedirects; I had to change self.success_url to unicode(self.success_url) since assertRedirects can't perform a comparison with a proxy.
Final product:
def test_form_submission_with_valid_data_create_new_entry_and_redirects(self):
attributes = EntryFactory.attributes()
attributes['product'] = ProductFactory().id
response = self.client.post(self.form_url, attributes)
self.assertRedirects(response, unicode(self.success_url))
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()
Are forms that use the POST method required to have CSRF protection? I'm following a book and the code examples throw 403 errors. I did some searching and it seems as if I need to enable CSRF in all my forms.
My questions are:
Does Django now require that all POST forms be protected from CSRF?
All I need to do to accomplish this is add 'django.middleware.csrf.CsrfViewMiddleware', return render_to_response(template,dictionary,context_instance=RequestContext(request), and add '{% csrf_token %}' in the corresponding form? Am I missing anything here?
When I do this, the form works fine. When any of these pieces are missing, it fails to 403. I just want to make sure I'm doing it RIGHT. :)
Thanks in advance.
edit:
For some reason this code doesn't make sense to me but it doesnt return any error. Please ignore the primitive validation as I haven't gotten to the section of the book where it shows the more efficient way to do it yet.
def contact(request):
errors = []
if request.method == 'POST':
if not request.POST.get('subject',''):
errors.append('Enter a subject')
if not request.POST.get('message',''):
errors.append('Enter a message')
if request.POST.get('email', '') and '#' not in request.POST['email']:
errors.append('Enter a valid email address')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply#example.com'), ['siteownder#example.com'],)
return HttpResponseRedirect('/contact/thanks/')
return render_to_response('contact_form.html', { 'errors': errors }, context_instance=RequestContext(request))
My issue is with the very last line of this view function. It is only called if the request.method != POST. This seems completely wrong to me. Shouldn't I be calling "context_instance=RequestContext(request)" when it's doing a POST?
POST should be used for sensitive information, such as passwords, and django requires securing it with csrf_token; GET should be used for bookmarkable stuff which doesn't need to be secured, like searches. You ARE doing it RIGHT.
EDIT
You shouldn't be calling context_instance=RequestContext(request) when it's doing a POST, you should be calling it regardless of the request type. Look at it like this:
Is it a POST? this means the form was submitted. we validate the form, and redirect the user to another page if the form is OK, or show the form again to the user, with the errors.
Is it a GET? this means the form was not submitted, but other stuff is happening which we don't care about (some referrer link or other stuff). Show the form anyway
Actions in italic are done by the last return, regardless of the if.
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.