Friday, April 14, 2006

Papr.info

I have just finished getting my new project to the "usable" stage, so I thought I'd go ahead and let you know about it. It's an academic paper sharing/tagging site (slightly similar to del.icio.us, but for papers). I guess it's kind of a "mash-up" since it uses at least 3 different sites' APIs (Google, CiteSeer, and Citidel). Take a look and tell me what you think!

Thursday, March 16, 2006

A hypothesis on why Pythonistas reinvent wheels

Well, maybe the title's more inflammatory than need be. This post is kind of a follow-up to Three Reasons You Shouldn't Write Your Own Web Framework, but it's a little broader than that.

My hypothesis is that Python has two "things" that appear together in few (if any!) other languages right now, and that the combination of these ingredients makes the proliferation of web frameworks (and libraries, and RSS readers, and wikis, etc....) nearly unavoidable.

Python has power



First off, Python is a powerful, expressive language. Sure, the Lispers out there deride its lack of macros, Rubyists bemoan its "impure OO", and so on. But Python is expressive enough. Enough to lower the amount of pain it inflicts upon programmers to where some would rather build a framework/library that's just right instead of grabbing something else that's out there. It's just so stinkin' easy to code up something quickly that will do exactly what you want. (Well, almost what you want, and you know how to avoid those bugs, anyway. ;-) )

But there are other languages that are more expressive, concise, "powerful." Why have they, too, not embarked upon the path of infinite diversity?

Python has programmers



Python is one of the most widely-used dynamic languages in the world today. Yes, Perl is more widely used. [Edit 4/1/07: In my completely uninformed opinion as someone who has never used Perl for anything even remotely serious,] Perl also makes it very easy to get yourself in trouble very quickly when building a large system. Not that it can't be done. Just that it kind of fails the "powerful" criterion above. Ruby is "getting there," or it may already have surpassed Python's popularity (hard to know for sure with such scientific measurements as "# of Google search results", "# of new books", "# of SourceForge/FreshMeat projects", etc. But Ruby has not had the time to develop the same critical mass of programmers with a hankering to build their own framework. Haskell, ML, Smalltalk, and so forth just don't have the base, either. Lispers built quite a few frameworks/libraries back in the day, but these were mainly in the realm of AI, so we won't hear much about them until AI comes back in vogue.

What to do?



I can't say I really know. After seeing Ian's tutorial on building your own WSGI framework, I see the allure. Working in academia, I also feel the urge to build upon prior research. Maybe it's the nature of the beast (having a widely-used, expressive language). What do you think?

Monday, March 06, 2006

TG Admin Interface (part II)

Well, the week was less-than-productive on the automated TG admin pages front. I did manage to extract the CRUD code from the model class, however. Here is the (new) code to create an admin page for a given SQLObject model:


import model
from crud import crudbase, fields

class DistributionCenterView(crudbase):
modelClass=model.DistributionCenter
displayName='Distribution Center'
fields=fields('name', 'note',
'address.street1', 'address.street2',
'address.city', 'address.state', 'address.zip',
'stores', 'trucks')
key='name'

this is for the following SQLObject model:

class DistributionCenter(SQLObject):
def destroySelf(self):
self.address.destroySelf()
SQLObject.destroySelf(self)

name=StringCol(default=uniqueValue('DistributionCenter', 'name', 'ctr'),
alternateID=True)
note=StringCol(default='')
address=ForeignKey('Address', default=newAddressID, cascade=False)
stores=MultipleJoin('Store')
trucks=MultipleJoin('Truck')

The resulting list view looks a little like this (simplified a bit for blogger):








































Name Note Street1 Street2 City State Zip Stores Trucks

Edit
Delete
Dist Ctr A A note on dca 123 Main Street Marietta GA 30062 Home Dep 3 SXG 456,ABC 123

Edit
Delete
Center 2 Another Center 456 Beechwood Ave. Marietta GA 30067 Store Num 1,store_2 333 HFF

As you can see, foreign keys and multiple joins are handled, as well as columns from foreign tables (via the "address.street1..." stuff). I'm still working on cleaning things up, but if this code would be a useful starting point for you, drop me a comment and I'll email you a copy. Alternatively, if there are several people interested, we'll set up a project on SourceForge.

Monday, February 27, 2006

Django-like automated admin interface

Categories: , , ,

As I continue to upgrade ConsulTracker and work on my new (not-yet announced) project, I often find myself wanting a nice CRUD-like interface, something akin to what I’ve heard Django has. While less masochistic developers might go for the “real deal” of Django, I chose to begin adding admin-style CRUD pages for TurboGears, the Python web framework with which I’m most familiar.





Requirements



Now, TurboGears already has something called “FastData”, but I’ve found it somewhat limiting. What I started with, then, is a set of requirements:



Table View



There should be a (HTML) table view of the (SQL) table which shows all rows of the (SQL) table.




  • The columns shown for each table must be customizable with a minimum of custom code.

  • When a column contains a foreign key, something “useful” must show up in that column (e.g. not the numeric “id” column of the foreign table)

  • The table should have the capability of displaying columns from foreign-key’d tables as native columns. (This is kind of like creating an SQL view containing columns from multiple tables.)

  • The table should have a link for editing each row, a link for marking a row for deletion, and a button for actually deleting the marked items. This is really a pet peeve of mine. GET-style links should not modify data in a well-designed web application. That can lead to all sorts of nastiness, especially when dealing with web spiders that follow all links. So I’ll keep all the data-modifying stuff in buttons.



From these requirements, I determined that I needed some kind of information, call it “CRUD metadata” or crudmeta for short, which contained the following information for each SQLObject model I want to view:




  • The fields to include in the table (note that these can be sub-fields, as in “address.street1”, etc.)

  • The “name” of each row in the table—this is for displaying the row as a “foreign key” in another table.



Edit View



There should be an “edit” view which allows for the following functionality:




  • Each SQLObject property refrenced in “crudmeta” should have a place on the form.

  • The fields in the form should make sense for their data type. This means:

    • TextFields for StringCol, IntCol, FloatCol, etc. (with data validation)

    • CalendarDate[Time]Picker for Date[Time]Col

    • Select fields for ForeignKey – populated with the “name” from crudmeta, of course!

    • Tables for MultiJoin fields with the similar functionality as the “Table View” above.



  • AJAX-y goodness to allow you to “drill down” in the object hierarchy without losing your context.



Note that some (most) of these fields have TurboGears 0.9 widgets-style names. Not a coincidince. I want to reuse as much as possible from existing TurboGears development so as to avoid “re-inventing the wheel.”



Status & next steps



As of today (2006–02-27), the basic functionality is there. It’s not pretty, it doesn’t handle errors well, it needs refactoring badly, and it mixes model and view a bit from MVC, but it exists. My current plan is to clean it up a bit this week and (hopefully) make it available by next weekend. To whet your appetite, here is an (abbreviated) model from my new application with the CRUD annotations present. (Currently, this is all the custom code you need in order to create the above-described interface.)



class Store(SQLObject):
class crudmeta:
fields=crud.fields(‘name’, ‘note’,
‘address.street1’, ‘address.street2’,
‘address.city’, ‘address.state’, ‘address.zip’,
(‘distributionCenter’, ‘Distribution Center’),
‘orders’)
key=‘name’

name=StringCol(default=uniqueValue(‘Store’, ‘name’, ‘store’),
alternateID=True)
note=StringCol(default=’’)
distributionCenter=ForeignKey(‘DistributionCenter’,default=None,
cascade=None)
address=ForeignKey(‘Address’,default=newAddressID)
orders=MultipleJoin(‘StoreOrder’)
deliveries=MultipleJoin(‘Delivery’)