RESTful + Controller = Restler

Restler is a base controller for `Pylons` projects that provides a set of default RESTful actions that can be overridden as needed. It also handles database  connectivity as long as a few simple rules are followed.

It adds a bit of 'convention-over-configuration' to Pylons and takes some inspiration from Rails' scaffold_resource generator. Templates aren't generated at this time, but a set of default generic templates are supplied which can be copied and modified to suit.

`ActiveMapper` model classes are required. This means you can also use `Elixir`, since `Elixir` uses `ActiveMapper` "under the hood." If you're looking for the most 'Rails-esque' experience, you'll probably want to use `Elixir`.


== Quick Start ==

=== Install Pylons 0.9.5 ===

{{{
easy_install Pylons>=0.9.5
}}}

Pylons 0.9.4.1 should work fine too for the time being. Future development
and testing will be done with Pylons 0.9.5.


=== Make sure Routes 1.6.3 is installed ===

Restler relies on some features of Routes not found in earlier versions of
Routes. If you just installed or upgraded to Pylons 0.9.5, you can skip this
step.

{{{
easy_install Routes>=1.6.3
}}}


=== Install Restler ===

{{{
easy_install -U Restler
}}}

Or, install from SVN:

{{{
svn checkout http://restler.googlecode.com/svn/trunk/ Restler
cd Restler
python setup.py develop
}}}


=== Create a new Pylons project ===

{{{
paster create --template=pylons MyProject
cd MyProject
}}}

To use Mako instead of Myghty for templating (recommended), modify line 32 of `myproject/config/middleware.py`:

Change

{{{
config.init_app(global_conf, app_conf, package='myproject')
}}}

to

{{{
config.init_app(global_conf, app_conf, package='myproject', template_engine='mako')
}}}


=== Add your database configuration to development.ini ===

Add a line like this to the `[app:main]` section:

{{{
sqlalchemy.dburi = <db_type>://<user>:<password>@<host>/<database>
}}}

Replace <db_type>, <user>, <password>, <host>, and <database> to reflect your setup. For example:

{{{
sqlalchemy.dburi = sqlite:///%(here)s/myproject.db
}}}

=== Make your project Restler aware ===

Open `myproject/lib/base.py` and add the following lines below the existing imports:

{{{
import restler
RestController = restler.BaseController(model)
}}}

You can also create a new `RestController` class in `lib.base` that inherits from `restler.BaseController`. First, remove the line `RestController = restler.BaseController(model)` added above, then add a class like this (which shows a quick and dirty way to secure the `new`, `edit`, and `delete` actions for _all_ controllers):

{{{
class RestController(restler.BaseController(model)):
    def __call__(self, environ, start_response):
        return super(RestController, self).__call__(environ, start_response)

    def new(self):
        abort(403)
    edit = delete = new
}}}

In real life, you'll probably want to use `AuthKit` or something similar to protect these actions.

Note: Don't remove the default `BaseController`; it's needed by the `error` and `template` controllers.


=== Declare one or more `ActiveMapper` or `Elixir` `Entity` classes in models/__init__.py ===

I prefer `Elixir`, so that's what this example uses.

{{{
from elixir import metadata, Entity, has_field, has_many, belongs_to
from elixir import String, Integer

class Directory(Entity):
    """A `Directory` contains `Page`s."""
    has_field('title', String(40))
    has_field('slug', String(20))
    has_many('pages', of_kind='Page')

class Page(Entity):
    """A `Page` belongs to a `Directory`."""
    has_field('title', String(40))
    has_field('slug', String(20))
    has_field('content', String)
    has_field('sidebar', String)
    belongs_to('directory', of_kind='Directory')
}}}

Entity classes should use the "member name" of their associated resources ('Page' in this example).


=== Create a controller for each of the Entity classes declared above ===

Use the "collection name" of the associated resource as the controller name:

{{{
# directories.py
from myproject.lib.base import *
class DirectoriesController(RestController): pass

# pages.py
from myproject.lib.base import *
class PagesController(RestController): pass
}}}


=== Map URLs for resources/entities to controllers ===

Add routes to `myproject/config/routing.py` for each of the Entity classes declared above. Add them right below the default 'error' route. You might want to remove the default `:controller/:action/:id` route

{{{
map.resource('directory', 'directories')  # member name, collection name
map.resource('page', 'pages',
    parent_resource={'member_name': 'directory', 'collection_name': 'directories'})
}}}


=== Create the database tables for the entity classes you declared above ===

Open myproject/websetup.py and add these imports:

{{{
import sqlalchemy
import myproject.models as model
}}}

Add this line to the bottom of the `setup_config` function:

{{{
    engine = sqlalchemy.create_engine(conf['sqlalchemy.dburi'])
    engine.echo = True
    model.metadata.connect(engine)
    drop_all_tables()
    create_all_tables()
}}}

Add these functions, then close websetup.py:

{{{
def drop_all_tables():
    really_drop = raw_input('Really drop all tables?! [yes/N] ')
    if really_drop.lower().strip() == 'yes':
        print 'Dropping all tables...'
        model.metadata.drop_all()
    else:
        print 'Tables not dropped'

def create_all_tables():
    print 'Creating all tables...'
    model.metadata.create_all()
    print 'Done creating all tables.'
}}}

Run this command:

{{{
paster setup-app development.ini
}}}


=== Create templates for the basic CRUD actions ===

Actually, instead of creating templates, you can just use Restler's default templates. There are a few ways to do this.

    # Locate the Restler installation directory in your python2.x/site-packages folder and copy the directory `/path/to/restler/templates/defaults` to your project's templates folder, `myproject/templates/defaults`
    # Create a symbolic link from `myproject/templates/defaults` to `/path/to/restler/templates/defaults`
    # If your project is in a Subversion repository, you can use svn:externals like this:
        {{{
cd myproject/templates
svn propedit svn:externals .
Add this line to the file that opens:
    defaults http://restler.googlecode.com/svn/trunk/restler/templates/defaults
Save, quit, and `svn up`
        }}}

Using Restler's default templates requires importing Restler's helpers. Add the following import to myproject/lib/helpers.py:

{{{
from restler.helpers import *
}}}

You might want to use Restler's default stylesheet and images. To do so, copy the images, javascripts, and stylesheets directories from  `/path/to/restler/public` to `myproject/public`.

To create your own templates, make subdirectories in your project's templates directory using each resource's collection name. For the `Directory` model, you'd create `myproject/templates/directories`; for the `Page` model, you'd create `myproject/templates/pages`. Templates added to these directories will override templates with the same name in the `defaults` directory, so you can selectively override the defaults.


=== Fire up your Pylons app and try it out ===

{{{
paster serve --reload development.ini
}}}

You should now be able to Create, Read, Update, and Delete resources.

    * GET http://localhost:5000/directories => show directories list (paginated)
    * POST http://localhost:5000/directories => create a new directory with POST data
    * GET http://localhost:5000/directories/1 => show directory with ID 1
    * GET http://localhost:5000/directories/root => use "slug" instead of ID
    * GET http://localhost:5000/directories/1;edit => show edit form for directory with ID 1
    * GET http://localhost:5000/directories/root/pages => list pages in root directory
    * POST http://localhost:5000/directories/root/pages => create a new page with POST data
    * PUT http://localhost:5000/directories/root/pages/1 => update page #1 in root directory with PUT data
    * DELETE http://localhost:5000/directories/root/pages/1 => delete page with ID 1 in root directory


=== Look at all the things I'm _not_ doing ===

    * The only database config required is specifying the connection parameters in the `ini` file (the `sqlalchemy.dburi` setting)
    * No actions have been defined in the application's controllers
    * Only one line is required per entity to create routes for all the basic actions

Of course, as you customize your app and it gets more complex, you'll have to modify the basic setup, but it's a good starting point (at least, it's supposed to be; if you find that it's not, please do make an [http://code.google.com/p/restler/issues/list issue] out of it).


=== Epilogue ===

Restler was extracted from the byCycle.org Bicycle Trip Planner (http://tripplanner.bycycle.org).

Send feedback, corrections, et cetera to wyatt .DOT. lee .DOT. baldwin .AT. gmail .DOT. com or [http://code.google.com/p/restler/issues/list create an issue].
