WTForms takes the pain out of handling forms and validation, you define your form, tell it what validation checks the data needs to pass, then WTForms uses it's widgets to generate the html.
A common problem you'll face is "Mutiple select fields suck, they are confusing-- how can I show the user a nice list of checkboxes instead?"
The answer is to tell WTForms to use a different widgets to render it's html. Lets first show how we'd render a simple SelectMultipleField form:
from flask import Flask, render_template_string from wtforms import SelectMultipleField, Form app = Flask(__name__) data = [('value_a','Value A'), ('value_b','Value B'), ('value_c','Value C')] class ExampleForm(Form): example = SelectMultipleField( 'Pick Things!', choices=data ) @app.route('/') def home(): form = ExampleForm() return render_template_string('<form>{{ form.example }}</form>',form=form) if __name__ == '__main__': app.run(debug=True)
Voila, we have a basic horrible to use multiple select field, now we just need to change how WTForms renders that SelectMultipleField by telling it to use a couple of different widgets to produce our checkboxes
from wtforms import widgets class ExampleForm(Form): example = SelectMultipleField( 'Pick Things!', choices=data, option_widget=widgets.CheckboxInput(), widget=widgets.ListWidget(prefix_label=False) )
And that's it-- you'll be rendering check boxes for each of the sets of data, congratulations!
Ideally a user would be able to post directly to an S3 storage bucket, and that in turn would alert you to the identifier, but this is the ugly version that ends up with the same result, good enough for images, but latency would only grow with filesize.
So we are going to create a very simple Flask application, that serves up a form to the user, asking for a file. When the file is uploaded to the server, the server then sends it off to S3 for long-term storage, and returns the unique file that was created in the storage bucket to the user.
The HTML template that gets rendered is a very simple form, but because we're using Flask-WTF for handling our form validation and presentation details, we also need to remember the CRSF protection that handily comes with it:
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<form method="POST" enctype="multipart/form-data" action=".">
{{ form.csrf_token }}
{{ form.example }}
<input type="submit" value="Go">
</form>
The {% %} might look a little strange, but all it is doing is saying 'If the webserver has passed me a flashed message, show it'. You can read more about message flashing in the documentation. We're going to use it to send a message back to the user when the S3 upload is complete.
The the application itself, very bare bones
from flask import Flask, render_template, flash from flask.ext.wtf import FileField, Form from tools import s3_upload app = Flask(__name__) app.config.from_object('config') class UploadForm(Form): example = FileField('Example File') @app.route('/',methods=['POST','GET']) def upload_page(): form = UploadForm() if form.validate_on_submit(): output = s3_upload(form.example) flash('{src} uploaded to S3 as {dst}'.format(src=form.example.data.filename, dst=output)) return render_template('example.html',form=form) if __name__ == '__main__': app.run()
Nothing out of the ordinary here. The basic process is to check if the form sent by the user is valid, if it is, call the s3_upload function and pass in the FileField, which includes a FileStorage object holding the uploaded data. The application brings in some configuration values from a config.py file, shown below— customize it to match your account details.
S3_LOCATION = 'http://your-amazon-site.amazonaws.com/' S3_KEY = 'YOURAMAZONKEY' S3_SECRET = 'YOURAMAZONSECRET' S3_UPLOAD_DIRECTORY = 'what_directory_on_s3' S3_BUCKET = 's3_bucket_name' SECRET_KEY = "FLASK_SECRET_KEY" DEBUG = True PORT = 5000
So the only bit of code left is the actual S3 upload implementation, and luckily with the boto library it's pretty straight forward.
from uuid import uuid4 import boto import os.path from flask import current_app as app from werkzeug import secure_filename def s3_upload(source_file,acl='public-read'): source_filename = secure_filename(source_file.data.filename) source_extension = os.path.splitext(source_filename)[1] destination_filename = uuid4().hex + source_extension # Connect to S3 conn = boto.connect_s3(app.config["S3_KEY"], app.config["S3_SECRET"]) b = conn.get_bucket(app.config["S3_BUCKET"]) # Upload the File sml = b.new_key("/".join([app.config["S3_UPLOAD_DIRECTORY"],destination_filename])) sml.set_contents_from_string(source_file.data.readlines()) # Set the file's permissions. sml.set_acl(acl) return destination_filename
By default we are going to give the uploaded file permissions to be publically read, there are a number of other ACL options which you can optionally choose.
That's it in a nutshell, have a look at the full project on Github and drop me a note if you feel I've skimmed over something, or have suggestions to improve the implementation.
WeasyPrint converts HTML including images to PDF, it's cross platform but Windows requires a small amount of massaging to persuade it to work.
It depends on PyGTK, which can be easily installed on Windows using using their all-in-one installer.
Next we need to install the lxml library, which is easiest to install from a precompiled binary, so head over to Christoph Gohkle's Python Packages and download and install the correct version of lxml for your setup.
Now you should be able to just pip install WeasyPrint
After getting slightly stumped on using jqPlot with a getJson ajax call and finding very little on the subject in the usual places, I thought I'd write it up for anyone who hits a similar wall. You'll find the solution is actually incredibly simple, but the jqPlot documentation leads you down a slightly different path.
In this short guide, we'll go through making a date graph, with dates on the x-axis and value on the y-axis. I'll focus on the html/json element first to make it more applicable to a wider reader, and then show how I generated it all in Flask for those that are interested in the web-service side.
The end result can be seen here. Refreshing the page generates a new chart, with a new random series of json data, which you can view here.
jqPlot says it can handle a wide variety of human parsable date formats, but to avoid problems and ambiguity, we'll use JavaScript native format, which is milliseconds since the Unix epoch (Jan 1, 1970). Python's standard mktime returns seconds since that epoch, so we can make a simple translation (x 1000) between the two if your translating from a python datetime.
Essentially we want to build a JSON array that looks like...
{ "output": [ [ 1339199340000.0, 328], [ 1339173060000.0, 451], [ 1339201620000.0, 948] ] }
For testing you can just paste that into a text file on your web server, we'll grab it with getJSON in a little while. On my test server, I set it to be accessed at /_get_data
All my scripts are assumed to be in /static/
<!DOCTYPE html> <html> <head> <title>Date Axes</title> <link class="include" rel="stylesheet" type="text/css" href="/static/jquery.jqplot.min.css" /> <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="/static/excanvas.js"></script><![endif]--> <script class="include" type="text/javascript" src="/static/jquery.min.js"></script> </head> <body> <div id="chart" style="height:300px; width:650px;"></div> <script class="code" type="text/javascript"> $(document).ready(function(){ $.getJSON('/_get_data',function (data) { console.log(data.output); $.jqplot('chart', [data.output], { title:'Customized Date Axis', axes:{ xaxis:{ renderer:$.jqplot.DateAxisRenderer, tickOptions:{formatString:'%b %#d, %#I %p'}, } }, series:[{ lineWidth:2, markerOptions:{style:'diamond',size:10}, rendererOptions: { smooth: true } }] }); }); }); </script> <script class="include" type="text/javascript" src="/static/jquery.jqplot.min.js"></script> <script class="include" language="javascript" type="text/javascript" src="/static/plugins/jqplot.dateAxisRenderer.min.js"></script> </body> </html>
I've added a few options into the graph to give you an idea how to customise
it to your own desires.
The main tripping point I fell over was with getting the JSON array into the correct format for jqPlot to handle. Turns out I just needed to wrap it within another array. Lets have a closer look.
$.getJSON('/_get_data',function (data) { console.log(data.output); $.jqplot('chart', [data.output], { ... chart options ...
So we call getJSON and provide it with the link to the JSON array we've created. Once we've grabbed it, we'll begin the callback function which begins the rendering process, rendering some data to our console, so we can see what's going on, and calling the jqplot function to render the chart.
Our JSON is a named array, but we just want the meat of the array, so we'll pass the data.output (our array was named 'output') wrapped in another array to the jqplot. With that, the rest is just customization.
This is very simple, the only part that might catch you out is the millisecond vs seconds from epoch differences between Python and Javascript.
from flask import Flask, render_template, jsonify from datetime import datetime, timedelta import time from random import randint app = Flask(__name__) def js_timestamp(dt): """ Return a javascript timestamp from a datetime object. Javascript times are milliseconds since the epoch, whereas python uses just seconds, so lets return the times in the javascript format, so it's unambiguous to jqPlot. """ return time.mktime(dt.timetuple()) * 1000 @app.route('/_get_data') def get_date(): """ Generate a JSON array of 30 random dates and times """ data = list() start_date = datetime(2012,6,8,12,00) for i in range(30): random_value = randint(1,1000) random_offset = timedelta(minutes=randint(1,1000)) data.append([js_timestamp(start_date+random_offset), random_value]) return jsonify(output=data) @app.route('/') def home(): """ Simply serve our chart page """ return render_template('chart.html') if __name__ == '__main__': app.run(debug=True)
Easy. If I've missed something, or you have some suggestions for improving the clarity of the code, please drop me an email.
You can grab the working example from github.
I recently ran into a problem on how best to handle complex model relationships in web-forms using Flask-WTForms and Flask-SQLAlchemy. Let's have a look at the model to get a better understanding:
tags = db.Table('tags', db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')), db.Column('article_id', db.Integer, db.ForeignKey('article.id')) ) class Tag(db.Model): __tablename__ = 'tag' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(30)) class Article(db.Model): __tablename__ = 'article' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200)) content = db.Column(db.Text()) date_created = db.Column(db.DateTime(), default=datetime.now) tags = db.relationship('Tag', secondary=tags, backref=db.backref('articles',lazy='dynamic'))
The relationship between the Article and the Tag is m2m— an Article may have many Tags and a Tag in turn may be associated to many Articles. So we create a relationship mapping that. With that in place, I can then query the Article very easily to find out which Tags are associated with it, or Articles associated with a Tag.
>>> single_article = Article.query.first() >>> single_article.tags [<tag_a>,<tag_b>,<tag_c>] >>> single_tag = Tag.query.filter_by(name='tag_a').first() >>> single_tag.articles [<article_one>,<article_two>]
I then used Flask-WTF to build a simple form to create and edit the article
class ArticleForm(Form): title = TextField('Post Title',[Required()]) tags = TextField('Tags') content = TextAreaField('Content')
One of the nice features of Flask-WTF is the ability to autopopulate the model, a typical way to do that is:
@app.route('/edit/<int:id>',methods=['GET','POST']) def edit_post(id): post = Article.query.filter_by(id=id).first_or_404() form = ArticleForm(request.form, post) if form.validate_on_submit(): form.populate_obj(post) db.session.commit() return redirect(url_for('home')) return render_template('edit_post.html', form=form)
The problem of course comes in when we have to manage the tags. In my case the user enters the tags as a comma separated string, which Flask-WTF can't use to autopopulate the Tags relationship, it doesn't know how to turn that string into Tag objects, and how to treat them. So we need to put a bit more work into our model to help that process be as smooth as possible. We'll leverage the property builtin to let us control how the data is handled by adding the following to the bottom of the Article class.
def get_tags_csv(self): return ",".join(x.name for x in self.tags) def set_tags_csv(self,value): current = self.tags new = (x for x in value.strip().split(',')) self.tags = [] db.session.commit() for tag in new: if len(tag): self.tags.append(Tag(tag.strip())) db.session.commit() tags_csv = property(get_tags_csv, set_tags_csv)
And change the ArticleForm to use that tags_csv property instead of the tags relationship.
Now we can call article.csv_tags and the property builtin will forward that to the get_tags_csv function, which presents us and Flask-WTF with a neat formatted string to use on the form's input field.
>>> single_article = Article.query.first() >>> print single_article.tags_csv 'tag_a,tag_b,tag_c'
And when we populate the tags_csv property, it calls the set_tags_csv method, which takes our plain string and turns it into Tag objects. It also handles the database, removing existing Tags before adding the new ones in.
With this in place, Flask-WTF has the knowledge required to get and set the objects properties, so we can continue to use the populate_obj method and save writing some code. Having your model handle it's own heavy lifting makes your code more portable and your views more simple.