Pooter is a WSGI Python Web Framework for Google App Engine. It is currently in a pre-alpha state and not ready for production.
WebOb Request and Response Objects
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)
Request, Response, Exception, and Not Found class-based middleware
Google and Federated auth
Easily integrated zip-packages
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',)
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
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',)
While you can use regular functions to create your views, it is recommended that you use
the JinericViews (
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()
This may be a bit different from those coming from Django world. Here are two different ways to setup your Pooter project file structure.
This version does not zip the pooter framework.
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
in the settings.py file.
Please feel free to fork Pooter and start working on any of these or other features as well.
More Unit Tests
Better Blobstore integration
Django style management commands
Switch to GAETestBed or a similar solution
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.