CRUD, variable_decode, and generics

7 views
Skip to first unread message

Ben Bangert

unread,
Feb 17, 2006, 2:44:47 PM2/17/06
to pylons-...@googlegroups.com
I've been playing around a bit more with random CRUD approaches.
First up, is a scaffold like controller that uses the new paginate
stuff from WebHelpers. Here's what it looks like, its intended to be
put in lib/base.py, and then you make your own fairly basic set of
templates and a controller. In my case, I made a Person Sqlobject
class like this:

class Person(SQLObject):
first_name = StringCol()
last_name = StringCol()

Then I made a person controller that looked like this:

class PersonController(CRUDController):
model = model.Person
individual = 'person'
template_prefix = '/person'
conditions = dict(orderBy='last_name')

I'm using class attributes to tell the CRUDController where the
templates are, what an individual instance should be called, and
conditions are passed into the SQLObject query that displays the
list. As I mentioned, it does use pagination which is kind of cool,
here's the CRUDController I put in lib/base.py:


class CRUDController(BaseController):
def index(self):
h.redirect_to(action='list')

def new(self):
setattr(c, self.__class__.individual, {})
m.subexec(self.__class__.template_prefix + '/new.myt')

def create(self):
obj = m.request_args[self.__class__.individual]
self.__class__.model(**obj)
h.redirect_to(action='list')

def destroy(self, id):
self.__class__.model.get(id).destroySelf()
h.redirect_to(action='list')

def edit(self, id):
obj = self.__class__.model.get(id)
objdict = dict([(x, getattr(obj, x)) for x in
obj._columnDict.keys()])
objdict['id'] = obj.id
setattr(c, self.__class__.individual, objdict)
m.subexec(self.__class__.template_prefix + '/edit.myt')

def update(self, id):
obj = self.__class__.model.get(id)
objargs = m.request_args[self.__class__.individual]
obj.set(**objargs)
h.redirect_to(action='detail')

def list(self):
options = getattr(self.__class__, 'conditions', {})
pages, collection = paginate(self.__class__.model,
m.request_args.get('page', 1), **options)
setattr(c, self.__class__.individual + '_pages', pages)
setattr(c, self.__class__.individual + '_collection',
collection)
m.subexec(self.__class__.template_prefix + '/list.myt')

def detail(self, id):
setattr(c, self.__class__.individual,
self.__class__.model.get(id))
m.subexec(self.__class__.template_prefix + '/detail.myt')

There's just 5 fairly short templates used to make this come
together. I've attached them as a zip to this message if you're
curious what they look like.

person.zip

Ben Bangert

unread,
Feb 17, 2006, 2:52:51 PM2/17/06
to pylons-...@googlegroups.com
Forgot to mention, if you're trying to get that code to work, make
sure you upgrade to the latest WebHelpers, and add this to the top of
lib/base.py:
from webhelpers.pagination import paginate, Paginator

Cheers,
Ben

myha...@gmail.com

unread,
Feb 17, 2006, 6:54:55 PM2/17/06
to pylons-discuss
i also think crud should not complex,but simple to use.because normal
crud templates can be modified in practice.
ben,your example is very simple,is good enough to run.i look at code in
trunk ,paginate is
supported by sqlobject and sqlalchemy.but if other is supported by the
both orm,does it do same so?i think it should use interface to abtract
it,not only in paginate.

Ben Bangert

unread,
Feb 17, 2006, 8:35:54 PM2/17/06
to pylons-...@googlegroups.com

True, there's so few ORM's out there, I was rather hoping just to
keep the wrappers necessary inside the paginate package. I will add a
fall-back so that if the ORM class used is not SQLObject or
SQLAlchemy, it will assume that it can support the two container
operations needed (len and slicing).

- Ben

Ben Bangert

unread,
Mar 5, 2006, 5:05:35 PM3/5/06
to pylons-...@googlegroups.com
On Feb 17, 2006, at 5:35 PM, Ben Bangert wrote:

> True, there's so few ORM's out there, I was rather hoping just to
> keep the wrappers necessary inside the paginate package. I will add a
> fall-back so that if the ORM class used is not SQLObject or
> SQLAlchemy, it will assume that it can support the two container
> operations needed (len and slicing).

I've updated WebHelpers so that the Pagination ORM support can now
handle being tossed an SQLAlchemy Mapper or Table object. I've also
been playing around with generic controller stuff a bit with
SQLAlchemy, trying to work out best use patterns with Pylons and
organization of the resources.

The approach Django makes by having a way to setup Form information,
validation information, and db information, all within a fairly
concise Model metaclass looks rather appealing. I've been toying with
several approaches that are similar, however there's no use repeating
their work if there's not a massive payoff. The payoff I have in mind
here, is something that is framework-neutral, and uses a
significantly more powerful ORM, SQLAlchemy. Huy Do has been working
on some stuff that uses a combination in a similar spirit, but keeps
the added flexibility of SA so I'm looking into that as well.

But back to the generics and SQLAlchemy.....

The layout looks like this:
models/
__init__.py - Stores the actual plain old Python classes
that are used
tables.py - Stores the SQLAlchemy table definitions
forms.py - Stores the FormEncode schemas
contentstor.py - Some helpers for making a more functional Model
class

I then setup the Python class to have a validate method that uses the
FormEncode schema. One of the nice things about SQLAlchemy is that
you can delay and even swap the db engine at a later point. This made
it rather easy to put a database option in the config file, yet be
able to load it all up from the command prompt and connect the models
to the db of my choosing.

This time when making the generics, rather than having you sub-class
them, you continue to sub-class your BaseController, and we use
multiple inheritance to mix-in the methods desired. There's two
classes, one with list/detail views, one with edit/destroy/create
views. It's even fairly easy for the list view to know if the edit/
destroy/create are present, so you can show Edit/Destroy/New options
if they're mixed in.

Here's the user.py controller:

from app1.lib.base import *

class UserController(BaseController, generics.View, generics.Modify):
model = model.User
conditions = dict(order_by='name')


Of course, we still need to make the templates, but you'll want to
customize those no matter what. I'm including in this tarball, my
models dir, the templates relevant, and the lib files relevant for
those interested. It's a rather small set of code, which makes it a
heck of a lot quicker for getting CRUD going with SQLAlchemy, and I
see the process only improving in the future.

I should note that I kept the FormEncode schema separate, because its
very useful in many forms to combine schemas for larger, more complex
forms. Maybe you're editing 4 users at once, or editing a user plus
related content. FormEncode makes it a snap to include schemas back
and forth so you can validate the whole request params in one go
before having to even bother pushing the data into individual objects
and validate it there.

Please note that I called my app 'app1', you'll need to change this
where appropriate before using it. Also remember to set a db connect
string in the dev ini file under your [app:main] section called
database.

Cheers,
Ben

user_example.zip

count...@gmail.com

unread,
Mar 31, 2006, 11:56:58 AM3/31/06
to pylons-discuss
I know that it has been a while since user_example.zip was posted. I am
having a little trouble. I was just wondering if Ben could provide and
example of what websetup.py should look like for setting up the
connection to the db and creating the user object.

Thanks!

Reply all
Reply to author
Forward
0 new messages