pyramid_formalchemy provides a CRUD interface for Pyramid based on FormAlchemy. Which is to say it looks at your SQLAlchemy models and automagically generates an HTML interface to create, read, update and delete instances of those models from the database, very much like Django’s admin interface. That sounds pretty cool, doesn’t it? In this post I will demonstrate how to get it up and running and then start customizing the forms.
The Pyramid FAQ suggests you use pyramid_formalchemy if you want a Django style admin interface for Pyramid. I tried it and found that while the documentation is terrible it looks like pyramid_formalchemy could be a good way to do this. It didn’t come out perfectly straight away, and it took some detective work to figure out how to customize it at all, but it looks like it’s actually quite easy to customize in any way you would like.
There’s also fa.bootstrap which looks nice, and I think is based on pyramid_formalchemy, but I didn’t try, and I’m not sure what the license is.
A Quick Introduction
I will create a toy Pyramid app that I can use to keep track of which rock climbing routes I’ve climbed. This will involve first setting up a Pyramid app with sqlalchemy and pyramid_formalchemy, and then creating a sqlalchemy model class to store the info in the database, allowing pyramid_formalchemy to provide the forms to add and edit the information. So following along with the pyramid_formalchemy docs:
Create The App And Get It Up And Running
$ # create the python environment and install necessary packages $ mkvirtualenv formalchemy $ pip install six $ pip install pyramid_formalchemy $ pip install pyramid_fanstatic $ pip install fa.jquery
$ # use Pyramid's pcreate to start a Pyramid app with sqlalchemy scaffolding. $ pcreate -s alchemy rockclimber
as the docs say, update
setup.py to add
$ # now add the pyramid_formalchemy scaffolding $ python setup.py develop $ cd .. $ pcreate -s pyramid_fa rockclimber
README_FORMALCHEMY.txt says, add
config.include('rockclimber.fainit') to rockclimber/init.py:
$ # a missing dependency apparently $ pip install couchdbkit $ # run Pyramid's autogenerated db initialization script to create a sqlite db and tables. $ initialize_rockclimber_db development.ini $ # run the development web server $ pserve development.ini
and woo! that’s pretty fancy! the dummy MyModel class that the alchemy scaffold creates is available to CRUD.
Add My Own Model
So now it’s time for my rock climbing route model.
add to rockclimber/models.py
from sqlalchemy import Boolean, Date class Route(Base): ''' A SQLAlchemy model class that will persist in the database all the information about a rock climbing route that I want to keep track of. ''' <strong>tablename</strong> = 'routes' id = Column(Integer, primary_key=True) name = Column(Text) rating = Column(Text) guidebook = Column(Text) route_type = Column(Text) is_indoor = Column(Boolean, default=False) location = Column(Text) date_climbed = Column(Date) climbing_partners = Column(Text) is_lead = Column(Boolean, default=True) notes = Column(Text)
$ # recreate the database, and restart the web server $ rm rockclimber.sqlite $ initialize_rockclimber_db $ pserve development.ini
Nice! So that was easy. There are a couple of things wrong with this though, the dates months are weird and it would be nice to have Route Type be a select.
Add A Select Input And A Better Renderer For The Date
One of the files created when the pyramid_fa scaffold was run was called faforms.py. If we make that file look like this:
from formalchemy import forms from formalchemy import tables from rockclimber import models class FieldSet(forms.FieldSet): pass class Grid(tables.Grid): pass route_type_options = [('', ''), ('boulder', 'boulder'), ('sport', 'sport'), ('trad', 'trad')]
Create a specially named instance of FieldSet that
pyramid_formalchemy will find and use for the Route model.
Route = FieldSet(models.Route) Route.configure( options=[ Route.route_type.dropdown(options=route_type_options), Route.date_climbed.date(), ] )
then we get a dropdown for route and avoid the month problem by using a different renderer for date altogether. There’s still a problem with the dropdown where the current value isn’t selected when you edit the model though.
So there we have it. You should take my solution for the dropdown and date rendering with a grain of salt as I’m not by any means an expert. This did seem to me to be the way you’re supposed to do it because the scaffold creates the faforms.py and then the pyramid_formalchemy views.py does this sort of thing:
350 @actions.action() 351 def new(self): 352 fs = self.get_fieldset(suffix='Add')
231 def get_fieldset(self, suffix='', id=None): 232 """return a FieldSet object bound to the correct record for id. 233 """ 234 request = self.request 235 model = id and request.model_instance or request.model_class 236 form_name = request.model_name + suffix 237 fs = getattr(request.forms, form_name, None) 238 if fs is None: 239 fs = getattr(request.forms, request.model_name, 240 self.fieldset_class)
which is to say that on Add it first looks for an instance of a FieldSet named
RouteAdd in faforms.py and then, if it fails to find that, for an instance of a FieldSet named
Route in the same place, before using a default
Would I Use This For Real?
So, the documentation is bad. I couldn’t find a bunch of tutorials or articles about it when googling for help, and some of the stuff I did find was out of date. The FormAlchemy google group I found had posts mostly between 2007 – 2012 (although more recent posts did have responses) which suggests it’s not currently very actively used.
pyramid_formalchemy last got a commit in April 2013. There’s an obvious glaring problem with the out of the box date renderer.
On the other hand pyramid_formalchemy and FormAlchemy seem mature and fully featured. It was quite easy to get up and running and quite easy to customize once I understood what I needed to do. I was able to create a useful app in a couple of hours (even with all the googling) typing very few lines of code. Which is awesome.
I plan on using it again. Have you tried (and perhaps had better success with?) any other packages that solve this problem?