I'm new to WTForms, and from what I've learned so far, I find the validation error handling a bit tricky.
First off, I can't implement the native HTML <input required >;
Secondly, when the form failed validation, I have to rerender the page template where the form is at, and if my form is placed on bottom of a page, the user will see the page 'refreshed' and have no idea what is going on.
Does anyone have suggestions on how to integrate WTForms more seamlessly? Any comments, resources, urls, code samples are welcomed, thanks!
Here are some of my related codes:
# /forms.py
from wtforms import Form, BooleanField, StringField, PasswordField, validators
class RegistrationForm(Form):
email = StringField('Email Address', [
validators.required(),
validators.Email(message='Please enter a valid email address'),
validators.length(min=6, max=35)
])
password = PasswordField('New Password', [
validators.required(),
validators.DataRequired(),
validators.length(min=6, max=35)
])
# /views.py
from flask import Flask, Blueprint, flash, render_template, redirect, request, url_for
from forms import RegistrationForm, LoginForm
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def index():
form = RegistrationForm()
context = {
"form": form
}
if request.method == 'POST' and form.validate():
submitted_form = request.form
return redirect('/welcome')
else:
return render_template('form.html', context = context)
# /form.html
<form class="form" action="" method="POST" name="register">
{% for field_name, field_errors in context.reg_form.errors|dictsort if field_errors %}
{% for err in field_errors %}
<li class="error">{{ context.reg_form[field_name].label }}: {{ err }}</li>
{% endfor %}
{% endfor %}
<ul class="form-fields center">
<li class="form-field">
{{ context.form.email(class='email', placeholder='email') }}
</li>
<li class="form-field">
{{ context.form.password(class='password', placeholder='password') }}
</li>
</ul>
</form>
As far as I understand, WTForm will always have to refresh the page in order to show the errors. I worked this out is by letting the front-end to validate the form for me. Do not know if it possible on your case, but I've used AngularJS to do the trick. It's quite easy, not a single line of code is required if you need simple validation like email format, password length and etc, it's all done by html attributes. You can even disable the submit button until your form is ready to go.
Check out this CodePen
You can scroll to the bottom to the page using this, if still needed:
var objDiv = document.getElementById("your_div");
objDiv.scrollTop = objDiv.scrollHeight
If you need a more robust solution, you might come up with a API that return true or false to whatever field you want to validate. Then make angular make a http request to check it as you type, this way you are 100% sure you form is going to validate. But depending on what you are checking, you might expose a security hole. There is an excellent blog post about this on Ng-Newsletter
For a better experience with forms in Flask you should use the Flask-WTF extension. Install it:
$ pip install flask-wtf
import it
from flask.ext.wtf import Form
and use it along with wtforms module.
.py file:
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('What is your name?', validators=[Required()])
submit = SubmitField('Submit')
.html file:
<form method="POST">
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }} #this is your submit button
</form>
Related
I have a page with a POST form, that have a action set to some url.
i.e assume this page url is /form_url/ :
..
The view in /submit_url/ take care of the form data. After this, I want to return the same page of the form with a success message.
In the view that take care for the POST form, I use HttpResponseRedirect, in order to "clear" the form data from the browser.
But in this way I can't display a message in the form page, unless I do something like:
return HttpResponseRedirect("/form_url/?success=1")
and then check for this parameter in the template. I don't like this way, since if the user refreshes the page, he will still see the success message.
I've noticed that in django admin site, the delete/add of objects does use redirect after POST submit, and still display a success message somehow.
How?
I've already briefly seen django "messaging" app, but I want to know how it work first..
The django admin uses django.contrib.messages, you use it like this:
In your view:
from django.contrib import messages
def my_view(request):
...
if form.is_valid():
....
messages.success(request, 'Form submission successful')
And in your templates:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li {% if message.tags %} class=" {{ message.tags }} " {% endif %}> {{ message }} </li>
{% endfor %}
</ul>
{% endif %}
For Class Based Views use self.request
I also use self.request.path_info in my return
from django.contrib import messages
class MyCreateView(CreateView):
...
def form_valid(self, form):
....
self.object.save()
messages.success(self.request, 'Form submission successful')
return HttpResponseRedirect(self.request.path_info)
Same template as damio's answer:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li {% if message.tags %} class=" {{ message.tags }} " {% endif %}> {{ message }} </li>
{% endfor %}
</ul>
{% endif %}
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(SuccessMessageMixin, CreateView):
model = Author
success_url = '/success/'
success_message = "%(name)s was created successfully"
https://docs.djangoproject.com/en/1.11/ref/contrib/messages/
You don't need to do a redirect to clear the form data. All you need to do is re-instantiate the form:
def your_view(request):
form = YourForm(request.POST or None)
success = False
if request.method == 'POST':
if form.is_valid():
form.save()
form = YourForm()
success = True
return render(request, 'your_template.html', {'form': form})
If the user refreshes the page, they're going to initiate a GET request, and success will be False. Either way, the form will be unbound on a GET, or on a successful POST.
If you leverage the messages framework, you'll still need to add a conditional in the template to display the messages if they exist or not.
Django messages framework stores the messages in the session or cookie (it depends on the storage backend).
If you're using a classed based view with django forms, and granted you've got the messages.html template set up, you can simply pass the 'SuccessMessageMixin' into your view like below
class YourView(SuccessMessageMixin,View):
success_message = 'your message_here'
This will display your message upon form success
I'm trying to render a template that contains a DatePicker, but I'm getting a 500 error when I try. For my the code is correct, but it seems that something is failing or I'm not understanding correctly the way to do it.
The code is the following:
Reporting.py
from flask.ext.admin import BaseView, expose
from wtforms import DateField, Form
from wtforms.validators import Required
from flask.ext.admin.form import widgets
from flask import request
class DateRangeForm(Form):
start_date = DateField('Start', validators=[Required()], format = '%d/%m/%Y', description = 'Time that the event will occur', widget=widgets.DatePickerWidget)
class ReportingView(BaseView):
#expose('/')
def index(self):
form = DateRangeForm(request.form)
return self.render('reporting.j2', form=form)
Reporting template:
{% extends 'admin/master.html' %}
{% block body %}
{{super()}}
Working on it!
{% if form %}
{{form.start_date}}
{% endif %}
{% endblock %}
As davidism says in the comments, the default DateField just provides date parsing, it'll just be displayed as a normal text-input.
If you're ready to fully embrace html5 then you can use the DateField from wtforms.fields.html5 which will render a datepicker in any browser that supports it:
from flask import Flask, render_template
from flask_wtf import Form
from wtforms.fields.html5 import DateField
app = Flask(__name__)
app.secret_key = 'SHH!'
class ExampleForm(Form):
dt = DateField('DatePicker', format='%Y-%m-%d')
#app.route('/', methods=['POST','GET'])
def hello_world():
form = ExampleForm()
if form.validate_on_submit():
return form.dt.data.strftime('%Y-%m-%d')
return render_template('example.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
The more usual approach is to find a nice datepicker online, that's using CSS and JS to handle it's display and include that code into your html template, and tell it to apply the datepicker style to any html element that has a class of datepicker. Then when you generate your form you can just do:
<!-- all your CSS and JS code, including the stuff -->
<!-- to handle the datepicker formatting -->
<form action="#" method="post">
{{ form.dt(class='datepicker') }}
{{ form.hidden_tag() }}
<input type="submit"/>
</form>
Any attributes (that aren't reserved for other use) you pass into rendering the form element will by default just add it as an attribute, for example the {{ form.dt(class='datepicker') }} will generate <input class="datepicker" id="dt" name="dt" type="text" value=""> so your CSS/JS can then do whatever it needs to do to provide a good interface for your user.
I have looked at other similar problems on here and a few other places, but the solutions don't seem to help with my problem. Even though, I am not seeing too much of a difference between this simple code that I've got and other similar code. Especially this one Flask - wtforms: Validation always false
forms.validate_on_submit() is always false and I can't see why.
I'm going through the Flask Web Development Book by Miguel Grinberg, but I wanted to change some things in order to learn more.
It works when using the wtf.quick_form(form) in the html template, but if I remove the quickform entry and put in form fields , then it doesn't work
The screen just refreshes and it doesn't change Stranger to whatever name is entered
HTML index template
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
<form action="" method='POST'>
{{ form.name.label }} <br>
{{ form.name }}
{{ form.submit }}
</form>
{% endblock %}
relevant code hello.py
from flask import Flask, render_template, request
from flask.ext.script import Manager
from flask.ext.bootstrap import Bootstrap
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField, RadioField, TextField, validators
from wtforms.validators import Required
from wtforms.validators import DataRequired
app = Flask(__name__)
class NameForm(Form):
name = StringField('What is your name?',validators=[Required()] )
submit = SubmitField('Submit')
#app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm(request.form) #From the docs I read I don't need
# request.form but it
# doesn't work either with it or without it
if form.validate() == True:
name='True' #never happens is not validating or is always set to False for
# some reason
if form.validate_on_submit(): #isn't validating or working
name = form.name.data #'Stranger' will disappear from the html template and
#replaced with the name the user entered in the
# Stringfield
form.name.data = '' #clear stringfield for next round
return render_template('index.html',form=form, name=name)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=True)'
what am I not understanding\missing?
Thanks
g
The problem is with wtf not finding the CSRF Tokens as part of your form data. Add {{ form.hidden_tag() }} or {{ form.csrf_token }} as the top element of your form.
Just a small remind for anyone who uses bootstrap template form like me.
Be sure to add "name" attribute into the input tag as well. For example,
<label>Your name</label>
<input name = 'name' required>
<label>Your email</label>
<input name = 'email' required>
I have apparently forgotten something really basic about django. Here's my views.py:
def sourcedoc_create(request):
if request.method == 'POST': # If the form has been submitted...
form = SourcedocForm(request.POST, request.FILES) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
handle_uploaded_file(request.FILES['doc'])
form.save()
return HttpResponseRedirect('/index/') # Redirect after POST
else:
form = SourcedocForm() # An unbound form
return render_to_response(
'sourcedoc_create.html',
{'form': form},
RequestContext(request)
Here's the relevant part of urls.py:
url(r'^$', index),
url(r'^index/', index),
url(r'^sourcedoc/create/', sourcedoc_create),
When I run the app, I create the record in the database, and the uploaded file appears successfully in the relevant directory (thus I infer that the form.save worked ok), but then I get:
KeyError at /sourcedoc/create/
0
Request Method: POST
Request URL: http://www.rosshartshorn.net/worldmaker/sourcedoc/create/
Django Version: 1.4.3
It appears that my HttpResponseRedirect is, for whatever reason, not working, and it's trying to re-POST and throwing a KeyError off a blank form? Or something. In any event, it's not redirecting. When I manually go to /index/, all is well, and the new record is there.
Any ideas what is wrong with my redirect?
In case the forms are relevant:
<body>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<h1>New Post</h1>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<input type="submit" value="Submit">
</form>
Also, I'm using mongoforms, which is supposed to work like ModelForms:
from mongodbforms import DocumentForm
class SourcedocForm(DocumentForm):
class Meta:
document = Sourcedoc
It seems to have been at least somewhat related to this post: Django MongodbForms KeyError _meta['cascade'].
So, I just upgraded to Django 1.5 and the newest mongodbforms. This eliminated that error, although I was then getting a problem similar to this:
MongoKit "ImportError: No module named objectid " error
I just implemented keppla's answer from that second post, and it now seems to work!
Hello I'm a newbie trying to use django to register some users, I have been reading the Django Book and am on a chapter about registration,http://www.djangobook.com/en/2.0/chapter14/ when I do the instructions I get this
Forbidden (403)
CSRF verification failed. Request aborted.
Help
Reason given for failure:
CSRF token missing or incorrect.
In general, this can occur when there is a genuine Cross Site Request Forgery, or when Django's CSRF mechanism has not been used correctly. For POST forms, you need to ensure:
Your browser is accepting cookies.
The view function uses RequestContext for the template, instead of Context.
In the template, there is a {% csrf_token %} template tag inside each POST form that targets an internal URL.
If you are not using CsrfViewMiddleware, then you must use csrf_protect on any views that use the csrf_token template tag, as well as those that accept the POST data.
You're seeing the help section of this page because you have DEBUG = True in your Django settings file. Change that to False, and only the initial error message will be displayed.
You can customize this page using the CSRF_FAILURE_VIEW setting.
I put the {% csrf_token %} template tag inside the post tag and it still gives me this error. thanks
# views.py
#
# Copyright 2012 Talisman <KlanestroTalisman#gmail.com>
from django.shortcuts import render_to_response
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.http import HttpResponseRedirect
def home (request):
return render_to_response('FirstTemplate.html',)
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
new_user = form.save()
return HttpResponseRedirect("/books/")
else:
form = UserCreationForm()
return render_to_response("register.html", {
'form': form,
})
forms
{% extends "base.html" %}
{% block title %}Create an account{% endblock %}
{% block content %}
<h1>Create an account</h1>
<form action="" method="post"{% csrf_token %}>
{{ form.as_p }}
<input type="submit" value="Create the account">
</form>
{% endblock %}
Djangobook uses a pretty old version of django, you may be on a newer version, I have tried the information and csrf section is definitely outdated since they had some modification to the way this is handled in newer versions, match your django version with the book version, also some frequent reasons for this error (In addition to the middleware thing mentioned by pahko) are
not using csrf_token tag in template
not using RequestContext class - replace Context with RequestContext
like this
from django.template import RequestContext
and in the render statement
return render_to_response("home/index.html", c, context_instance=RequestContext(request))
note: use your own template path in above statement.
try to put this in your middleware config in settings.py
MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware',
)
hope it helps