I keep receiving an internal server error - 'BadValueError: Property content is required. It seems to happen when I pass blog_table into the template to render. Here's the code:
main.py:
import os
import webapp2
import jinja2
from google.appengine.ext import db
jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__),'templates')), autoescape = True)
class Handler(webapp2.RequestHandler):
def write(self,*a,**kw):
self.response.out.write(*a,**kw)
def render_str(self,template,**params):
t = jinja_env.get_template(template)
return t.render(params)
def render(self,template,**kw):
self.write(self.render_str(template,**kw))
class Blog(db.Model):
subject = db.StringProperty(required = True)
content = db.TextProperty(required = True)
created = db.DateTimeProperty(auto_now_add = True)
class MainPage(Handler):
def render_front(self, subject="", content="", error=""):
blog_table = db.GqlQuery("SELECT * FROM Blog ORDER BY created DESC")
self.render("blog.html",subject = subject,content = content,error = error,blog_table = blog_table)
def get(self):
self.render_front()
def post(self):
subject = self.request.get("subject")
content = self.request.get("content")
if subject and content:
b = Blog(subject = subject, content = content)
b.put()
self.redirect("/")
else:
error = "we need both a subject and some content"
self.render_front(subject,content,error)
app = webapp2.WSGIApplication([('/', MainPage)],debug=True)
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
</head>
<body>
<h1>Blog</h1>
<form method="post">
<label>
<div>Subject</div>
<input type="text" name="subject" value="{{subject}}">
</label>
<label>
<div>Content</div>
<textarea name="content" value="{{content}}"></textarea>
</label>
<div class="error">{{error}}</div>
<input type="submit">
</form>
<hr>
{% for post in blog_table %}
<div class="post">
<div class="subject">{{post.subject}}</div>
<pre class="content">{{post.content}}</pre>
</div>
{% endfor %}
</body>
</html>
To see the error go to http://udacity-cs253-a31.appspot.com/
Thanks.
This is just a guess - changed your Blog model and added the Content attribute recently.
There's some old instances of the Blog model in your datastore that have no Content. If you try to load those, your code fails. If you load a Blog instance with Content, everything is fine.
You have to go through your datastore and ensure all instances of your Blog have Content.
Related
I am newbie and after reading documentation of django . i made forms but my form is not saving to my db and not showing in my db . please help me i tried so many ways but still its not saving . i have drivers for this purpose i want to register them . but its not saving to my db . and I think its not posting data to my db.
please help me .
Views.py
def driver_form(request):
args = {}
template = Template.objects.get(template_default__exact=1)
template_page = template.template_alias + str("/rentacar/rentacar_driver_form.html")#sir this ?
return render(request, template_page, args)
def driver_save(request):
args = {}
if request.POST:
driver_firstname = request.POST.get('driver_firstname')
driver_lastname = request.POST.get('driver_lastname')
driver_save_form = DriverForm(request.POST)
if driver_save_form.is_valid():
new_driver = driver_save_form.save(commit=False)
new_driver.driver_firstname = driver_firstname
new_driver.driver_lastname = driver_lastname
new_driver.save()
template = Template.objects.get(template_default__exact=1)
template_page = template.template_alias + str("/rentacar/rentacar_driver_form.html")
return render(request, template_page, args)
else:
return HttpResponseRedirect('/')
else:
return HttpResponseRedirect('/')
rentacar_driver_form
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div class="container">
<form method="POST" action="/rentacar/driver-save/">
{% csrf_token %}
<label>
First Name<br>
<input type="text" name="driver_firstname" required>
</label>
<br>
<label>
Last Name<br>
<input type="text" name="driver_lastname" required>
</label>
<br>
<input type="submit" class='btn btn-primary' value="Submit">
</form>
</div>
</body>
</html>
Forms.py
from __future__ import unicode_literals
from django import forms
from rentacar.models import *
class DriverForm(forms.ModelForm):
class Meta:
model = BookingApproval
exclude = (
'driver_firstname',
'driver_lastname',
)
just change
if request.POST:
...
inside of the driver_save view to
if request.method == "POST":
...
Try changing:
driver_firstname = request.POST.get('driver_firstname')
driver_lastname = request.POST.get('driver_lastname')
to:
driver_firstname = request.POST['driver_firstname']
driver_lastname = request.POST['driver_lastname']
I'm trying to implement Select2 field in one of my flask views. Basically I want the same select2 field in my flask application view (not a flask admin modelview) as in Flask-admin model create views. Currently my solution has been QuerySelectField from wtforms that looks something like this
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all())
This allows me to load and select all the data I need, but it does not provide select2 search box etc. Currently all that I have found is Select2Field and Select2Widget from flask/admin/form/fields and flask/admin/form/widgets similarly like in this post https://stackoverflow.com/questions/24644960/how-to-steal-flask-admin-tag-form-field and also select2 documentation at http://ivaynberg.github.io/select2/
As I understand these could be reusable, meaning there is no need for other custom widgets, custom fields.
Would be grateful if someone could provide more information about select2 field implementation in flask application (including views, templates, forms files and how to correctly "connect" with neccessary js and css files, also how to load the field up with the database model I need).
This worked for me:
...
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from flask_admin.form.widgets import Select2Widget
...
class TestForm(Form):
name= QuerySelectField(query_factory=lambda: models.User.query.all(),
widget=Select2Widget())
And in your template:
{% extends "admin/master.html" %}
{% import 'admin/lib.html' as lib with context %}
{% block head %}
{{ super() }}
{{ lib.form_css() }}
{% endblock %}
{% block body %}
...
{% endblock %}
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
{% endblock %}
I can try to put together a minimal working example if necessary.
I had a similar requirement and have put together a minimal example.
Note the following:
Class TestView defines three routes; a get view, a post view and an Ajax lookup view.
Function get_loader_by_name takes a string name and returns a QueryAjaxModelLoader. This function is used in both the Ajax lookup call and in the TestForm field definitions.
The text displayed in the Select2 widgets is the value returned by the User model's __unicode__ method.
I've used Faker to generate the sample user data.
File app.py:
from flask import Flask, render_template, url_for, request, abort, json, Response
from flask.ext.admin import Admin, BaseView, expose, babel
from flask.ext.admin.contrib.sqla.ajax import QueryAjaxModelLoader
from flask.ext.admin.model.fields import AjaxSelectField, AjaxSelectMultipleField
from flask.ext.sqlalchemy import SQLAlchemy
from wtforms import Form
from faker import Factory
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
try:
from flask_debugtoolbar import DebugToolbarExtension
DebugToolbarExtension(app)
except:
pass
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Unicode(length=255), nullable=False)
last_name = db.Column(db.Unicode(length=255), nullable=False)
email = db.Column(db.Unicode(length=254), nullable=False, unique=True)
def __unicode__(self):
return u"{first} {last}; {email}".format(first=self.first_name, last=self.last_name, email=self.email)
def get_loader_by_name(name):
_dicts = {
'user': QueryAjaxModelLoader(
'user',
db.session, User,
fields=['first_name', 'last_name', 'email'],
page_size=10,
placeholder="Select a user"
)
}
return _dicts.get(name, None)
class TestView(BaseView):
def __init__(self, name=None, category=None,
endpoint=None, url=None,
template='admin/index.html',
menu_class_name=None,
menu_icon_type=None,
menu_icon_value=None):
super(TestView, self).__init__(name or babel.lazy_gettext('Home'),
category,
endpoint or 'admin',
url or '/admin',
'static',
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._template = template
#expose('/', methods=('GET',))
def index_view(self):
_form = TestForm()
return self.render(self._template, form=_form)
#expose('/', methods=('POST',))
def post_view(self):
pass
#expose('/ajax/lookup/')
def ajax_lookup(self):
name = request.args.get('name')
query = request.args.get('query')
offset = request.args.get('offset', type=int)
limit = request.args.get('limit', 10, type=int)
loader = get_loader_by_name(name)
if not loader:
abort(404)
data = [loader.format(m) for m in loader.get_list(query, offset, limit)]
return Response(json.dumps(data), mimetype='application/json')
# Create admin and Test View
admin = Admin(app, name='Admin', template_mode='bootstrap3')
admin.add_view(TestView(template='test.html', name="Test", url='/test', endpoint='test'))
class TestForm(Form):
single_user = AjaxSelectField(
loader=get_loader_by_name('user')
)
multiple_users = AjaxSelectMultipleField(
loader=get_loader_by_name('user')
)
#app.route('/')
def index():
return render_template("index.html", link=url_for('test.index_view'))
def build_db():
db.drop_all()
db.create_all()
fake = Factory.create()
for index in range(0, 1000):
_first_name = fake.first_name()
_last_name = fake.last_name()
_user_db = User(
first_name=_first_name,
last_name=_last_name,
email="{first}.{last}{index}#example.com".format(first=_first_name.lower(), last=_last_name.lower(), index=index)
)
db.session.add(_user_db)
db.session.commit()
#app.before_first_request
def before_first_request():
build_db()
if __name__ == '__main__':
app.debug = True
app.run(port=5000, debug=True)
File templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
</head>
<body>
Go to the test form
</body>
</html>
File templates/test.html:
{% import 'admin/static.html' as admin_static with context %}
{% import 'admin/lib.html' as lib with context %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Select2</title>
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap-theme.min.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='admin/css/bootstrap3/admin.css') }}" rel="stylesheet">
{{ lib.form_css() }}
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-2">
<form>
{{ lib.render_form_fields(form) }}
</form>
</div>
</div>
</div>
<script src="{{ admin_static.url(filename='vendor/jquery-2.1.1.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap3/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/moment-2.8.4.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js') }}" type="text/javascript"></script>
{{ lib.form_js() }}
</body>
</html>
Update July 2018
Added a standalone Flask extension available on Github - Flask-Select2 - WIP.
I recently implemented a "tags" field in the front-end of a Flask app, using Select2 and WTForms. I wrote a sample app, demonstrating how I got it working (the view code for populating the select options, and for dynamically saving new options, is where most of the work happens):
https://github.com/Jaza/flasktaggingtest
You can see a demo of this app at:
https://flasktaggingtest.herokuapp.com/
No AJAX autocomplete in my sample (it just populates the select field with all available tags, when initializing the form). Other than that, should be everything that you'd generally want for a tagging widget in your Flask views / templates.
Other answers seem not to work anymore. What I did to make it work is:
On the backend:
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
...
users = users = QuerySelectMultipleField('users', query_factory=lambda: User.query.order_by(User.fullname).all(),
get_label=lambda u: u.fullname, get_pk=lambda u: u.id)
Then in the frontend:
$("#users").attr("multiple", "multiple").select2();
You need to manually add css and js for select2.
I'm having trouble getting my ancestor queries to display the links associated with Tom's photos that are stored in the datastore. Nothing is displayed from the datastore even though there are several links in the datastore. Any assistance will be appreciated. Thanks in advance.
main.py
import webapp2
import jinja2
import os
from google.appengine.ext import db
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
class MainPage(webapp2.RequestHandler):
def get(self):
tom = Person(key_name='Tom')
wedding_photo = Photo(parent=tom)
wedding_photo.image_url='http://pinterest.com/pin/200691727117011230/'
wedding_photo.put()
baby_photo = Photo(parent=tom)
baby_photo.image_url='http://pinterest.com/pin/518828819542052681/'
baby_photo.put()
dance_photo = Photo(parent=tom)
dance_photo.image_url='http://pinterest.com/pin/257197828689352707/'
dance_photo.put()
dog_photo = Photo()
dog_photo.image_url='http://pinterest.com/pin/279575089339614779/'
dog_photo.put()
photo_query = Photo.all()
photo_query.ancestor(tom)
message = "Photos"
template_values = {
'message': message,
'photo_query': photo_query,
}
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render(template_values))
class PhotoStore(webapp2.RequestHandler):
def post(self):
photo = Photo()
photo.image_url = self.request.get('image_url')
photo.put()
self.redirect('/')
class Person(db.Model):
name = db.StringProperty()
class Photo(db.Model):
image_url = db.StringProperty()
app = webapp2.WSGIApplication([('/', MainPage),
('/new_photo',PhotoStore)], debug=True)
index.html
<html>
<body>
<form action="/new_photo" method="post">
<label for="photo">Photo</label>
<div><textarea name="image_url" rows="1" cols="60" id="image_url"></textarea></div>
<input type="submit" value="Submit">
</form>
<hr></hr>
<div><b>{{ message }}</b></div>
<b>{% for p in photo_query.run(limit=5): %}</b>
<div>{{ p }}</div>
<b>{% endfor %}</b>
</body>
</html>
What do you see if you open the generated html source? I assume that the code {{ p }} just shows string representation of the Photo objects, which results in showing strings like <__main__.Photo object at 0xfcbd9ef0> that is interpreted as HTML tag and doesn't show in the rendered HTML.
I'm a newbie programmer and new to Google App Engine and webapp2 etc. So this may be a very basic question.
I am creating an application to store images into BlobStore. My model stores description, blob_key, image url and date.
I am able to save everything, so that bit is okay.
But now I want to create a delete button which will not only delete an item from the datastore, but also delete the image saved in the blobstore.
I have created a DeleteHandler, and in the html I have a form, passing the key for the item I want to delete. In the DeleteHandler, I am using the posted key to delete the item from the datastore. I am also trying to use the key to use it delete the image saved in the blobstore.
So far I'm getting a 404 on the delete form post, and even if I get past that, I'm not sure if my DeleteHandler is correct to handle the functionality I am looking for.
Any help would be much appreciated..
Main.py:
import os
import urllib
import webapp2
from google.appengine.ext.webapp import template
from google.appengine.ext import blobstore
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.api import images
#Models
from google.appengine.ext import db
class ImageItem(db.Model):
description = db.StringProperty(required=True)
img_url = db.StringProperty()
blob_key = blobstore.BlobReferenceProperty()
when = db.DateTimeProperty(auto_now_add=True)
#Handlers (Views)
class MainHandler(webapp2.RequestHandler):
def get(self):
upload_url = blobstore.create_upload_url('/upload')
imgs = db.GqlQuery(
'SELECT * FROM ImageItem '
'ORDER BY when DESC')
imgs_dict = {'imgs': imgs}
self.response.out.write( template.render( 'main.html',locals() ) )
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
f = self.get_uploads('file')[0] # 'file' is file upload field in the form
img =ImageItem(description=self.request.get('description'))
img.blob_key = f.key()
img.img_url = images.get_serving_url( f.key() )
img.put()
self.redirect('/')
class DeleteHandler(webapp2.RequestHandler):
def post(self):
key = self.request.get('k')
item = db.get(key)
images.delete( item.blob_key )
item.delete()
self.response.out.write(key)
#URL Routing happens here
app = webapp2.WSGIApplication([('/', MainHandler),
('/upload', UploadHandler),
('/delete', DeleteHandler)],
debug=True)
Main.html:
<form action="{{upload_url}}" method="POST" enctype="multipart/form-data">
<p>
<label for="file">Upload File</label>
<input type="file" name="file" id="file">
</p>
<p>
<label for="description">Description</label>
<input type="text" id="description" name="description">
</p>
<input type="submit" name="submit" value="Submit">
</form>
<ul>
{% for i in imgs %}
<li>
<img src="{{i.img_url}}=s400-c" alt="">
{{i.description }}
</li>
<li>{{i.when }}</li>
<li>
<form action="/delete" method="POST" enctype="multipart/form-data">
<input type="text" name="k" value="{{i.key}}" />
<input type="submit" value="delete">
</form>
</li>
{% endfor %}
</ul>
The deletes are close. Once you have a key, you can delete entities by calling db.delete(key).
For your example, this would be something like this:
class DeleteHandler(webapp2.RequestHandler):
def post(self):
key = self.request.get('k')
item = db.get(key)
blobstore.delete([item.blob_key])
db.delete(item)
self.response.out.write(key)
Your url handling in main.py is good, so it's not obvious to me why you're getting a 404. You could double-check your app.yaml file to make sure all urls are passed to main.py.
Here's a sample app.yaml handlers url section:
handlers
- url: /.*
script: main.app
Got it to work. Thanks Eric, yours was real close. I needed to use blob_key.key().
Final code is following:
class DelHandler(webapp2.RequestHandler):
def post(self):
key = self.request.get('k')
item = db.get(key)
n = item.blob_key.key()
blobstore.delete(n)
item.delete()
self.redirect('/')
Hi how can I get the variables in a HTTP post with WTForms when the post is done with a blobstoreuploadhandler and preferably also with i18n localized messages for validation?
This is my code that is not working:
class AdForm(Form):
name = TextField(_('Name'))
title = TextField(_('title'))
text = TextAreaField(_('Text'),widget=TextArea())
phonenumber = TextField(_('Phone number'))
phonenumberhide = BooleanField(_('Display phone number on site'))
price = TextField(_('Price'))
password = PasswordField(_('Password'))
email = TextField(_('Email'))
When I try to access the data posted via the form the data turns out as None:
form = AdForm(data=self.request.POST)
if form.title:
logging.info('getting title:'+form.title.data)
ad.title = form.title.data
ad.save()
The above does not save anything to the datastore and this is the template where it's coming from
<div class="labelform">
<div class="labelform" style="clear:left;">
<label> {% filter capitalize %}{% trans %}title{% endtrans %}{% endfilter %}:</label>
</div>
</div>
</td><td>
{{ form.title }}{% if form.title.errors %}
<ul class="errors">{% for error in form.title.errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
Can you help me? There's something in the WTForms manual about appengine but I couldn't find a working example.
Update
I added validation tests and I still can't access the variables:
logging.info('getting requests')
if form.validate():
if form.title:
logging.info('getting title:'+form.title.data)
ad.title = form.title.data
ad.save()
ad.put()
Logging output:
INFO 2011-11-05 23:17:24,653 main.py:1504] getting requests INFO
2011-11-05 23:17:24,653 main.py:1507] getting title:
Update 2
I removed the WTForms dependence and it is still not working. The line logging.info('getting data:'+ self.request.get('title', '0')) only outputs 0 even though the form is just a regular http post form:
<form action="{{form_url}}" name="upload" method="post" enctype="multipart/form-data" accept-charset="utf-8">
Update 3
This minimal config with no WTForms and no Jinja works so it's probably something with Jinja when this bare-bones example works with webapp2 and python 2.7 where I'm going to add the faulty code line by line to troubleshoot:
class GuestPage(BaseHandler):
def get(self):
self.response.out.write("""
<html>
<body>
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")
class Guestbook(BaseHandler, I18NHandler, blobstore_handlers.BlobstoreUploadHandler):
csrf_protect = False
def post(self):
self.response.out.write('<html><body>You wrote:<pre>')
self.response.out.write(self.request.get('content'))
self.response.out.write('</pre></body></html>')
app = webapp2.WSGIApplication([ ('/guest', GuestPage),
('/sign', Guestbook),
...
Update 4
My back to basics is working with Jinja so I suppose I just build on this example and see where it breaks:
class GuestPage(BaseHandler):
def get(self):
self.render_jinja('form_jinja')
class Guestbook(BaseHandler, I18NHandler, blobstore_handlers.BlobstoreUploadHandler):
csrf_protect = False
def post(self):
self.response.out.write('<html><body>You wrote:<pre>')
self.response.out.write(self.request.get('content'))
self.response.out.write('</pre></body></html>')
Update 5
I can reproduce the error with this minimal example that can't access the http post variable:
class GuestPage(webapp2.RequestHandler):
def get(self):
self.response.out.write("""
<html>
<body>
<form action=" """ +blobstore.create_upload_url('/sign')+ """ " method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")
class Guestbook(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
self.response.out.write('<html><body>You wrote:<pre>')
self.response.out.write(self.request.get('content'))
self.response.out.write('</pre></body></html>')
app = webapp2.WSGIApplication([ ('/guest', GuestPage),
('/sign', Guestbook),
Update 6
From the guestbook example code with blobstoreuploadhandler I can upload a file on the production server so I could make a working example that uses the blobstoreuploadhandler that I will try to build on for my use case.
Update 7
I could get my original code so that everything works except the blob transfer. I suspect a diff between dev_appserver and production that I posted to the google appengine group about. We'll see how it progresses.
Update 8
Here's another common use how nothing works when you add WTForms:
logging.info('getting data:'+ self.request.get('title', '0'))
logging.info('http post data:'+ str(self.request.post))
form = AdForm(formdata=self.request.data)
logging.info('populated form')
logging.info('form data:' + str(form.formdata))
if form.validate():
if form.title:
logging.info('getting title:'+str( form.get('title') ) )
ad.title = form.title.data ad.save() ad.put()
if form.text:
logging.info('getting text:' +str(form.text))
ad.text = form.text.data
if self.request.get('currency'):
ad.currency = self.request.get('currency')
if self.request.get('cg'):
ad.category = form.cg.data
if self.request.get('company_ad') == '1':
ad.company_ad = True
ad.put()
else:
logging.info('form did not validate')
except Exception, ex:
logging.info('there occured exception %s', str(ex))
INFO 2011-11-09 12:11:50,868 main.py:1385] getting data:TEST INFO
2011-11-09 12:11:50,868 main.py:1409] there occured exception post
Update 9
Finally the form populates it just doesn't validate. Thank you Sean for the info that got me further. Now I get past populated the form object with no exception but the exception occurs when I try to validate:
logging.info('getting data:'+ self.request.get('title', '0'))
form = AForm(self.request.POST)
logging.info('populated form')
if form.validate():
logging.info('validated form')
The above code is logging the output:
INFO 2011-11-11 08:03:59,913 main.py:1387] getting data:TEST
INFO 2011-11-11 08:03:59,914 main.py:1390] populated form
INFO 2011-11-11 08:03:59,914 main.py:1412] there occured exception 'builtin_function_or_method' object is not iterable
What does the exception mean?
My form class is
class AForm(Form):
name = TextField(_('Name'))
title = TextField(_('title'))
text = TextAreaField(_('Text'),widget=TextArea())
phonenumber = TextField(_('Phone number'))
phonenumberhide = BooleanField(_('Display phone number on site'))
price = TextField(_('Price'))
password = PasswordField(_('Password'))
email = TextField(_('Email'))
category = SelectField(choices=categories.keys)
I don't know anything about WTForm, but I'd guess that like Django forms, you need to call the validation function before you can access the data. In this case, it's form.validate():
form = AdForm(formdata=self.request.POST)
if form.validate():
ad.title = form.title.data
Daniel actually it is not data=self.request.POST that you need to pass to the form but formdata instead of data
http://wtforms.simplecodes.com/docs/dev/forms.html#the-form-class
hope it will be usefull for all those who rushed through the doc as i did