My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
DBSprocketsAPI  
Information about how one might extend DBSprockets for a purpose-built controller.
Updated Feb 4, 2010 by perciou...@gmail.com

The DBSprockets API is designed to allow the developer to rapidly develop data forms by automatically selecting the correct field types and validators based on database metadata. Ultimately, features like Foreign Key drop down menus will be standardized in the system.

Here is an example of how you can use a built-in sprocket in your TG2 controller.

See the demoModel for reference.

Your template would be as simple as this:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
    />
<body>
     ${addView(value=addValue)}
</body>

Cached Widgets

DBSprockets provides a widget caching system, which means that it stores your widgets the first time they are created which reduces the amount of overhead required for object creation in subsequent form manipulations. The caching system is easy to use because it acts like a simple dictionary, transparent to the developer.

Extensible

The power of DBSprockets is that every part of it is extensible so customizing forms, widget templates and other portions of your database view is made easier. Simply override the fields you want to change by name or type with what you want. Want a whole set of sprockets that have the same modification? No problem, create a Sprockets cache with your new defaults.

Validation

DBSprockets creates automated validation so that you don't have to.

Sprockets

The most straightforward way of extending dbsprockets to cache custom form widgets is to make a sprocket and associate it with a key. You can then retrieve your form at a latter time, simply by calling it up from the sprockets cache you created earlier. Additionally, this can be done at start-up so that there will be no lag for the first user to use your form. Here is an example of a registration form.

Note that in the demoModel, the user has an "enabled" field which should later be checked by an admin. Also note the user_group table which has foreign keys connecting the user to a group. This is important later on.

Now, here is how we create a custom sprocket for a user registration form:

from mytgproject.model import metadata
from dbsprockets import SAProvider
from dbsprockets.sprockets import Sprockets, Sprocket, AddRecordSessionConfig, AddRecordViewConfig, ViewFactory
from dbsprockets.validator import validate

from tg.controllers import redirect
from toscawidgets.widgets.forms.fields import PasswordField
from formencode.validators import FieldsMatch
from formencode import Schema

class CustomRegistrationViewConfig(AddRecordViewConfig):
    _hiddenFields   = ['enabled', 'user_id', 'tg_groups', 'created']
    _requiredFields = ['password', 'passwordVerification', 'user_name', 'email_address']

    def __init__(self, provider, identifier='tg_user', controller=''):
        AddRecordViewConfig.__init__(self, provider, identifier, controller)

    def getWidgetArgs(self, id=None):
        d = AddRecordViewConfig.getWidgetArgs(self, id)
        passwordVerification = PasswordField('passwordVerification', label_text='Verify')
        d['children'].append(passwordVerification)
        d['validator'] = Schema(chained_validators=(FieldsMatch('password',
                                                                'passwordVerification',
                                                                 messages={'invalidNoMatch': 
                                                                "Passwords do not match"}),))
        return d

provider = SAProvider(metadata)
global_sprockets = Sprockets(provider, '')
sessionConfig = AddRecordSessionConfig('customRegistration', provider, 'tg_user')

viewFactory = ViewFactory()
viewConfig = CustomRegistrationViewConfig(provider)
view=viewFactory().create(viewConfig)
sprocket = Sprocket(viewFactory, sessionConfig)
global_sprockets['customRegistration'] = sprocket

class RootController(BaseController):

    sprockets = global_sprockets

    @expose('tgbrewerycontroller.templates.index')
    def index(self):
        from datetime import datetime
        #flash("Your application is now running")
        return dict(now=datetime.now())
    
    @expose('tgbrewerycontroller.templates.register')
    def register(self, **kw):
        sprocket=self.sprockets['customRegistration']
        view = sprocket.view.widget
        value = sprocket.session.getValue(values=kw)
        return dict(view=view, value=value)

    @expose()
    @validate(error_handler='register')
    def add(self, **kw):
        if kw['tableName'] == 'tg_user':
            provider.add(**kw)
        flash("Your registration request has been made and is awaiting approval.")
        redirect('/')

Now, there is a lot going on here, so I am going to break it down a bit.

from mytgproject.model import metadata
from dbsprockets import SAProvider
from dbsprockets.sprockets import Sprockets, Sprocket, AddRecordSessionConfig, AddRecordViewConfig
from dbsprockets.validator import validate

These are the basic imports to get dbsprockets up and running. The SAProvider is the link from dbsprockets to SQLAlchemy. You must import the metadata from your projects model for the SAProvider to work. We will need the sprocket caching system, and the AddRecord Session and View Configurations.

from tg.controllers import redirect
from toscawidgets.widgets.forms.fields import PasswordField
from formencode.validators import FieldsMatch
from formencode import Schema

We are going to be adding a password validation field to this form and therefore we will need to borrow some validator stuff from formencode, as well as a password field from Toscawidgets. The redirect is there so we have a place to send the user after they register successfully.

class CustomRegistrationViewConfig(AddRecordViewConfig):
    _hiddenFields   = ['enabled', 'user_id', 'tg_groups', 'created']
    _requiredFields = ['password', 'passwordVerification', 'user_name', 'email_address']

This is the real meat-and-potatoes part of the example, showing how easily you can override the dbsprockets defaults to get a usable form. First off, we don't want the user to see the enabled, user_id, tg_groups or created fields, so we hide them. We want a validation error to be thrown if the person forgets their password, verification, user_name or email_address. DBSprockets will take care of whether or not an entered email address is valid, however, so we don't need anything special for that.

    def __init__(self, provider, identifier='tg_user', controller=''):
        AddRecordViewConfig.__init__(self, provider, identifier, controller)

We want this widget to default to the tg_user table, so we set those defaults and pass them off to the super class

    def getWidgetArgs(self, id=None):
        d = AddRecordViewConfig.getWidgetArgs(self, id)

Lets get the parent form's widget arguments

        passwordVerification = PasswordField('passwordVerification', label_text='Verify')
        d['children'].append(passwordVerification)

and to them we are going to add a new password verification field.

        d['validator'] = Schema(chained_validators=(FieldsMatch('password',
                                                                'passwordVerification',
                                                                 messages={'invalidNoMatch': 
                                                                "Passwords do not match"}),))

Then we are going to add a validator to the form so that the two passwords can be compared.

        return d

and finally we return the enhanced dictionary.

provider = SAProvider(metadata)
global_sprockets = Sprockets(provider, '')
sessionConfig = AddRecordSessionConfig('customRegistration', provider, 'tg_user')

sprocket = Sprocket(CustomRegistrationViewConfig(provider), sessionConfig)
global_sprockets['customRegistration'] = sprocket

Here we are registering our custom registration view configuration with the sprocket's cache. We are not modifying the session config, so we just use the default one that dbsprockets already has. This would be a good place to put default data in, if you had something that did not already match the database definition defaults.

class RootController(BaseController):

    sprockets = global_sprockets

    @expose('tgbrewerycontroller.templates.index')
    def index(self):
        from datetime import datetime
        #flash("Your application is now running")
        return dict(now=datetime.now())

we need to send a copy of the global sprockets to the controller so that the validator can pick up our sprocket view widgets.

    @expose('tgbrewerycontroller.templates.register')
    def register(self, **kw):
        sprocket=self.sprockets['customRegistration']
        view = sprocket.view.widget
        value = sprocket.session.getValue(values=kw)
        return dict(view=view, value=value)

Then we create a new method which will allow the user to enter their data. We grab the sprocket from the cache we created above and feed it to the web form, as well as feeding the value from the session. kw is needed so that validation can pass it's data through session config before it reaches the screen.

    @expose()
    @validate(error_handler='register')
    def add(self, **kw):
        if kw['tableName'] == 'tg_user':
            provider.add(**kw)
        flash("Your registration request has been made and is awaiting approval.")
        redirect('/')

Finally we get to add some code that actually processes the user's request. The @validate constructor sends the user back to the register method if there is a problem. The provider makes it very easy to add the user data with one line of code. Finally, we send a message to TG and redirect to the root page.

Your template for register.html would be as simple as this:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
    />
<body>
     ${view(value=value)}
</body>

The end result looks something like this:

and example project utilizing the DBSprockets code can be found here: http://pybrewery.googlecode.com/svn/trunk/TGBreweryController/

WidgetSelector

So you don't like how DBSprockets selects it's field widgets. Well ok, then you are welcome to write your own. Simply Extend WidgetSelector and override the appropriate attributes and functions and boom!, your own custom way of selecting field widgets.

Provider

At the lowest level, DBSprockets IProvider gives the developer a way to hook their preferred database mechanism into a usable form. Simply create a class which Extends dbmechanic.IProvider and code the hook functions. For testing, you can simply inherit from dbmechanic.test.testIProvider. Any missed functions will cause an error letting you know that you need to test a given function. When functionally testing your Provider, a NotImplementedError Exception will be raised in the event you did not write that hook function. This way the programmer is encouraged to both write tests and have a completely working Provider. more on IProvider


Sign in to add a comment
Powered by Google Project Hosting