I'm fighting with the Django form validation for a while now. I'm using pretty basic code. Returning the form data using an ajax request is the only special thing but I don't think its the cause auf my problems here.
I'm trying to update an user entry. I create an instance of the user and I feed that instance into the CreateForm() but I still get the duplicate username error.
here is a part of the view:
def user_update(request):
if request.is_ajax():
print request.POST
user = User.objects.get(pk=int(request.POST['pk']))
print user
form = UserCreateForm(request.POST, instance=user)
print form
if form.is_valid():
form_cleaned = form.cleaned_data
else:
print '### form is invalid ###'
print form.error_messages
The output for request.POST is:
<QueryDict: {u'username': [u'testuser'], u'password1': [u'test'], u'password2': [u'test'], u'pk': [u'27'], u'csrfmiddlewaretoken': [u'wyBoaBAlxLTO952BzWSxR7HMK6W7nsAM'], u'email': [u'soso#soso.so']}>
The output for print user is:
testuser
In print form I always get the duplicate username error, hence the form is always invalid.
Please give me a hint. Many thanks in advance.
For form validation, you have used UserCreateForm.
Apart from the regular regex validation of fields, the UserCreateForm also checks if a given username is available or not. And any existing user will fail this check.
You should use an appropriate form like UserChangeForm or if your application only updates a particular field like password, then choose from the built in forms.
With reference to UserCreationForm in django, it has clean_username() validation method that checks if user with given username already exists, if so raises the validation error that you are getting.
You should better use UserChangeForm if you want to update user fields.
The UserCreationForm's clean_username() has the following code:
User._default_manager.get(username=username)
If this is successful, you get your error. Since your user (admin) already exists, you'll always get the error. As suggested by #sudipta, you're better off with the UserChangeForm.
IMHO, you could define your own custom form either with your own behaviour rather than using a stock one and deviate from its original purpose.
Related
I've seen Django's samples and I can see they have decent error handling. However I want to see if there is yet a better approach, a general pattern to handle form validation errors in Django. This is the sample I found here:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
In particular, I was wondering:
How can the view in "/thanks/" be sure that the form was validated? Are there any common ways to pass the successful validation of the form to the next view? Or do I need to do something manually such as setting a flag in request's session?
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
EDIT: With regards to #1 I am referring to cases like user manually entering the '/thanks/' url or going back and forth through history pages and accidentally openning it without any form being validated. (Do we still show the "thanks" page? or we need to somehow re-validate why we are in thanks view).
The view can be sure that the form is validated because it will only be called if the form is valid...
If the page is generated through a post request the browser will always ask you that when hitting refresh... I guess the only way to avoid this would be redirecting to another page!
How can the view in "/thanks/" be sure that the form was validated?
form.is_valid() should thoroughly check any field or - if necessary - any combination, cornercase, etc. That's basically it. The views knows, the form was valid if it renders. There is no need to include redundant information in the session.
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
I am not sure what the point would be. The form contains errors and the user may correct them or leave. To render a page that would not ask for form resubmission, one could use a redirect, just as in the valid case. The error markup would have to be done manually in that case.
I have a django form that first validates its data through calling form.is_valid(). If its not, the form is redisplayed, with an error message regarding the invalid data.
Now if is_valid() is true, I try to save the data in an ldap backend. If the form.cleaned_data is not in correspondance with the ldap data type, I get an Exception from my ldap save method. Now what I would like to do in this case is to redisplay the form with an error message, just like the thing that happens after form.is_valid() returns false.
I tried reading some docs and also some django source, but could not find where I could hook into this.
An alternative would be to carefully build the form of (custom) form fields that would "guarantee" that the data is allready compliant to ldap syntax.
But I would like to make shure that I catch ldap syntax errors and display them in a convenient form. So if I could hook into that form redisplay mechanism would make me a happy little programmer :-)
Any ideas or hints?
Under your class for the form that extends forms.Form, add one of the following methods, assuming you have a is_valid_ldap_data() defined somewhere:
for a whole form:
def clean(self):
if !is_valid_ldap_data(self.cleaned_data.get("fieldname")):
raise forms.ValidationError("Invalid LDAP data type");
return self.cleaned_data
or for a single field:
def clean_fieldname(self):
if !is_valid_ldap_data(self.cleaned_data['fieldname'])):
raise forms.ValidationError("Invalid LDAP data type");
return self.cleaned_data['fieldname']
At your Form subclass implement custom field validation method
http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute
Validation logic must go where it belongs. If form.is_valid() == True than form.cleaned_data must be valid. Just because code says so. You want to hide some of validation logic somewhere else -- and that is just bad practice.
It seems to me that you just have an additional step of validation.
You could, depending on which are your specific needs:
Put a validator on your fields, to check for simple things, or
Implement additional cleaning on your field or on multiple fields at once,
but in every case if the form is not valid for any reason (as in your case), is_valid should return False.
What I'm trying to do is to add an error for a field in a view, AFTER both form.is_valid() and form.save(), and it seems to work but only because of a hack, and I'm hoping someone here can explain why it works.
So in my form's save() function, I connect to an LDAP server, and try to authenticate the user and password supplied in the form. If it all works, what ends up happening is a User object and a Profile object gets created. However in the code for connecting to and getting data from LDAP it's possible that an error occurs, such as wrong credentials or simply can't bind to the server. In such a case, I return None and my view then deals with it by adding an error to the form. Here's the relevant code in the view:
if form.is_valid():
profile = form.save()
if profile is None:
# there was an LDAP error
msg = u"An error ocurred trying to authenticate over LDAP. Try again."
form = form_class(request.POST)
print form
form._errors['user_name'] = ErrorList([msg])
return render_to_response(template_name, locals())
Now, with that print statement commented out, I get a 'NoneType' object does not support item assignment when I try to add the error to the form. If I uncomment the print statement, it works like I expect it to. Any ideas as to why? And anyway I can do this without having that print statement in there? Thanks!
PS: I didn't want to do form validation in the clean method since I don't want to have to bind to the LDAP server and try to authenticate the user twice, I just want it to happen once and it either works or doesn't.
form._errors doesn't exist until validation occurs. By calling print form, you are implicitly calling form.is_valid() for your new form instance.
Which raises the question, why are you creating a new form instance? I would comment out the form = form_class(request.POST) line and use your existing form.
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.
Does the concept of severity exist in Django's form validation or is it only errors?
Also, how about suppressing warnings/errors?
Old question, but I think it is still relevant.
It really depends on what you consider to be a warning.
You may accept partially valid data in your form (not raise ValidationError on fields upon which you want warnings). Then, using the contrib.messages framework (or similar), you may display a warning box on the next page (be it the same form page, or a redirection to home or any other page)
Alternatively, you might want confirmation instead of a warning. You may add or alter fields dynamically upon creation, so why not add hidden "I accept the risks" checkboxes that are required only if your form raises that warning?
User loads form. Checkbox is an hidden HTML input set to false.
User fills form with data that raises warning. Form is displayed again, but now the checkbox is visible.
User checks box then resubmits their form.
The server handles the data correctly and ignores the warning.
The second option has the advantage of not requiring cookies, and it also adds interactivity (your user might not want to proceed because of the warning...).
In your code, all you would have to do is this:
#views.py
...
if form.is_valid():
# proceed
else:
form.fields["my_checkbox"].widget = widgets.CheckboxInput
# re-display form
...
#forms.py
...
def clean_myfield(self):
# do your cleaning
if (myfield_warning==True) and not (my_checkbox==True):
raise ValidationError("blabla")
else:
return myfield
In your view, you may check for appropriate errors in form.errors if needed.
Django forms can only raise ValidationErrors (see here). One way to get around this is to use the new messaging system. There are 5 levels of messages defined, with the ability to define additional custom message levels.
As for suppressing errors/warnings, you can always simply ignore form.errors in your template. Also take a look at the clean methods in the forms module - you should be able to suppress some warnings there.
I had a similar requirement in my Django Admin app. I needed to get a confirmation from the user before saving a possibly duplicate entry. I used the error message itself for this as a workaround. In the message, i added a hidden HTML input. On saving a second time, this input appeared in the form data, in which case i went ahead with saving skipping the warning.
def MyForm(forms.ModelForm):
def clean(self):
if (not self.instance.id and # check only new entries
'warn_possible_duplicate' not in self.data): # on first save this is true
# check if possible duplicate
if possible_duplicate:
self.add_error('dup_field', format_html(
'Similar entry already exists.'
' To add the new entry anyway, please save again.'
'<input type="hidden" id="warn-possible-duplicate"' # inject hidden input with error msg itself
'name="warn_possible_duplicate" value="0"/>' # so it's returned in form `data` on second save
))
Any possible flaws with this? Any better suggestions?