turbogears2 pluggable applications
DESCRIPTION
TRANSCRIPT
Pluggable Applications with TurboGears2
Using TurboGears2 pluggable applications
Alessandro Molina@__amol__
TurboGears2● Framework for rapid development encouraging
customization ● Object Dispatch based, regular expressions can get messy,
you will never have to write one anymore ● By default an XML template engine with error detection ● Declarative Models with transactional unit of work ● Built in Validation, Authentication, Authorization, Caching,
Sessions, Migrations, MongoDB Support and many more.
OverView
CONTROLLER class RootController(BaseController): error = ErrorController()
@expose('wiki20.templates.index') def index(self): return dict(page='index')
MODEL class Page(DeclarativeBase): __tablename__ = 'pages' id = Column(Integer, primary_key=True) pagename = Column(Text, unique=True) data = Column(Text)
TEMPLATE <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="master.html" />
<head> <title>My Title</title></head>
<body> <h1>${page}</h1> <div py:for="num in range(10)">${num}</div></body></html>
Object Dispatch
class BlogEntryController(BaseController): @expose() def index(self, post): return 'HI'
@expose() def edit(self, post): return 'HI'
@expose() def update(self, post): return 'HI'
class RootController(BaseController): blog = BlogEntryController()
@expose() def index(self): return 'HI' @expose() def about(self): return 'HI' @expose() def more(self, *args, **kw): return 'HI'
URL CONTROLLER
/index RootController.index
/ RootController.index
/blog/3 BlogEntryController.index(post = 3)
/blog/update?post=3 BlogEntryController.update(post = 3)
/about RootController.about
/more/1/2/3 RootController.more(args[0]=1, args[1]=2, args[3]=3)
/more?data=5 RootController.more(kw['data']=5)
Declarative Model
current_page = DBSession.query(Page).filter_by(pagename='index').first()pages = DBSession.query(Page).all()subpages = DBSession.query(Page).filter(Page.pagename.like('index_%')).all()
DBSession.add(Page(pagename='index_about', data='About Content'))
DBSession.flush()transaction.commit()
TurboGears does this for us if no errors appeared during the request. The Unit of Work will reorder insertions as required by dependencies.
TurboGears will automatically commit transactions on any session used inside the request if no error appears, otherwise a rollback is issued to avoid messing up the database.
Genshi XML templating<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"> <head> <title>Geddit: News</title> </head> <body class="index"> <div id="header"> <h1>News</h1> </div>
<ol py:if="links"> <li py:for="link in reversed(links)"> <a href="${link.url}">${link.title}</a> posted by ${link.username} at ${link.time.strftime('%x %X')} </li> </ol>
<p><a class="action" href="/submit/">Submit new link</a></p>
<div id="footer"> <hr /> <p class="legalese">© 2007 Edgewall Software</p> </div> </body></html>
● Validated, if there is any error in the template structure it will be reported
● XML based, just a bunch of attributes to your
tags, this makes possible to edit the templates with wysiwyg editors
● Conditional tags
● Tags content iteration
● Variables substitution with escaping
● Templates inheritance
Not so fast, if you want superfast templating TurboGears offers Mako support builtin.Mako is not validated and xml editors friendly but it is superfast.
Quickstarting an Application$ virtualenv --no-site-packages -p python2.6 tg2env$ cd tg2env/$ source bin/activate(tg2env)$ easy_install -i http://tg.gy/current tg.devtools(tg2env)$ paster quickstart example(tg2env)$ cd example/(tg2env)$ python setup.py develop(tg2env)$ paster setup-app development.ini(tg2env)$ paster serve development.ini
● We use a virtual environment not to mess with our system wide packages
● Using TurboGears private index will permit to install the exact right version of the packages
● The quickstart command will create for us a new project with authentication, authorization and models.
● We use the setup.py develop command to install the project to be able to run it
● The setup-app command will initialize the database with a bunch of users for us, we can avoid this
if we choose not to enable authentication
● Last we can call the serve command to run out application
Explore Quickstart
Sample Project
● Personal Website ● with Photo Galleries ● a Blog ● and many Wiki like pages
Photo Galleries
from tgext.pluggable import plugplug(base_config, 'photos')
<body> <div id="getting_started"> ${h.call_partial('photos.partials:albums')} <div style="clear:both"/> </div></body>
We Plug tgapp-photos to have ready to use photo albums support
Our Template ends being just a call to the partial that renders the previews of our albums
.photos_gallery { display: block; float: left; text-align: center; margin-left: 10px; margin-bottom: 10px; }
Some CSS to give a minimal look to our gallery
(tg2env)$ easy_install tgapp-photos(tg2env)$ mkdir example/public/attachments
Install tgapp-photos pluggable applications for photo albums support
Our Photo galleries
Argh, it look awful...But this is why CSS exist
I want my blog, Now !
from tgext.pluggable import plugplug(base_config, 'photos')
<body> <div id="getting_started"> ${h.call_partial('photos.partials:albums')} <div style="clear:both"/> ${h.call_partial('smallpress.partials:articles')} </div></body>
We Plug tgapp-photos to have ready to use photo albums support
Add call to smallpress partial to render the list of articles of the default blog
(tg2env)$ easy_install tgapp-smallpressInstall tgapp-smallpress for blogs support and create a directory where it can store attachments
#smallpress_articles { margin-top: 30px; }.smallpress_article h2 { margin-bottom: 0; }.smallpress_article h2 a { text-decoration: none; }.smallpress_article h4 { margin-top: 0; }
Our usual bunch of minimal styling.
Well, somehow it works
What about the other pages?
What's up with those About, Authentication, WSGI and Contact pages? We don't need them for our personal website! Let's remove all the methods in RootController between index and login and their templates!
Doh!
Well, actually we need to remove also the links inside the menu bar. Those are defined into master.html which is the master template that every page inherits.
Let's now add wiki to our site
First of all our model, it's just a page with a title and a content.
from sqlalchemy import Table, ForeignKey, Columnfrom sqlalchemy.types import Unicode, Integer, DateTime, Textfrom sqlalchemy.orm import relation, synonym
from example.model import DeclarativeBase
class WikiPage(DeclarativeBase): __tablename__ = 'wikipages'
uid = Column(Integer, primary_key=True) pagename = Column(Text, unique=True) data = Column(Text)
Let our pages exist
First of all we need to make our WikiPage model available inside the model module:
from example.model.wiki import WikiPage
Then run setup-app again so that our new table get created.
(tg2env)$ paster setup-app development.ini
Time for some magic
from tgext.crud import EasyCrudRestController class WikiController(EasyCrudRestController): model = model.WikiPage
class RootController(BaseController): pages = WikiController(DBSession) [...]
Works out of the box
CRUD did all the work for us, now we just have to create our wiki pages!
Fetch the pages for our menu
_before method get called before calling every controller method and tmpl_context is the TurboGears template context, which is always available inside a request. from tg import tmpl_context class RootController(BaseController): pages = WikiController(DBSession)
def _before(self, *args, **kw): tmpl_context.pages = DBSession.query(model.WikiPage).all()
Let's draw our menu
Inside master.html mainmenu add links for our pages <li py:for="page in getattr(tmpl_context, 'pages', [])"> <a href="${'/%s' % page.pagename}">${page.pagename}</a></li>
Doh!
Again?!Well our website yet doesn't know how to display our wiki pages
Serving our wiki pagesfrom webob.exc import HTTPNotFoundfrom webhelpers.markdown import Markdownclass RootController(BaseController): pages = WikiController(DBSession)
def _before(self, *args, **kw): tmpl_context.pages = DBSession.query(model.WikiPage).all()
@expose('example.templates.index') def index(self): return dict(page='index')
@expose('example.templates.page') def _default(self, *args, **kw): page = None if args: page = DBSession.query(model.WikiPage).filter_by(pagename=args[0]).first()
if not page: raise HTTPNotFound
return dict(page=page.pagename, content=Markdown(page.data).convert())
_default method gets executed every time Object Dispatch is able to resolve the url only up to the controller but is not able to find a method that matches the url
example.templates.page
Very little to do, just show a title and its content. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="master.html" />
<head> <title>${page}</title> </head> <body> <h1>${page}</h1> ${Markup(content)} </body></html>
Markup is required to tell Genshi that the value should not be escaped. webhelpers.Markdown generated nice HTML for us and we want to show the rendering not the HTML itself!
Woh! It works!
Our website is ready! Now we can add any number of pages using the Markdown syntax.
Have Fun!
TurboGears2, its crud and pluggable applications can make as easy as 1,2,3 creating a web application, just open your editor and have fun!
http://www.turbogears.org