from google.appengine.ext import db class Post(db.Model): title = db.StringProperty(required=True) content = db.TextProperty(required=True) content_html = db.TextProperty(required=True) # we cache category_name within Post to avoid JOIN query category_name = db.CategoryProperty(required=True) category = db.ReferenceProperty(Category, required=True) # we also cache tags for same reason tags = db.StringListProperty(required=True) # these 2 fields will be autopopulated by GAE, see docs for more info created_at = db.DateTimeProperty(required=True, auto_now_add=True) updated_at = db.DateTimeProperty(required=True, auto_now=True)
The code is pretty straightforward. Now we have to create form that will represent our Post model. Flask has very nice integration with WTForms and we are going to use it, because WTForms officially supports standard App Engine models. That means that WTForms is able to generate form from supplied model. You have to download WTForms and Flask-WTF extension. To generate form from existing model we have to use method model_form(). Create file blog/forms.py:
from flaskext import wtf from wtforms.ext.appengine.db import model_form # model_form() will collect properties from models.Port and convert them to appropriate fields PostForm = model_form(models.Post, base_class=wtf.Form)
Now we have model that can store our post and form that can display it. We can implement view that will create posts using model and form. Create file blog/views.py:
# this view is accessible via URL http://localhost:8080/create-post using GET and POST methods @module.route('/create-post', methods=['GET', 'POST']) def create_post(): form = forms.PostForm() if form.validate_on_submit(): # form was submitted and validated flash(u'Post successfully create') # notify user # create post post = models.Post(title=form.title.data, content=form.content.data) using data from form # save post in datastore post.put() # redirect user to the post return redirect(post.get_absolute_url()) # if form was not submitted or user provided invalid data just render template with our form return render_template('blog/create_post.html', form=form)
The only thing that I still did not explained is rendering form. We are using Jinja2 as template engine and we will use some reusable functions (macros in Jinja2 terms) that will help us render our form. You can see these macros at github. Template that renders form is located at blog/post/_form.html:
<!-- Import form macro I mentioned above --> {% from '_form_macros.html' import form_field_td %} <form method="post" action="" enctype="multipart/form-data"> <table> <!-- Here we manually render each field --> <!-- It is boring but with this approach we can easily move and group fields --> <tr>{{ form_field_td(form.title) }}</tr> <tr>{{ form_field_td(form.category_name) }}</tr> <tr>{{ form_field_td(form.content) }}</tr> </table> <fieldset class="submit"> <input type="submit" name="submit" value="Submit"> </fieldset> </form>
The last thing that we will do today is displaying posts. To do this we have to create new view:
# this view will be accessible via URL like http://localhost/view/flask-on-gae @module.route('/view/<slug>', methods=['GET']) def view_post(slug): # slug is used as key for model # get post by key post = models.Post.get_by_key_name(slug) return render_template('blog/post/post.html', post=post)
Template that renders post is very simple:
{% extends 'base.html' %} {% block body %} <h1>{{ post.title }}</h1> <p>Category: {{ post.category_name }} @ {{ post.created_at }}</p> {{ post.content_html|safe }} {% endblock body %}
I think it is enough for today. I don't explain a lot of things that are covered by official docs. If you don't understand anything please ask questions. Demo is here: Flask on GAE
Edited by Roger, 29 November 2010 - 10:19 AM.
added hyperlink