<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6972144554804670310</id><updated>2011-04-21T12:16:58.651-07:00</updated><category term='error-reporting'/><category term='future'/><category term='iphone'/><category term='sqlobject'/><category term='adwords'/><category term='hello'/><category term='turbogears'/><category term='supercool'/><category term='sqlalchemy'/><category term='features'/><category term='importing'/><category term='logo'/><category term='makeover'/><title type='text'>The BandRadar Blog</title><subtitle type='html'>About the development of BandRadar, a band/events website for Portland, OR</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-318372080315746183</id><published>2009-01-04T18:29:00.001-08:00</published><updated>2009-01-05T14:12:39.344-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sqlobject'/><category scheme='http://www.blogger.com/atom/ns#' term='sqlalchemy'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>Moving to TG2, Part 1</title><content type='html'>&lt;div xmlns="http://www.w3.org/1999/xhtml"&gt;This is the start of a series on the work we're doing upgrading BandRadar's components. BandRadar's development started in 2006, and used the default components of TurboGears at the time: SQLObject for ORM, kid for templating, tgwidgets for widgeting, mochikit for js library, and CherryPy as appserver.&lt;br /&gt;&lt;br /&gt;Basically all of these components are now obsolete, or at least no longer recommended for new TG/TG2 projects. But they still work fine, so why change?&lt;br /&gt;&lt;br /&gt;Good question. I'm hoping that re-examining the choices we made in implementing BandRadar version 1 will bear dividends in terms of simplifying the code or making it easier to add new functionality, but honestly it's not clear at this point whether time spent on this work will have more value than spending it on extending the current code. It also depends on how hard moving to the new components is. Thankfully, TurboGears will let us transition to new components in stages, instead of all at once.&lt;br /&gt;&lt;br /&gt;The plan:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;SQLObject to SQLAlchemy (Elixir)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;kid to Genshi&lt;/li&gt;&lt;li&gt;tg.widgets to ToscaWidgets&lt;/li&gt;&lt;li&gt;CherryPy to Pylons/mod_wsgi&lt;/li&gt;&lt;/ol&gt;Currently working on #1. Converting classes derived from class SQLObject to class Entity and defining the relationships has been relatively painless. All our table names are not what Elixir is expecting, but it's easy to tell it what they should be. The first little bit of trouble is that SQLObject did not create db constraints for required fields, it keeps track of these itself and throws an error if a required field is omitted when creating an object. SA appears to rely on the database for this (which makes sense) but our existing db schema (originally created by SO) will need to be updated by hand for the new not-NULL constraints. We've also added a fair number of additional methods to the objects on model.py, and these will need to be reimplemented in SA-ese. Finally, our SO model includes a feature where all modifications to some tables are logged to a separate table (so we could recover from vandalism if necessary)... this will need to be reimplemented in SA, or we need to jump to versioned objects. I'd prefer the former, frankly, but I don't know if SA has a feature equivalent to sqlobject.events, or if I could just override __setattr__() in my model objects.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-318372080315746183?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/318372080315746183/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=318372080315746183' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/318372080315746183'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/318372080315746183'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2009/01/moving-to-tg2-part-1.html' title='Moving to TG2, Part 1'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-427842358572064024</id><published>2008-02-19T14:52:00.000-08:00</published><updated>2008-02-19T14:54:48.031-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='supercool'/><category scheme='http://www.blogger.com/atom/ns#' term='logo'/><title type='text'>New Logo!</title><content type='html'>As you may have noticed, we have a new logo. This logo was professionally designed for us by Andrew Harrington, and I think he did a great job. Of course we will be continuing to refine the site's look, but I think the logo is a great starting point.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-427842358572064024?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/427842358572064024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=427842358572064024' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/427842358572064024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/427842358572064024'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2008/02/new-logo.html' title='New Logo!'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-6668600953770535167</id><published>2008-02-18T10:23:00.000-08:00</published><updated>2008-02-18T10:37:01.415-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adwords'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>Watch out for 500 errors due to gclid</title><content type='html'>One kind of 500 seeing I've been seeing is this:&lt;br /&gt;&lt;pre wrap=""&gt;GET /&lt;br /&gt;(traceback)&lt;br /&gt;TypeError: index() got an unexpected keyword argument 'gclid'&lt;br /&gt;&lt;/pre&gt;I shrugged these off (stupid bots) but no these are VERY BAD.&lt;br /&gt;&lt;br /&gt;After a little Googling, I discovered that Google AdWords adds this parameter to requests to track when people click through your ads. (We do a small amount of ads on Google.) Well every one of these that we've been paying for (!) have been getting 500 errors instead of the main page because of this added parameter! I have tg.strict_parameters set.&lt;br /&gt;&lt;br /&gt;If you run a site and are using AdWords, make sure you aren't getting these errors. I would never have known if I hadn't turned on error reporting (see &lt;a href="http://bandradar.blogspot.com/2008/02/error-reporting-is-really-nice-to-have.html"&gt;here&lt;/a&gt;).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-6668600953770535167?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/6668600953770535167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=6668600953770535167' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/6668600953770535167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/6668600953770535167'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2008/02/watch-out-for-500-errors-due-to-gclid.html' title='Watch out for 500 errors due to gclid'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-4365958357896904095</id><published>2008-02-04T12:12:00.000-08:00</published><updated>2008-02-04T12:54:03.548-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='features'/><category scheme='http://www.blogger.com/atom/ns#' term='future'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><title type='text'>More stuff coming soon</title><content type='html'>Although BandRadar is a labor of love more than a business (currently anyways) we have still been adding features and users at a slow but steady pace over the past few months. For example, we keep adding more artist info, as we can get it from online APIs such as last.fm and Musicbrainz, as well as album links to Cdbaby and Amazon. We also are currently on our &lt;span style="font-style: italic;"&gt;third &lt;/span&gt;site design, with more improvements in that area on the way.&lt;br /&gt;&lt;br /&gt;We also recently acquired an iPhone, and are seeing great possibilities for making the site more useful from portable wireless devices. I think one common scenario is:&lt;br /&gt;&lt;br /&gt;(out at a bar)&lt;br /&gt;Guy 1:  Let's go check out a band!&lt;br /&gt;Guy 2: Sure! Do you know who's playing around here tonight?&lt;br /&gt;Guy 1: Uhh no... does anyone have a paper?&lt;br /&gt;&lt;br /&gt;Of course if you have a web-enabled mobile device you could check BandRadar, but right now we're giving you ALL the events tonight -- too much to wade through on a tiny screen. What you really want to know is what's nearby, and a little about each one to know if it's something you'd like to check out.&lt;br /&gt;&lt;br /&gt;This isn't hard to do. we could add a m.bandradar.com with a simplified webpage, enter your location and it finds the closest events. But what would be better would be to use the built-in GPS or cell-location features (like the iPhone has) so people could skip the "type in my location" step. That's the most painful part, really. It would go from taking 30 seconds (while your friends are waiting for you to quit futzing with your iPhone) to maybe 3 seconds. That difference boosts its usefulness tremendously, and it will be used much more.&lt;br /&gt;&lt;br /&gt;Unfortunately, all these devices may know where they are, but they may not have a way to tell us this information. On the iPhone, its Maps app supports location, but it's written by Apple of course. Once the iPhone SDK is released it may be possible to write our own app with location info, but that's a big unknown. That still doesn't help people who own all the other location-aware devices out there now, or coming soon.&lt;br /&gt;&lt;br /&gt;But we're thinking about these things. This is a killer feature that we personally would find very useful, and really want to make it happen if we can pull it off.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-4365958357896904095?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/4365958357896904095/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=4365958357896904095' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/4365958357896904095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/4365958357896904095'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2008/02/more-stuff-coming-soon.html' title='More stuff coming soon'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-6193772053087535085</id><published>2008-02-04T11:51:00.000-08:00</published><updated>2008-02-04T16:07:04.108-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='error-reporting'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>Error reporting is really nice to have</title><content type='html'>One thing that has really helped me feel better about the site lately is adding error reporting. That is, every time users get an error, I get an email. It's documented here: &lt;a href="http://docs.turbogears.org/1.0/ErrorReporting"&gt;ErrorReporting&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I think most people want whole-site error reporting (documented as Method 3). The code listed worked great, but something irks me about cutting and pasting code out of the docs in order to add that feature. I felt compelled to review it line-by-line, which I don't think I would have felt the need to do otherwise.&lt;br /&gt;&lt;br /&gt;Could this be made a standard part of TG, possibly?&lt;br /&gt;&lt;br /&gt;EDITED: BTW spiders are really good as an automated testing system and finding bad links, as one finds after enabling error reporting.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-6193772053087535085?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/6193772053087535085/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=6193772053087535085' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/6193772053087535085'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/6193772053087535085'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2008/02/error-reporting-is-really-nice-to-have.html' title='Error reporting is really nice to have'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-5722267410279699750</id><published>2007-04-23T20:56:00.000-07:00</published><updated>2007-04-23T21:01:31.883-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='makeover'/><title type='text'>Site makeover</title><content type='html'>Just went live with the new site makeover! W00t! No more harsh green, we went for a more mellow ochre (?) palette, and a blocky, vintage feel inspired from old concert posters.&lt;br /&gt;&lt;br /&gt;We still have some kinks to work out when it comes to IE -- please bear with us, or even better, use Firefox.&lt;br /&gt;&lt;br /&gt;In other news, I was out at a &lt;a href="http://karmedymusic.com"&gt;Karmedy&lt;/a&gt; gig at the Red Room on Thursday, the place lost power! Never had that happen at a gig before. Totally missed Karmedy (sup Josh!) and only caught half of The Unnamed's set (who were frickin amazing, btw)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-5722267410279699750?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/5722267410279699750/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=5722267410279699750' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/5722267410279699750'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/5722267410279699750'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/site-makeover.html' title='Site makeover'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-7447420214043666282</id><published>2007-04-20T14:54:00.000-07:00</published><updated>2007-04-20T15:14:48.486-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='importing'/><category scheme='http://www.blogger.com/atom/ns#' term='sqlobject'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>A persistent dict class</title><content type='html'>One big part of the website is importing events, with artist names associated with it. However, sometimes a band is referred to in slightly a different way -- not enough to be significant to a person, but enough to fool a computer into thinking it's a new band. The way I initially fixed this was a artist_name_fixup dict in the importing module. The dict contained a mapping between the incorrect name and the correct one. So for example, "Static X" mapped to "Static-X" (the latter is the actual band name, according to their website.)&lt;br /&gt;&lt;br /&gt;But this meant that every time a new fixup was needed, the source file containing the dict needed to be changed. OK, so put it in the database. Well, we check this dict a LOT, so it would be nice to not hit the DB every time.&lt;br /&gt;&lt;br /&gt;The persistent dict solves this by saving added entries in the database, but also keeping the dict in-memory. First, we define the fixup DB table in our model:&lt;pre&gt;class ArtistNameFixup(SQLObject):&lt;br /&gt;    name = UnicodeCol(alternateID=True)&lt;br /&gt;    value = UnicodeCol()&lt;/pre&gt;Then, the new class (in our util.py module):&lt;pre&gt;class PersistentDict(dict):&lt;br /&gt;    def __init__(self, model):&lt;br /&gt;        super(PersistentDict, self).__init__()&lt;br /&gt;        self.model = model&lt;br /&gt;        for row in model.select():&lt;br /&gt;            super(PersistentDict, self).__setitem__(row.name, row.value)&lt;br /&gt;&lt;br /&gt;    def __setitem__(self, name, value):&lt;br /&gt;        try:&lt;br /&gt;            r = self.model.byName(name)&lt;br /&gt;            r.value = value&lt;br /&gt;        except SQLObjectNotFound:&lt;br /&gt;            r = self.model(name=name, value=value)&lt;br /&gt;        super(PersistentDict, self).__setitem__(name, value)&lt;br /&gt;&lt;br /&gt;    def __delitem__(self, name):&lt;br /&gt;        self.model.byName(name).destroySelf()&lt;br /&gt;        super(PersistentDict, self).__delitem__(name)&lt;br /&gt;&lt;br /&gt;    def update(self, other_dict):&lt;br /&gt;        for name, value in other_dict.iteritems():&lt;br /&gt;            try:&lt;br /&gt;                r = self.model.byName(name)&lt;br /&gt;                r.value = value&lt;br /&gt;            except SQLObjectNotFound:&lt;br /&gt;                r = self.model(name=name, value=value)&lt;br /&gt;        super(PersistentDict, self).update(other_dict)&lt;/pre&gt;Finally in the import module, we add:&lt;pre&gt;import util&lt;br /&gt;artist_fixup_dict = util.PersistentDict(ArtistNameFixup)&lt;/pre&gt;When we start the TG application, the artist_fixup_dict will be populated from the database. Subsequent additions and deletions will be reflected both in the in-memory version, as well as in the db table, so if the app is reloaded, it won't lose anything.&lt;br /&gt;&lt;br /&gt;Finally, note that the PersistentDict code is making assumptions about the SO model it is initialized with -- that the table has "name" and "value" fields. With a little more code, these could be made configurable, but I don't need that flexibility, so I haven't added it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-7447420214043666282?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/7447420214043666282/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=7447420214043666282' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/7447420214043666282'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/7447420214043666282'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/persistent-dict-class.html' title='A persistent dict class'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-2941290296220320353</id><published>2007-04-15T20:24:00.000-07:00</published><updated>2007-04-15T22:48:26.065-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sqlobject'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>Logging all table changes to another table</title><content type='html'>Since the site is user-editable, it needs some way to log all changes to certain tables, so they can be undone, if hit by vandals adding bad data. What I came up with this: model classes that need this (not all do) inherit from Journalled (as well as SQLObject, of course). Journalled overrides the __setattr__ and set() methods, and creates entries in the UpdateLog table for each change.&lt;br /&gt;&lt;pre&gt;class UpdateLog(SQLObject):&lt;br /&gt;    created = DateTimeCol(default=datetime.now)&lt;br /&gt;    changed_by = IntCol()&lt;br /&gt;    table_name = UnicodeCol(length=12)&lt;br /&gt;    table_id = IntCol()&lt;br /&gt;    attrib_name = UnicodeCol(length=20)&lt;br /&gt;    attrib_old_value = UnicodeCol()&lt;br /&gt;    attrib_new_value = UnicodeCol()&lt;br /&gt;&lt;br /&gt;    def _get_attrib_old_value(self):&lt;br /&gt;        return pickle.loads(str(self._SO_get_attrib_old_value()))&lt;br /&gt;    def _set_attrib_old_value(self, value):&lt;br /&gt;        self._SO_set_attrib_old_value(pickle.dumps(value))&lt;br /&gt;    def _get_attrib_new_value(self):&lt;br /&gt;        return pickle.loads(str(self._SO_get_attrib_new_value()))&lt;br /&gt;    def _set_attrib_new_value(self, value):&lt;br /&gt;        self._SO_set_attrib_new_value(pickle.dumps(value))&lt;/pre&gt;The old and new values are stored as actual Python objects. IDs are stored as just Ints, no relationships, since entries here can come from more than one table.&lt;pre&gt;# Classes inheriting from this will have changes stored in the UpdateLog table&lt;br /&gt;class Journalled(object):&lt;br /&gt;&lt;br /&gt;    def __setattr__(self, name, value):&lt;br /&gt;        if name in self.sqlmeta.columns.keys():&lt;br /&gt;            self._record_update({name:value})&lt;br /&gt;        super(Journalled, self).__setattr__(name, value)&lt;br /&gt;&lt;br /&gt;    def set(self, **kw):&lt;br /&gt;        self._record_update(kw)&lt;br /&gt;        super(Journalled, self).set(**kw)&lt;br /&gt;&lt;br /&gt;    def _record_update(self, updates):&lt;br /&gt;        for name, value in updates.iteritems():&lt;br /&gt;            old_value = getattr(self, name, None)&lt;br /&gt;            if old_value != value:&lt;br /&gt;                try:&lt;br /&gt;                    current_user = identity.current.user.id&lt;br /&gt;                except:&lt;br /&gt;                    current_user = None&lt;br /&gt;                u = UpdateLog(&lt;br /&gt;                    changed_by=current_user,&lt;br /&gt;                    table_name=self.sqlmeta.table,&lt;br /&gt;                    table_id=self.id,&lt;br /&gt;                    attrib_name=name,&lt;br /&gt;                    attrib_old_value=old_value,&lt;br /&gt;                    attrib_new_value=value&lt;br /&gt;                    )&lt;/pre&gt;Honestly, with this in place, I haven't actually written the code to roll back any vandalism. The data is all in the UpdateLog, so I can put it off until the website actually gets hit ;-)&lt;br /&gt;&lt;br /&gt;SQLObject svn trunk also appears to have recently added table versioning support. Sounds good, but I haven't tried it yet.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-2941290296220320353?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/2941290296220320353/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=2941290296220320353' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/2941290296220320353'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/2941290296220320353'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/logging-all-table-changes-to-another.html' title='Logging all table changes to another table'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-571917816478934765</id><published>2007-04-09T10:28:00.000-07:00</published><updated>2007-04-09T10:39:32.977-07:00</updated><title type='text'>Our data's getting better, but still not perfect</title><content type='html'>We have worked hard over the past few months to substantially improve the amount and correctness of event data available through BandRadar. This is the kind of thing that doesn't really show up as a new feature, but is very important to the site being valuable to users.&lt;br /&gt;&lt;br /&gt;Although we are pulling data from multiple sources automatically, a human is still involved in reviewing each item for correctness. Lately, the further-out events have been more complete on the site. However, if you see incorrect or missing entries (maybe for your band's next gig?) YOU can add them, or correct them! If there are other problems with the site, please leave a comment and we'll take a look right away.&lt;br /&gt;&lt;br /&gt;We have some new user-visible features coming soon but I'll blog about them after they're actually up.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-571917816478934765?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/571917816478934765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=571917816478934765' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/571917816478934765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/571917816478934765'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/our-datas-getting-better-but-still-not.html' title='Our data&apos;s getting better, but still not perfect'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-1109838930057556312</id><published>2007-04-08T23:03:00.000-07:00</published><updated>2007-04-09T00:23:34.986-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sqlobject'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>SelectResults are great</title><content type='html'>One change I made as the website has evolved is to switch from using SQLObject RelatedJoin and MultipleJoin to their cousins, SQLRelatedJoin and SQLMultipleJoin. After having used them, I hazard to say that I think they should be the default. The non-SQL versions return lists of objects; the SQL versions return a SelectResult.&lt;br /&gt;&lt;br /&gt;A SelectResult (which of course is also returned from Class.select()) doesn't actually contain the results of the select, but it knows how to get it. It waits to hit the database until actually dereferenced. If you have a SelectResult instead of an actual list of objects, it's not too late to further filter or modify the result!&lt;br /&gt;&lt;br /&gt;I had several places in my code where I was iterating through a RelatedJoin, performing a filter in Python. Switching these to SQLRelatedJoins allowed me to ask the database to perform the filter instead, thus reducing the amount of code, as well as helping performance.&lt;br /&gt;&lt;br /&gt;The one thing to watch out for: to determine if the SelectResults actually has any rows, you need to call class.results.count() instead of len(class.results), since class.results isn't a list anymore.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-1109838930057556312?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/1109838930057556312/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=1109838930057556312' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/1109838930057556312'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/1109838930057556312'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/selectresults-are-great.html' title='SelectResults are great'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-2623530585412860553</id><published>2007-04-01T20:35:00.000-07:00</published><updated>2007-04-01T20:47:53.259-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>how to use the TurboGears scheduler</title><content type='html'>This is a quick overview of how to use the TG scheduler to, for example, do nightly db cleanups, or similar.&lt;br /&gt;&lt;br /&gt;First, put this in your controllers.py:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from turbogears import scheduler&lt;br /&gt;import batch&lt;br /&gt;&lt;br /&gt;def startup():&lt;br /&gt;    scheduler.add_weekday_task(batch.task, range(1,8), (3,0))&lt;br /&gt;&lt;br /&gt;turbogears.startup.call_on_startup.append(startup)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can see I've put the code I'm going to run in another file, called batch.py, in a function called task(). You can also take a look at scheduler.py in the TG code for more details on the scheduler API, but above I'm basically asking to be called every day at 3 am.&lt;br /&gt;&lt;br /&gt;Then, in batch.py:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def task():&lt;br /&gt;    hub.threadingLocal = threading_local()&lt;br /&gt;    hub.begin()&lt;br /&gt;    # do stuff, like clean up your identity database. You can use your ORM objects here.&lt;br /&gt;    hub.commit()&lt;br /&gt;    hub.end()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hope this helps!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-2623530585412860553?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/2623530585412860553/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=2623530585412860553' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/2623530585412860553'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/2623530585412860553'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/how-to-use-turbogears-scheduler.html' title='how to use the TurboGears scheduler'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-8663350448894117892</id><published>2007-04-01T19:48:00.000-07:00</published><updated>2007-04-01T20:06:57.800-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sqlobject'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>undocumented new TG features</title><content type='html'>BandRadar is implemented in TurboGears, a Python-based web framework. The interesting thing about TG is that it is like the Borg of web frameworks -- it incorporates several different projects for different areas of functionality, and acts to tie them all together nicely.&lt;br /&gt;&lt;br /&gt;While there is the larger debate about which components to use, now that TG offers choices for templating and ORM, there are also smaller issues, because new versions of the old components are still coming out!&lt;br /&gt;&lt;br /&gt;For example, when I first started implementing BandRadar in TurboGears 0.8, SQLObject would not clean up entries in an intermediate table used for many-to-many relationships. Therefore, I had to write destroySelf() methods for all classes with these that cleaned this up manually.&lt;br /&gt;&lt;br /&gt;This isn't necessary anymore, SQLObject 0.8 deletes them for you. It also allows you to define cascade behavior for ForeignKeys. This is very nice, and I am glad to be rid of that code. But why did I have to find out about this new feature by happenstance? The SQLObject developers really need to have a changelog and release notes evident on their web page. These new features are documented, but I don't think many people make it a habit to skim docs for new tidbits -- this is why release notes are important.&lt;br /&gt;&lt;br /&gt;However, ranting aside, there is now one less reason why I should take the time to switch to SQLAlchemy. ;-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-8663350448894117892?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/8663350448894117892/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=8663350448894117892' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/8663350448894117892'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/8663350448894117892'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/04/undocumented-new-tg-features.html' title='undocumented new TG features'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6972144554804670310.post-7190982194324551495</id><published>2007-03-07T16:19:00.000-08:00</published><updated>2007-03-07T16:22:23.714-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hello'/><category scheme='http://www.blogger.com/atom/ns#' term='turbogears'/><title type='text'>Testing...</title><content type='html'>Hi this is a new blog, where I will talk about my website BandRadar, and its implementation with TurboGears. Unfortunately, the new blogger API isn't yet supported yet in Drivel, Doh!&lt;br /&gt;&lt;br /&gt;Lately I've been adding AJAXish features to the website, in addition to the ability to track venues (you will receive a weekly email with upcoming events for each venue) as well as integrating Google Maps onto the site.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6972144554804670310-7190982194324551495?l=bandradar.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bandradar.blogspot.com/feeds/7190982194324551495/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6972144554804670310&amp;postID=7190982194324551495' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/7190982194324551495'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6972144554804670310/posts/default/7190982194324551495'/><link rel='alternate' type='text/html' href='http://bandradar.blogspot.com/2007/03/testing.html' title='Testing...'/><author><name>agrover</name><uri>http://www.blogger.com/profile/00851179570236750443</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
