My django form has errors in the initial page load, before the form even has a chance to be submitted.
My view:
def example_function(request):
if request.method == 'POST':
# the request is GET
else:
form = MyForm(user=request.user)
import pdb;pdb.set_trace()
return render_to_response('templates/example.html', locals(), context_instance=RequestContext(request),)
Where I have my pdb imported, in the console I can see that my form already has errors. The output of form.errors in my console is all the fields in the model which are set to not null.
(Pdb) form.errors
{'example_field_1': [u'This field is required.'], 'example_field_2': [u'This field is required.']}
The form has not submit yet, but I am still getting errors. Can someone explain?
I'm using django 1.4.
My form:
class MyForm(forms.ModelForm):
captcha = ReCaptchaField()
_readonly_template = form.TextInput(attrs={'readonly':'readonly'})
first_name = forms.CharField(widget = _readonly_tempalte)
def __init__(self, data=None, *args, **kwargs):
data = data or {}
if 'user' in kwargs:
user = kwargs['user']
del kwargs['user']
data.update({
'first_name' : user.first_name,
})
super(MyForm, self).__init__(data, *args, **kwargs)
class Meta:
model = MyModel
My model:
class MyModel(models.Model):
first_name = models.CharField(max_length=255)
example_field_1 = models.CharField(max_length=255)
example_field_2 = models.CharField(max_length=255)
https://docs.djangoproject.com/en/1.8/ref/forms/validation/
accessing the form.errors attribute will trigger the various form validation methods. Those errors shouldn't show up when you render the form.
I'm not sure how the user field is structured, but keep in mind that if you want the user name, you may want to change that from request.user to request.user.username.
I hope you resolved your issue, but in case you haven't, I had a similar issue which I was able to resolve by using "or None" when setting the form after checking if it is a POST (or GET) request.
In your case it looks like this may be a slightly different issue, but I wondered if this snippet might fix things up:
if request.method == "POST":
form = MyForm(request.POST or None)
# .. do stuff....
else: #.....this is a GET
data = {'user': request.user.username} #note this is changed to username
form = MyForm(data)
Not sure if still useful, but adding it here, as I just ran into this for my ChoiceField items within my form.
I was getting the same error messages, but eventually found out I had forgotten to ad 'or None' when initiating the form inside my view.
The initial code inside my view function that was displaying the error messages from the start:
form=FormName(request.POST)
I just added the 'or None' to it:
form=FormName(request.POST or None)
And all good after that.
Don't you need to do something like this
form = NameForm(request.POST)
Rather then attempting to use the user object to populate the form? Will the user object have an example_field_1 in it?
https://docs.djangoproject.com/en/1.8/topics/forms/
This is the normal behavior.
Some properties of fields are checked on client side. The error messages belong to the form, are part of the html but are not displayed until needed. It saves a client-server request.
Related
I am new to Django and have been doing lots of reading so perhaps this is a noob question.
We have applications that involve many forms that users fill out along the way. One user might fill out the budget page and another user might fill out the project description page. Along the way any data they input will be SAVED but NOT validated.
On the review page only data is shown and no input boxes / forms. At the bottom is a submit button. When the user submits the application I then want validation to be performed on all the parts / pages / forms of the application. If there are validation errors then the application can not be submitted.
My model fields are mostly marked as blank=True or null=True depending on the field type. Some fields are required but most I leave blank or null to allow the users to input data along the way.
Any advice on best practices or do not repeat yourself is greatly appreciated.
There is an app in django called form wizard. Using it you can split form submission process for multiple steps.
After a lot of learning, playing and reading I think I have figured a few things and will share them here. I do not know if this is right, however it is progress for me.
So first comes the models. Everything needs to accept blank or null depending on the field type. This will allow the end user to input data as they get it:
class exampleModel(models.Model):
field_1 = models.CharField(blank=True, max_length=25)
field_2 = models.CharField(blank=True, max_length=50)
.........
Then we create our model form:
from your.models import exampleModel
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column
class exampleForm(ModelForm):
class Meta:
model = exampleModel
fields = ('field_1','field_2')
def __init__(self, *args, **kwargs):
# DID WE GET A VALIDATE ARGUMENT?
self.validate = kwargs.pop('validate', False)
super(ExampleForm, self).__init__(*args, **kwargs)
# SEE IF WE HAVE TO VALIDATE
for field in self.fields:
if self.validate:
self.fields[field].required = True
else:
self.fields[field].required = False
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Column('field_1', css_class='col-lg-4 col-md-4'),
Column('field_2', css_class='col-lg-4 col-md-4')
)
)
def clean(self):
cleaned_data = super(ExampleForm, self).clean()
field_1 = cleaned_data.get('field1')
field_2 = cleaned_data.get('field2')
if self.validate and field_2 != field_2:
self.add_error('field_1', 'Field 1 does not match field2')
return cleaned_data
Here is the important part. I've learned a lot about forms and binding. As I mentioned I needed users to be able to fill out forms and not validate the data till the very end. This is my solution which helped me. I could not find a way to bind a form to the model data, so I created a function in my lib called bind_queryset_to_form which looks like this:
def bind_queryset_to_form(qs, form):
form_data = {}
my_form = form()
for field in my_form.fields:
form_data[field] = getattr(qs, field, None)
my_form = form(data=form_data, validate=True)
return my_form
The view:
from your.models import exampleModel
from your.form import exampleForm
from your.lib.bind_queryset_to_form import bind_queryset_to_form
from django.shortcuts import render, get_object_or_404
def your_view(request, pk):
query_set = get_object_or_404(exampleModel, id=pk)
context = dict()
context['query_set'] = query_set
# SAVE THE FORM (POST)
if request.method == 'POST':
form = exampleForm(request.POST, instance=query_set)
form.save()
context['form'] = form
# GET THE DATA.
if request.method == 'GET':
if request.session.get('validate_data'):
# BIND AND VALIDATE
context['form'] = bind_queryset_to_form(query_set, exampleForm)
else:
# NO BIND, NO VALIDATE
context['form'] = exampleForm(instance=query_set)
return render(request, 'dir/your.html', context)
The template:
{% load crispy_forms_tags %}
<div id="div_some_tab">
<form id="form_some_tab" action="{% url 'xx:xx' query_set.id %}" method="post">
{% crispy form form.helper %}
</form>
</div>
What does all the above allow?
I have many views with many data inputs. The user can visit each view and add data as they have it. On the review page I set the flag / session "validate_data". This causes the app to start validating all the fields. Any errors will all be displayed on the review page. When the user goes to correct the errors for the given view the bind_queryset_to_form(query_set, exampleForm) is called binding the form with data from the queryset and highlighting any errors.
I cut out a lot of the exceptions and permission to keep this as transparent as possible (the goat would hate that). Hope this idea might help someone else or someone else might improve upon it.
I've created a form which is a forms.ModelForm. On the "view" side, I've created a view which is a generic.UpdateView.
In those 2 differents classes, I have is_valid() on one side, and form_valid() on the other side.
class ProfileForm(FormForceLocalizedDateFields):
class Meta:
model = Personne
fields = ('sexe', 'statut', 'est_fumeur',
'est_physique', 'date_naissance')
exclude = ('user', 'est_physique')
# blabla fields declaration
def is_valid(self):
pass
and edit view:
class EditView(LoginRequiredMixin, generic.UpdateView):
model = Personne
template_name = 'my_home/profile/edit.html'
form_class = ProfileForm
success_url = reverse_lazy('my_home_index')
# blabla get_initial() and get_object() and get_context_data()
def form_valid(self, form):
# username = form.cleaned_data['username']
# Hack: redirect on same URL:
# - if user refreshes, no form re-send
# - if user goes back, no form re-send too, classical refresh
site_web = u"{0}://{1}".format(
self.request.scheme, self.request.META['HTTP_HOST']
)
return HttpResponseRedirect(u'{0}{1}'.format(
site_web, self.request.META['PATH_INFO']
))
My form shows 3 fields of 3 different models :
User,
Person which has a foreign key to User
Picture which has a foreign key to Person
Where should I create the code that update those fields, and why?
generic.UpdateView is supposed to help us when updating fields, but it seems that when you have fields not belonging to the model you edit, you have to write all the "update" by hand.
is_valid on the surface just tells you whether or not the form is valid, and thats the only job it should ever do..
From the source code:
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 self.errors
Underneath this, what it also does is (from docs)
run validation and return a boolean designating whether the data was valid:
The validation is ran because errors is a property that will call full_clean if the validation hasn't been called yet.
#property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
Where should I create the code that update those fields, and why?
In the form_valid method because by this point you've found out that your validation has verified that it is safe to update your model.
I am having trouble figuring out how to control field validation for ModelForms.
Here is my Model Form:
class UserForm(ModelForm):
class Meta:
model = User
fields = ('email', 'password')
Here is how I am rendering my form in the template:
<form method="post" action="">
{% csrf_token %}
{{ userform.as_p }}
<input type="submit" value="Submit" />
</form>
Here is my view:
def login_page(request):
if request.method == 'POST':
userform = UserForm(request.POST)
if userform.is_valid():
email = request.POST['email']
password = request.POST['password']
user = authenticate(username=email, password=password)
if user is not None:
login(request, user)
return redirect('/dashboard/')
else:
# How can I give the user feedback about authentication failute here. Right now it just reloads the form without any messages.
return render(request, "login.html", {"userform": userform})
else:
# Because .is_valid() is called, The userform will validate for email addresses and that a password input is present here.
return render(request, "login.html", {"userform": userform})
else:
userform = UserForm()
return render(request, "login.html", {"userform": userform})
From reading the docs, this seems like it should be as simple as passing my form some custom attribute; but since I am working with a ModelForm, I am unsure whether this is achieved in the same way.
Could anyone provide an example or tips on how best to control ModelForm field errors?
Thanks!
Very easy, you have multiple ways to display the error messages:
First one:
1- Override the clean method inside your form/modelform:
def clean(self):
# To keep the main validation and error messages
super(your_form_name, self).clean()
# Now it's time to add your custom validation
if len(self.cleaned_data['password']) < 10:
self._errors['password']='your password\'s length is too short'
# you may also use the below line to custom your error message, but it doesn't work with me for some reason
raise forms.ValidationError('Your password is too short')
Second One
By using django built in validators.
You can use the validators inside the models with the custom error message you want like this:
RE = re.compile('^[0-9]{10}$')
field_name = models.CharField('Name', default='your_name',validators=[RegexValidator(regex=RE, message="Inapproperiate Name!")],)
where validators can utilize multiple built-in validation rules that you can validate against.
please refer to this django documentation
also you can use validators inside your forms definition, but the error message inside validators will not work as it did with models in case of errors, to custom error message use the below third method.
Third Method:
skill = forms.CharField(
label = 'SSkill',
min_length = 2,
max_length = 12,
# message kwarg is only usable in models, yet,
#it doesn't spawn error here.
#validators=[RegexValidator(regex=NME, message="In-approperiate number!")],
required = True,
#Errors dictionary
error_messages={'required': 'Please enter your skill','min_length':'very short entry','invalid':'Improper format'},
#Similar to default you pass in model
initial = 'Extreme Coder'
)
Where, requied, min_length, max_length, along with others, are all fomr kw arguments, where the invalid will only be selected when validators a couple of lines above didn't match the criteria you selected inside validators.
Fourth Method
In this method, you will override your modelform's definition like this:
def __init__(self, *args, **kwargs):
super(Your_Form, self).__init__(*args, **kwargs)
self.fields['password'].error_messages['required'] = 'I require that you fill out this field'
Fifth Method:
I have seen some nice way to custom errors in one of the stackoverflow posts like this:
class Meta:
model = Customer
exclude = ('user',)
fields = ['birthday','name']
field_args = {
"name" : {
"error_messages" : {
"required" : "Please let us know what to call you!"
"max_length" : "Too short!!!"
}
}
}
reference to fifth method is: Brendel's Blog
May be there are other/better ways of doing things, but those are the most common, at least to me :-)
I hope this post helps.
The Django authentication app has a built in AuthenticationForm that you use. If you look at the source code, you can see their approach is to check the username and password inside the clean method. That way, a validation error can be raised if the username and password combination is invalid, and the form will display the error.
You can import the AuthenticationForm and use it in your login view with the following import:
from django.contrib.auth.forms import Authentication
You may also be able to use the built in login view as well. This means you can take advantage of the added functionality, such as displaying a message if the user does not have cookies enabled. It also decreases the chance of you creating a security vulnerability.
If you want to return error or warning messages back to the user (even if there are no errors in the validated form) you can either use the messaging framework, or you can insert custom error messsages into the form.
I think the messaging framework is your best bet - I don't think it's a good idea to inject errors into the form. If someone has entered a wrong username and pword, it's not a validation error, so it shouldn't be treated as such. A validation error should only be thrown if the wrong type of data is entered to a field, not the incorrect value.
I need to create registration form for an event. Each person that register can bring guests. I want to register everything in one page. To do that I use a view with 1 RegistrationForm and X GuestForms. In my models, I have a Registration and Guest class I used to create the two forms with ModelForm.
The problem is that a GuestForm is not required to be filled (you don't have to bring guests).
def register_form(request):
error = False
if request.method == 'POST':
register = RegistrationForm(request.POST, instance=Registration() )
guests = [GuestForm(request.POST, prefix=str(x), instance=Guest()) for x in range(MAXGUESTS)]
if register.is_valid():
print("register is valid")
for guest in guests:
if guest.is_valid():
print("guest is valid")
else:
print("guest is not valid") # always when empty form
error = True
else:
print("register is not valid")
error = True
if not error:
... # save the form in the database
register = RegistrationForm(instance=Registration())
guests = [GuestForm(prefix=str(x), instance=Guest()) for x in range(MAXGUESTS)]
return render_to_response('register.html',{
'form': register,
'max_guests': MAXGUESTS,
'guests': guests,
'error': error,
})
So I need to set a form as optional and be able to differentiate when the whole form is empty and when there is an error. Any idea how ?
Thank you
Solution
def register_form(request):
GuestFormSet = modelformset_factory(Guest, exclude=('register',))
error = False
if request.method == 'POST':
register = RegistrationForm(request.POST, instance=Registration() )
guests = GuestFormSet(request.POST)
if register.is_valid():
print("register is valid")
for guest in guests:
if guest.is_valid():
print("guest is valid") # even if some forms are empty
else:
print("guest is not valid")
error = True
else:
print("register is not valid")
error = True
if not error:
...
# save the form in the database
return something
else:
register = RegistrationForm(instance=Registration())
guests = GuestFormSet(queryset=Guest.objects.none())
return render_to_response('register.html',{
'form': register,
'max_guests': MAXGUESTS,
'guests': guests,
'error': error,
})
You can use a model formset for your guest forms. It can distinguish between empty and invalid forms.
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#model-formsets
I guess you will need some way to determine if a submitted guest form was actually filled in. When an empty form was submitted, ignore it:
forms_to_save =[]
for form in guest_forms:
if guest_form.is_valid()
forms_to_save.append( form )
else:
if form.contains_data(): # you'll have to implement this
error = True
if not error:
for form in forms_to_save():
form.save()
I have a similar problem in a wedding RSVP application that I'm building. In my case, each Guest has an 'attending' checkbox, and if it is not checked (i.e. the guest isn't attending) then I don't want any errors to be reported to the user.
My Guest model (slimmed down for brevity):
class Guest(models.Model):
email = models.EmailField(max_length=50, unique=True)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
attending = models.BooleanField(default=False)
I have a ModelForm class called RsvpForm that creates a form based on the Guest model. It looks like this:
class RsvpForm(ModelForm):
class Meta:
model = Guest
fields = ('attending', 'email', 'first_name', 'last_name')
def clean(self):
cleaned_data = self.cleaned_data
attending = cleaned_data.get('attending')
if not attending:
#clear the error collection
self._errors['email'] = {}
return cleaned_data
The solution to this problem lies in the clean() method that I've overriden in my RsvpForm class. In it, I check whether or not the guest is attending. If they aren't, then I clear the error messages for each of the other fields.
Originally, I had cleared the errors for the entire form like this:
if not attending:
#clear the error collection
self._errors['email'] = ''
self._errors['first_name'] = ''
self._errors['last_name'] = ''
But for some reason, modifying the values of any of these keys caused the form to fail validation, which in turn prevented Guest data from being saved if they had indicated that they were not attending. A small bug that most would never find, but annoying nonetheless.
At some point along the line, I had also tried to clear the errors collection by calling .clear() on the self._errors dictionary:
if not attending:
#clear the error collection
self._errors.clear()
But for some reason, the errors were still shown, even though I could verify that the dictionary was empty.
The end result is that form validation errors are only shown to my user if the 'attending' checkbox is selected, which is ideal because Guests might want to update their own contact information prior to deciding on who they will bring as a plus one.
There is a simpler solution than Alasdair's: using the empty_permitted keyword argument.
>>> f = MyForm(data={}, empty_permitted=True)
>>> f.is_valid()
True
Here is my situation. I have a web page for users to create their own accounts. On this page, there's reCaptcha to prevent bots. Onece a user click on "Submit", the reCaptcha validation is performed, prior to constructing the corresponding form, in the corresponding view. Let's say the user's input failed the reCaptcha validation. How should I prompt this error back to the user? Should I add the error to the "non_field_errors" of the form? If so, what's the correct way of doing this?
My current approach is to pass a list of errors, including the reCaptcha error, from the view to the form constructor and have the errors added to the form's non_field_errors in the init(). The way I add errors to the form's non_field_errors (referenced post), however, is insufficient though. When there are multiple errors in the list passed, the latter one always overwrites the one before it. How can I append errors to the form's non_field_errors rather then overwriting the existing one each time?
views.py:
def create_account(request):
""" User sign up form """
if request.method == 'POST':
recaptcha_result = check_recaptcha(request)
if recaptcha_result.is_valid:
...
else:
non_form_errors = ['Incorrect reCaptcha word entered. Please try again.'];
signup_form = SignUpForm(request.POST, non_form_errors=non_form_errors)
else:
signup_form = SignUpForm()
public_key = settings.RECAPTCHA_PUBLIC_KEY
script = displayhtml(public_key=public_key)
return render(request, 'create_account.html',
{'signup_form': signup_form, 'script': script})
forms.py:
class SignUpForm(UserCreationForm):
""" Require email address when a user signs up """
email = forms.EmailField(label='Email address', max_length=75, widget=TextInput(attrs={'size': 30}))
def __init__(self, *args, **kwargs):
non_form_errors = []
if kwargs.has_key('non_form_errors'):
non_form_errors.append(kwargs.pop('non_form_errors'))
super(SignUpForm, self).__init__(*args, **kwargs)
for err in non_form_errors:
self.errors['__all__'] = self.error_class(err)
Try this in forms.py:
def __init__(self, *args, **kwargs):
non_form_errors = []
if kwargs.has_key('non_form_errors'):
non_form_errors.append(kwargs.pop('non_form_errors'))
super(SignUpForm, self).__init__(*args, **kwargs)
errors = self.errors.get('__all__', [])
for err in non_form_errors:
errors.append(self.error_class(err))
self.errors['__all__'] = errors
First, I would like to thank #lazerscience for pointing out a better direction for a solution to my problem. I, however, didn't adopt the django-recaptcha app as suggested.
I ended up using a code snippet from Marco Fucci. In a quick summary, this code snippet helps you to create a custom form field (and widget) for ReCaptcha. Once this is in place, all you need to do to have ReCaptcha on your form is as simple as adding one line to the form definition.