Overview

Pooter - simple, smart, and fast

Summary

Pooter is a WSGI Python Web Framework for Google App Engine. It is currently in a pre-alpha state and not ready for production.

Features

  • WSGI Compliant

  • WebOb Request and Response Objects

  • Jinja Templating

  • Declarative Syntax Forms Library

  • JinericViews, class-based generic views (base, create, read, update, delete, detail, and rss). Also has JinericCrud generic admin class that allows easy switching out of default views and url changes.

  • GAEUnit unit testing and Pooter Test Client (very similar to Django's client)

  • Regex URLs

  • Request, Response, Exception, and Not Found class-based middleware

  • Google and Federated auth

  • Beaker sessions

  • Easily integrated zip-packages

Quick Overview/Tutorial

app.yaml (template file - app.yaml.tmpl)

Start with the app configuration file (app.yaml). Just like your regular App Engine app.yaml except you need a handler for the GAEUnit unit tests and the Jineric Views static media.

application: brianproject
version: 1
runtime: python
api_version: 1

handlers:

- url: /favicon.ico
 static_files: pooter/ext/jinericviews/static/images/favicon.ico
 upload: pooter/ext/jinericviews/static/images/favicon.ico

- url: /unittest.*
 script: gaeunit.py
 secure: always

- url: /static
 static_dir: pooter/ext/jinericviews/static

- url: /jineric_media/
 static_dir: pooter/ext/jinericviews/static

- url: /.*
 script: main.py

settings.py (template file - settings.py.tmpl)

Similar to Django's settings.py file

'''
Pooter Settings Module
'''
import os

PROJECT_NAME = 'pooter'
DEFAULT_SYS_PATH = os.path.abspath(os.path.dirname(__file__))

MEDIA_URL = '/static/'

#Relative path to the static media for JinericViews
JINERIC_MEDIA_URL = '/jineric_media/'

SECRET_KEY = 'secretkey'
APPEND_SLASH = True
PROJECT_URL_MAP = 'pooter.core.test_files.urls.url_map'

GAEUNIT_FOLDERS = ('pooter/core/tests',)

JINERICNAV = ('pooter.contrib.jinericprofiles.views.profile_crud',)

models.py

Pooter uses the same property names as default Google App Engine but you import them from pooter.core.models, except for Polymodel which is imported from the default Google App Engine location.

from google.appengine.ext.db.polymodel import PolyModel
from pooter.core import models as db

class Content(PolyModel):
    title = db.StringProperty(required=True)
    description = db.TextProperty()
    date_created = db.DateTimeProperty(auto_now_add=True)
    last_modified = db.DateTimeProperty(auto_now=True)
    created_by = db.UserProperty(auto_current_user_add=True,verbose_name='Created By')
    modified_by = db.UserProperty(auto_current_user=True,verbose_name='Modified By')
    def __unicode__(self):
        return self.title

    class Meta:
        excludes = ('_class','created_by','modified_by','date_created','last_modified',)

views.py

While you can use regular functions to create your views, it is recommended that you use the JinericViews (pooter.ext.jinericviews.views)

from pooter.ext.jinericviews.views import *
from models import TestBlogPost

#BaseView (``pooter.ext.jinericviews.views.base.BaseView``) - Think of base view as your 
#basic view with no extensive logic. If none of other JinericViews 
#suit your needs you can use BaseView as your base. You can also use it as sort of a 
#direct_to_template view as you would in Django.
class TestView(BaseView):
    #Template must be in one of the designated template folders from the settings.py file
    template = 'base.html'

#ReadView (``pooter.ext.jinericviews.views.crud.ReadView``) - View returns a list of 
#paginated objects.

class PostList(ReadView):
    #Model to build the queryset from
    model = TestBlogPost

    #Number of objects to return per page
    paginate_by = 5

    #Jinja2 template to use
    template = 'content/home.html'

    #If you need to change the queryset modify the prepare_queryset method
    def prepare_queryset(self):
        return self.model.all().filter('publish =',True)

    #If you need to add some context to the view use the get_context method             
    def get_context(self,extra_context):
        extra_context['categories'] = Category.all()
        return extra_context

#DeleteView (``pooter.ext.jinericviews.views.crud.DeleteView``) - Delete's an object based on the key
#passed in from the url

class TestDeletePostView(DeleteView):
    model = TestBlogPost

#CreateView (``pooter.ext.jinericviews.views.crud.CreateView``) - Create an entity based on the model 
#specified

class CreatePostView(CreateView):
    model = TestBlogPost

#UpdateView (``pooter.ext.jinericviews.views.crud.UpdateView``) - Update an entity based on the model 
#specified and the key given

class UpdatePostView(UpdateView):
    model = TestBlogPost

#RSSView (``pooter.ext.jinericviews.views.feeds.RSSView``) - Subclass of ReadView that builds a 
#RSS feed

class TestRSSView(RSSView):
    model = TestBlogPost
    channel_title = 'Test Rss View'
    channel_description = "This is just used for unit tests"
    channel_link = 'http://www.example.com'

    @staticmethod
    def item_title(item):
        return item.title

    @staticmethod
    def item_description(item):
        return item.short_description

    @staticmethod
    def item_link(item):
        return item.link

test_rss_view_urls = TestRSSView.get_url()

Folder Structure

This may be a bit different from those coming from Django world. Here are two different ways to setup your Pooter project file structure.

Option 1

This version does not zip the pooter framework.

Folder Structure Option 1

Option 2

In option 2 you can zip the Pooter framework files and copy the JinericViews static media to the static media folder. Just make sure you change your app.yaml Jineric media handler and your JINERIC_MEDIA_URL in the settings.py file.

Folder Structure Option 2

Roadmap

Please feel free to fork Pooter and start working on any of these or other features as well.

Near Future

  • Profiles

  • CSRF Protection

  • More Unit Tests

  • Better Blobstore integration

  • Object-level permissions

  • Django style management commands

  • Switch to GAETestBed or a similar solution

Distant Future

  • Git and Mercurial management command integration

  • Continuous integration with unit test

  • Integration of all of Google's appcfg.py and dev_appserver.py commands into Pooter's management commands

  • Better Caching integration

  • Better Multitenancy and the Namespaces integration

My Guess at Your Questions

Why Create Another Python Framework?

That's simple I wanted a framework that felt like Django but was built from the ground up especially for App Engine. After the App Engine Patch project died in favor of Django Non-Rel I decided to try Django proper or App Engine and after I didn't like that I decided to write Pooter. I wrote Pooter to solve my personal needs so with that said it may not fit every web app's needs.

Who's Writing Pooter?

Brian Jinwright, a Django developer by day from Durham, NC.

How Can I Get Involved?

Fork the project, ask me questions, challenge my design decisions. I know I have probably made some bad decisions so if you see them and you have a solution please let me know.

What Is The Major Philosophy Behind Pooter?

Too early to tell really. If I had to take a quick stab at it I would say "To be as simple, secure, and fast as possible without reinventing the wheel".

Why did you choose Jinja, WebOb, and Beaker?

  • Because I didn't want to write them on my own.

  • They were very similar to Django (as I mentioned earlier my day job uses Django), very efficient (in my opinion), and very active.