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’)




3 comments:

  1. Anonymous12:52 PM

    Hi, Rick! This looks really good. I've always thought Django was a little on the unorthodox side because they mix some stuff from the modelish area in the viewish area (can't say "model" and "view" because they aren't really an MVC framework - close, but not exactly). What you are doing here looks pretty similar in concept to what they are doing, only Django does it in the data-layer libraries rather than where you define your specific model. Good stuff!

    ReplyDelete
  2. Hi, Jeremy! Thanks for the comment! I have to say, my Django comparisons were based pretty much entirely on the 5 minute explanation you gave at the PyAtl meeting in January, so I don't know how accurate I am in calling this "Django-like."

    I am actually considering moving the code *out* of the model file and into some separate file where the "crudmeta" classes could just have a reference to which model they're "viewing." That (hopefully) will make the thing a little more MVCish without introducing any significant coding complexity. It will also let you define multiple CRUD pages for a single model as a byproduct of being more MVCish, which may be important in some applications.

    ReplyDelete
  3. Anonymous10:45 AM

    Rick loves Jeremy :)

    ReplyDelete