<?xml version="1.0" encoding="iso-8859-1"?>
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
    <title>brian&apos;s blog</title>
    <link>http://blog.case.edu/bmb12/</link>
    <description>I really should finish this template.</description>
    <language>en-us</language>
    <pubDate>Fri, 14 Dec 2007 02:05:36 EST</pubDate>
    <lastBuildDate>Fri, 14 Dec 2007 02:05:36 EST</lastBuildDate>
    <managingEditor>brian.beck@case.edu</managingEditor>
    <webMaster>brian.beck@case.edu</webMaster>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>Movable Type v3.121</generator>

    
    <item>
      <title>blog.brianbeck.com, RTA Schedule 2.0</title>
      <link>http://blog.case.edu/bmb12/2007/12/blogbrianbeckcom_rta_schedule_20</link>
      <description>Dear readers, Enclosed please find a link to my new blog. You will be delighted to discover that I have...</description>
      <guid>http://blog.case.edu/bmb12/2007/12/blogbrianbeckcom_rta_schedule_20</guid>
      
      <pubDate>Fri, 14 Dec 2007 02:05:36 EST</pubDate>
      <content:encoded><![CDATA[<p>Dear readers,</p>

<p>Enclosed please find a link to my new blog. You will be delighted to discover that I have stashed it on tumblr instead of creating more projects for myself. We'll see how that goes. There you will find <a href="http://blog.brianbeck.com/post/21556614">the announcement of RTA Schedule version 2.0</a>.</p>

<p><a href="http://blog.brianbeck.com" style="font-size: 3em;">blog.brianbeck.com</a></p>

<p><br />
New feed URL: <a href="http://blog.brianbeck.com/rss">http://blog.brianbeck.com/rss</a></p>]]></content:encoded>
    </item>

    <item>
      <title>September Projects</title>
      <link>http://blog.case.edu/bmb12/2007/09/september_projects</link>
      <description>I&apos;m in the situation Ian Bicking was in not long ago&amp;mdash;I&apos;m really tired of this blog design and software and...</description>
      <guid>http://blog.case.edu/bmb12/2007/09/september_projects</guid>
      
        <category domain="http://blog.case.edu/bmb12/pagoda/index">Pagoda</category>
      
        <category domain="http://blog.case.edu/bmb12/projects/index">Projects</category>
      
        <category domain="http://blog.case.edu/bmb12/python/index">Python</category>
      
        <category domain="http://blog.case.edu/bmb12/geopy/index">geopy</category>
      
      <pubDate>Tue, 25 Sep 2007 22:45:29 EST</pubDate>
      <content:encoded><![CDATA[<p>I'm in the situation <a href="http://blog.ianbicking.org">Ian Bicking</a> was in not long ago&mdash;I'm really tired of this blog design and software and it's making me not want to post any of the entries I have pending. This blog will soon redirect to something better.</p>

<p><a href="http://pagodacms.org/">Pagoda</a> should have a Developer Preview in October. Check out <a href="http://exogen.case.edu/clepy_pagoda/">my presentation</a> from the September meeting of the <a href="http://clepy.org/">Cleveland Python interest group</a>.</p>

<p>Remember how Ian and I spent months thinking up hundreds of names for our company? We are now incorporated as <a href="http://unstoppablerocket.com">Unstoppable Rocket</a>&mdash;one of the first names that was suggested.</p>

<p>If you've been following the <a href="http://groups.google.com/group/geopy">geopy list</a>, you've heard about the new release coming out. It should make things much more flexible and extendable, and fix all the issues from the past year or so. geopy 0.99 will be out this week.</p>

<p>The geopy update is also getting me back into the <a href="http://exogen.case.edu/crime/recent/">campus crime map</a> and my Case geocoder service, which is going to be really smart. Updates there soon.</p>

<p>I started a new project called Revisionist, which is like <a href="http://blog.case.edu/bmb12/2007/06/pagoda_revisions">Pagoda's revision model</a> except generalized and using SQLAlchemy 0.4. I'm hoping other people will be interested in using and improving such a project. With the right helpers it should make revisioning complex models really easy.</p>

<p>If anyone has any neat suggestions for what Gary or I should talk about at the October Clepy meeting, let me know.</p>]]></content:encoded>
    </item>

    <item>
      <title>Hacking in Project Club</title>
      <link>http://blog.case.edu/bmb12/2007/07/hacking_in_project_club</link>
      <description>Did I mention I got a MacBook Pro? (Working on a Mac app I&apos;ll be posting about in a couple...</description>
      <guid>http://blog.case.edu/bmb12/2007/07/hacking_in_project_club</guid>
      
        <category domain="http://blog.case.edu/bmb12/pictures/index">Pictures</category>
      
      <pubDate>Thu, 19 Jul 2007 11:52:33 EST</pubDate>
      <content:encoded><![CDATA[<p>Did I mention I got a MacBook Pro?</p>

<p><a href="http://blog.case.edu/bmb12/2007/07/19/ProjectClubHacking.jpg"><img alt="ProjectClubHacking.jpg" src="http://blog.case.edu/bmb12/2007/07/19/ProjectClubHacking-thumb.jpg" width="360" height="270" /></a></p>

<p>(Working on a Mac app I'll be posting about in a couple weeks.)</p>]]></content:encoded>
    </item>

    <item>
      <title>Content types in Pagoda, Part 1: The Model</title>
      <link>http://blog.case.edu/bmb12/2007/07/content_types_in_pagoda_part_1_the_model</link>
      <description>Any content management system will inevitably have to think about having different content types. Common content types include pages, attachments,...</description>
      <guid>http://blog.case.edu/bmb12/2007/07/content_types_in_pagoda_part_1_the_model</guid>
      
        <category domain="http://blog.case.edu/bmb12/pagoda/index">Pagoda</category>
      
        <category domain="http://blog.case.edu/bmb12/python/index">Python</category>
      
        <category domain="http://blog.case.edu/bmb12/sqlalchemy/index">SQLAlchemy</category>
      
      <pubDate>Wed, 04 Jul 2007 15:31:00 EST</pubDate>
      <content:encoded><![CDATA[<p>Any <a href="http://en.wikipedia.org/wiki/Content_management_system">content management system</a> will inevitably have to think about having different <strong>content types</strong>. Common content types include pages, attachments, calendars, events, and blog articles. Why make the distinction between different things that all appear as "pages" to the user? Because, of course, different content types must support different features and respond to different actions. For example, an event content type must have a date in order to show up on a calendar, and a calendar content type might support an iCalendar feed of its events.</p>

<p>Likewise, content types will all share similar features and actions. They all have a URL and a title. And if we take the common CMS approach of making a site as a hierarchy of objects, they all have a parent object and child objects. While sites might not be inherently hierarchical (URLs are just identifiers!), it's quite natural to create them this way&mdash;for example, if we move a page, we'd expect its entire tree of child pages to move with it.</p>

<p>One of the first things a web developer does when starting a project is to model its content types. Read any MVC (or, ahem, <a href="http://www2.jeffcroft.com/blog/2007/jan/11/django-and-mtv/">MTV</a>) web framework tutorial and there will be a Wiki model, or a Blog model, or a TodoList model&mdash;all content types. In this article I'll be talking about what it currently looks like to model a content type in <a href="http://pagodacms.org/">Pagoda</a>. Since Pagoda is based on <a href="http://www.pagodacms.org">TurboGears</a>, our goal is to make building your app alongside Pagoda no different than building your app with <a href="http://www.turbogears.org">TurboGears</a>, and so far I think we've done a pretty good job. (And by the way, since we're using SQLAlchemy, this part of Pagoda is <a href="http://www.blueskyonmars.com/2007/06/27/turbogears-2-a-reinvention-and-back-to-its-roots/">TG2 future-proof</a>.)</p>

<p>So, if it's supposed to be the same as just using TurboGears, why do I have to show you anything? The answer is that while you don't have to design your model with Pagoda in mind (existing apps will coexist just fine), doing so will make your model easily <a href="http://blog.case.edu/bmb12/2007/06/pagoda_revisions">localizable and revisionable</a>! That's a pretty big benefit in the world of content management. We'll be able to restore old content records from any point in their history, and make changes to locale-independent fields for all translations at once.</p>

<p>So, on to the code. I'll be modeling a simple Event content type. First, here's how you might do it with some plain old TurboGears and SQLAlchemy.</p>

<pre><code>
from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper
from turbogears.database import metadata, session
from datetime import datetime

event_table = Table('event', metadata,
    Column('event_id', Integer, primary_key=True),
    Column('start_date', DateTime, nullable=False, default=datetime.now),
    Column('end_date', DateTime, nullable=True),
    Column('title', Unicode(200), nullable=False),
    Column('url_slug', String(75), nullable=False),
    Column('description', TEXT, nullable=False, default=""),
    Column('show_in_calendar', Boolean, nullable=False, default=True)
)

class Event(object):
    def move_to_date(self, new_date):
        self.start_date = new_date
        if self.end_date:
           time_delta = new_date - self.start_date
           self.end_date += time_delta

assign_mapper(session.context, Event, event_table)
</code></pre>

<p>So, a pretty standard model with minimal event features. (One thing might not be obvious&mdash;the <code>url_slug</code> field is the short <a href="http://en.wikipedia.org/wiki/Latin-1">Latin-1</a> name of the event we'll show in the URL). Using the mapped Event class to use the model looks like this...</p>

<pre><code>
# Make an event.
bday_party = Event(start_date=datetime(2007, 10, 30, 19, 30),
    title="Brian turns twenty-three!", url_slug="brian_turns_23",
    description="The party will take place in my underwater hideout.",
    show_in_calendar=True)

# End at midnight!
bday_party.end_date = datetime(2007, 10, 31)

# Write it!
session.flush()
</code></pre>

<p>Now, how would it change with support for translations, revisions, and having parent and child objects? The first step is to split the table up into locale-dependent and locale-independent tables. If you read about our <a href="http://blog.case.edu/bmb12/2007/06/pagoda_revisions">localizable revision</a> model you'll see that this is how we support independently revisioned translations while avoiding data duplication. Here's what the two tables look like that will replace <code>event_table</code>:</p>

<pre><code>
event_generic_table = Table('event_generic', metadata,
    Column('event_id', Integer, primary_key=True),
    Column('start_date', DateTime, nullable=False, default=datetime.now),
    Column('end_date', DateTime, nullable=True),
    Column('url_slug', String(75), nullable=False),
    Column('show_in_calendar', Boolean, nullable=False, default=True)
)

event_localized_table = Table('event_localized', metadata,
    Column('event_id', Integer, primary_key=True),
    Column('title', Unicode(200), nullable=False),
    Column('description', TEXT, nullable=False, default="")
)
</code></pre>

<p>Since we want to support translations for the fields in <code>event_localized_table</code>, let's also add a <code>locale</code> field in order to tell the translations apart. <code>locale</code> will be a short identifier like "en-US", "fr", or "jp".</p>

<pre><code>
event_localized_table = Table('event_localized', metadata,
    Column('event_id', Integer, primary_key=True),
    Column('title', Unicode(200), nullable=False),
    Column('description', TEXT, nullable=False, default=""),
    <strong>Column('locale', String(25), nullable=False)</strong>
)
</code></pre>

<p>The next step is to point both tables at Pagoda's revision table in order to support revisions. Since each event record points to a unique revision record, our primary key is now redundant, and can be changed to the revision's ID:</p>

<pre><code>
<strong>from pagoda.models import Revision</strong>

event_generic_table = Table('event_generic', metadata,
    <strong>Column('revision_id', None, primary_key=True,
        ForeignKey(Revision.c.revision_id)),</strong>
    Column('start_date', DateTime, nullable=False, default=datetime.now),
    Column('end_date', DateTime, nullable=True),
    Column('url_slug', String(75), nullable=False),
    Column('show_in_calendar', Boolean, nullable=False, default=True)
)

event_localized_table = Table('event_localized', metadata,
    <strong>Column('revision_id', None, primary_key=True,
        ForeignKey(Revision.c.revision_id)),</strong>
    Column('title', Unicode(200), nullable=False),
    Column('description', TEXT, nullable=False, default=""),
    Column('locale', String(25), nullable=False)
)
</code></pre>

<p>A column type of <code>None</code> here will cause SQLAlchemy to use the column type of the foreign key&mdash;almost always what you want. There's one more change to make. Since Pagoda helps manage your site's content hierarchy, it already has a table to hold the URL of every object on the site. So we can get rid of the <code>url_slug</code> field&mdash;Pagoda will include its own when we tell it about this content type. Our final tables:</p>

<pre><code>
from pagoda.models import Revision

event_generic_table = Table('event_generic', metadata,
    Column('revision_id', None, primary_key=True,
        ForeignKey(Revision.c.revision_id)),
    Column('start_date', DateTime, nullable=False, default=datetime.now),
    Column('end_date', DateTime, nullable=True),
    Column('show_in_calendar', Boolean, nullable=False, default=True)
)

event_localized_table = Table('event_localized', metadata,
    Column('revision_id', None, primary_key=True,
        ForeignKey(Revision.c.revision_id)),
    Column('title', Unicode(200), nullable=False),
    Column('description', TEXT, nullable=False, default=""),
    Column('locale', String(25), nullable=False)
)
</code></pre>

<p>Just a few more small changes! Since we have two different tables, and are now adding some more tables (like Revision) into the mix, we need to join them somehow for SQLAlchemy to map against the resulting join. Pagoda has a function called <code>revisioned_table</code> that will perform the necessary joins. Just tell it about your two tables and give it an alias:</p>

<pre><code>
from pagoda.models import Revision<strong>, revisioned_table</strong>

...

<strong>event_table = revisioned_table('event', event_generic_table, event_localized_table)</strong>
</code></pre>

<p><code>event_table</code> is now a Selectable according to SQLAlchemy. Let's map against it! Pagoda uses a <a href="http://www.sqlalchemy.org/docs/adv_datamapping.html#advdatamapping_extending">mapper extension</a> to help with querying and modifying revisioned records. You can add <code>pagoda.models.RevisionableMapperExtension</code> to the mapper yourself, or you can use our helper called <code>revision_mapper</code> to do it. <code>revision_mapper</code> is a small wrapper around <code>assign_mapper</code> that makes sure the mapper extension is there, and gives the mapped class methods some more helpful docstrings.</p>

<pre><code>
from pagoda.models import Revision, revisioned_table<strong>, revision_mapper</strong>

...

<strong>revision_mapper(session.context, Event, event_table)</strong>
</code></pre>

<p>Last change! Since Event is now revisioned, it would be nice to have some helpful methods for dealing with revisions, like querying for the latest published revision or creating a new revision based on a previous revision. Pagoda has a base class for your mapped class that will give it a few such methods. Just subclass your mapped class from <code>Revision</code>:</p>

<pre><code>
class Event(<strong>Revision</strong>):
    def move_to_date(self, new_date):
        self.start_date = new_date
        if self.end_date:
           time_delta = new_date - self.start_date
           self.end_date += time_delta
</code></pre>

<p>And that's all it takes to support revisions. <code>Event</code> works just like before, except it now has some more methods and fields. A <code>url</code> column came from Pagoda's Node table, <code>content_id</code> and <code>content_type</code> came from Pagoda's Content table, and Revision's columns came along too. Note that no columns were added to either Event table&mdash;these additional fields came from joins. Using it looks much the same as before:</p>

<pre><code>
bday_party = Event(start_date=datetime(2007, 10, 30, 19, 30),
    title="Brian turns twenty-three!", <strong>url</strong>="brian_turns_23",
    description="The party will take place in my underwater hideout.",
    show_in_calendar=True<strong>, locale='en', content_type='event',
    revision_author="brian"</strong>)

revised_bday_party = bday_party.new_revision(title="Brian gets older")
revised_bday_party.publish()

session.flush()

# revised_bday_party is now "active" - the latest published revision

calendar_events = Event.select_active_by(show_in_calendar=True)

from datetime import datetime, timedelta
yesterday = datetime.today() - timedelta(days=1)
events_as_they_were_yesterday = Event.filter_snapshot(
    yesterday
).select_by(show_in_calendar=True)

</code></pre>

<p>Here's the final code. It's just a couple more lines than the original model at the beginning of this article:</p>

<pre><code>
from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper
from turbogears.database import metadata, session
from datetime import datetime
from pagoda.models import Revision, revisioned_table, revision_mapper

event_generic_table = Table('event_generic', metadata,
    Column('revision_id', None, primary_key=True,
        ForeignKey(Revision.c.revision_id)),
    Column('start_date', DateTime, nullable=False, default=datetime.now),
    Column('end_date', DateTime, nullable=True),
    Column('show_in_calendar', Boolean, nullable=False, default=True)
)

event_localized_table = Table('event_localized', metadata,
    Column('revision_id', None, primary_key=True,
        ForeignKey(Revision.c.revision_id)),
    Column('title', Unicode(200), nullable=False),
    Column('description', TEXT, nullable=False, default=""),
    Column('locale', String(25), nullable=False)
)

event_table = revisioned_table('event', event_generic_table, event_localized_table)

class Event(Revision):
    def move_to_date(self, new_date):
        self.start_date = new_date
        if self.end_date:
           time_delta = new_date - self.start_date
           self.end_date += time_delta

revision_mapper(session.context, Event, event_table)
</code></pre>

<p>So, hopefully those changes to the original Event weren't too jarring. Sure we could make many of those changes automatically, but we're trying to avoid magic in favor of small helpers, each extending the model The SQLAlchemy Way. If you think all this is too much work, let us know! We want this to be fun to hack on for everyone, not just us.</p>

<p>Next time I'll talk about content type controllers.</p>]]></content:encoded>
    </item>

    <item>
      <title>SQLAlchemy Bundle for TextMate</title>
      <link>http://blog.case.edu/bmb12/2007/06/sqlalchemy_bundle_for_textmate</link>
      <description>We&apos;ve been using a lot of SQLAlchemy here in Pagoda-land. Not long after I started using TextMate, I started making...</description>
      <guid>http://blog.case.edu/bmb12/2007/06/sqlalchemy_bundle_for_textmate</guid>
      
        <category domain="http://blog.case.edu/bmb12/python/index">Python</category>
      
      <pubDate>Sat, 30 Jun 2007 01:36:58 EST</pubDate>
      <content:encoded><![CDATA[<p>We've been using a lot of <a href="http://www.sqlalchemy.org">SQLAlchemy</a> here in <a href="http://www.pagodacms.org">Pagoda-land</a>. Not long after I started using <a href="http://www.macromates.com">TextMate</a>, I started making all kinds of shortcuts for common SQLAlchemy constructs. Pretty soon models were flying out of our fingertips left and right.</p>

<p>Anyway, now you can download my <a href="http://www.pagodacms.org/files/SQLAlchemy_Bundle.tar.gz">SQLAlchemy TextMate bundle</a>. Just extract that file and drag the resulting bundle onto TextMate to install it. There are currently 8 Snippets and 2 Templates, a few of which are demonstrated below.</p>

<p>Here's a quick little screencast where I make a few related tables using a Template and some Snippets. As you can see I've still got revisions on the brain. There's no talking, just some music. It's a minute and a half long. You have a minute, right?</p>

<p><a href="http://blog.case.edu/bmb12/2007/06/30/SQLAlchemy_Bundle.mov"><img alt="alchemy-thumbnail.png" src="http://blog.case.edu/bmb12/2007/06/30/alchemy-thumbnail.png" width="463" height="365" /><br />
</a></p>

<p>Ideas for additions and improvements are always welcome.</p>]]></content:encoded>
    </item>

    <item>
      <title>Polymorphic, multilingual, revisioned content!</title>
      <link>http://blog.case.edu/bmb12/2007/06/pagoda_revisions</link>
      <description>Let&apos;s talk about modeling revisioned, localized content in SQL! We&apos;re talking database engine agnostic here, people. This should work in...</description>
      <guid>http://blog.case.edu/bmb12/2007/06/pagoda_revisions</guid>
      
        <category domain="http://blog.case.edu/bmb12/pagoda/index">Pagoda</category>
      
      <pubDate>Wed, 27 Jun 2007 19:58:13 EST</pubDate>
      <content:encoded><![CDATA[<p>Let's talk about modeling revisioned, localized content in SQL! We're talking database engine agnostic here, people. This should work in SQLite, MySQL, PostgreSQL, and Bob's ValueSQL.</p>

<p>But first, if you're easily bored like me, here's a pretty visual aid I made for you describing the model we came up with. It's supposed to visualize how independently revisioned parts of an object can be combined into a single revision history. The rest of the post talks about how we ended up with this model. Click on it!</p>

<p><a href="http://exogen.case.edu/revisions.png"><img alt="revisions.png" src="http://exogen.case.edu/revisions-thumb.png" width="392" height="336" /></a></p>

<p>Content can mean any set of fields that applies to one type of object. For example, a page might have a title and text and some flags like whether or not to show up in the site's menu and search results.</p>

<p>Supporting revisions is useful for a couple of reasons. One is that it allows users to undo their actions in case something goes horribly wrong. Secondly, you might want a workflow with some approval process such that new changes can be in a pending state while older versions are still active.</p>

<p>A simple way to model this might look like the following. You can easily imagine what the SQL looks like.</p>

<dl>
<dt>revision</dt>
<dd>
<dl>
    <dt>revision_id</dt>
        <dd>A surrogate primary key. In an alternate schema it could be used as a composite key with
        <code>content_id</code>, in which case it could be interpreted as a revision number.
</dd>
    <dt>content_id</dt>
        <dd>Some number identifying this content that won't change across revisions.</dd>
    <dt>author</dt>
        <dd>The user who made this change.</dd>
    <dt>timestamp</dt>
        <dd>The time of the change.</dd>
    <dt>comment</dt>
        <dd>The author's description of the change.</dd>
    <dt>status</dt>
        <dd>The workflow status (could be anything, depending on your workflow
        needs).</dd>
</dl>
</dd>
<dt>page</dt>
<dd>
<dl>
    <dt>revision_id</dt>
        <dd>A surrogate primary key that points to the <code>revision</code> table.</dd>
    <dt>content_id</dt>
        <dd>A foreign key to the <code>revision</code> table.</dd>
    <dt>title</dt>
        <dd>The title text.</dd>
    <dt>text</dt>
        <dd>The main page content.</dd>
    <dt>show_in_menu</dt>
        <dd>A flag indicating whether or not to show up in the site's menu.</dd>
    <dt>show_in_search</dt>
        <dd>A flag indicating whether or not to show up in the site's search
        results.</dd>
</dl>
</dd>
</dl>

<p>Pretty easy, right? <code>revision_id</code> and <code>content_id</code> are foreign keys to the <code>revision</code> table, which will store information like the author, timestamp, comment, and workflow status for revisions of any type. <code>content_id</code> could also only exist in the <code>revision</code> table and be retrieved with a join, but I'm including it in the <code>page</code> table to make the examples easier.</p>

<p>This is simple enough if you don't need to support content translations. If you want a software solution that people will take seriously, however, localization shouldn't just be possible&mdash;it should be easy. And sadly, it's not the easiest thing to model correctly. A <a href="http://groups.drupal.org/node/1622">Drupal developer has a good summary of why this is so hard</a>. We need some way to have, for each <code>content_id</code>, multiple translations of the localizable fields&mdash;in this case, <code>title</code> and <code>text</code>.</p>

<p>A bad solution would be to add a column for every language-field pair:</p>

<dl>
<dt>page</dt>
<dd>
<dl>
    <dt>...</dt>
    <dt>en_title</dt>
    <dt>en_text</dt>
    <dt>jp_title</dt>
    <dt>jp_text</dt>
    <dt>es_title</dt>
    <dt>es_text</dt>
</dl>
</dd>
</dl>

<p>Like I said, that's obviously no good. Not only do we not want to add columns for every new translation, but we also don't want a new record with all of these columns when only one of the translations is updated. In other words, we want the translations to be independently revisioned, which better reflects how sites are normally translated. Another solution might be to have locale-independent title and text fields whose values are identifiers for strings in a translation table shared among all content types. Unfortunately this makes it harder to create and update page records, and also forces us to give up on any fields that
need a unique schema, such as a maximum content length.</p>

<p>A better solution would be to just add a locale field:</p>

<dl>
<dt>page</dt>
<dd>
<dl>
    <dt>...</dt>
    <dt>locale</dt>
<dd>
        The locale of the translated fields in this row. For example, "en_US".
</dd>
</dl>
</dd>
</dl>

<p>So we might have some entries that look like this:</p>

<table>
<thead>
<tr>
<th>revision_id</th>
<th>content_id</th>
<th>locale</th>
<th>title</th>
<th>text</th>
<th>show_in_menu</th>
<th>show_in_search</th>
</tr>
</thead>
<tbody>
<tr>
  <td>1</td>
  <td>1</td>
  <td>en_US</td>
  <td>Warning!</td>
  <td>The kitchen is on fire!</td>
  <td>true</td>
  <td>true</td>
</tr>
<tr>
  <td>2</td>
  <td>1</td>
  <td>es</td>
  <td>¡Aviso!</td>
  <td>¡La cocina se arde!</td>
  <td>true</td>
  <td>true</td>
</tr>
<tr>
  <td>3</td>
  <td>2</td>
  <td>en</td>
  <td>Bathroom status...</td>
  <td>Safe!</td>
  <td>false</td>
  <td>false</td>
</tr>
</tbody></table>

<p>There are two pages here. One has two translations in U.S. English and Spanish. The other has just an English translation. They all have a different <code>revision_id</code>&mdash;remember, this is just a link to another table that stores information about the change.</p>

<p>You might be able to see why this isn't ideal. What happens when we want a new revision that changes <code>show_in_menu</code> or <code>show_in_search</code>, two non-locale-specific fields? We'd have to insert a new record with the updates for every translation. That's a lot of work, and could be a lot of duplication!</p>

<p>To minimize duplication of data, we can split up the locale-dependent and locale-independent fields into two tables for each content type. Our tables would look like this:</p>

<dl>
<dt>page_generic</dt>
<dd>
<dl>
    <dt>revision_id</dt>
    <dt>content_id</dt>
    <dt>show_in_menu</dt>
    <dt>show_in_search</dt>
</dl>
</dd>
<dt>page_localized</dt>
<dd>
<dl>
    <dt>revision_id</dt>
    <dt>content_id</dt>
    <dt>locale</dt>
    <dt>title</dt>
    <dt>text</dt>
</dl>
</dd>
</dl>

<p>So, to get whole page revisions now we can just line up the <code>revision_id</code> from each table, and if an equivalent <code>revision_id</code> doesn't exist, just use the previous one that does exist from the table where it's missing. For instance, if the revision history looks like this:</p>

<table><tbody>
<tr><th>page_generic</th>
<td>1</td><td>2</td><td></td><td></td><td>5</td><td></td><td>7</td>
</tr>
<tr><th>page_localized</th><td>1</td><td></td><td>3</td><td>4</td><td>5</td><td>6</td><td></td>
</tr>
</tbody></table>

<p>The revisions would be the pairs (1, 1), (2, 1), (2, 3), (2, 4), (5, 5), (5, 6), (7, 6). But we need to take locale into account...</p>

<table><tbody>
<tr><th>page_generic</th>
<td>1</td><td>2</td><td></td><td></td><td>5</td><td></td><td>7</td>
</tr>
<tr><th>page_localized</th><td>1</td><td></td><td>3</td><td>4</td><td>5</td><td>6</td><td></td>
</tr>
<tr><th></th><td>en</td><td></td><td>en</td><td>jp</td><td>jp</td><td>en</td><td></td>
</tr>
</tbody></table>

<p>There are more pairs now, since each <code>page_generic</code> record needs to be paired with not just the previous <code>page_localized</code> record, but the previous <code>page_localized</code> record in each locale. The pairs are (1, 1), (2, 1), (2, 3), (2, 4), (5, 3), (5, 5), (5, 6), (7, 5), (7, 6).</p>

<p>There's one more aspect to cover in this model. What about the fields that all content types will need, and what if they, too, should be independently revisioned? For example, every content type might have a <code>url</code> field and it would be nice if we could undo that, too. But moving a page isn't really changing the page, it's more like changing the site, and besides, it would be nice to have the <code>url</code> for every object, regardless of content type, in one place. Luckily this looks very similar to what we have now, only this time the table is shared among content types. If we call this shared table <code>node</code>, the revision history might look like this:</p>

<table><tbody>
<tr><th>node</th>
<td>1</td><td></td><td>3</td><td></td><td></td><td></td><td>7</td><td>8</td>
</tr>
<tr><th>page_generic</th>
<td>1</td><td>2</td><td></td><td></td><td>5</td><td></td><td>7</td><td></td>
</tr>
<tr><th>page_localized</th><td>1</td><td></td><td>3</td><td>4</td><td>5</td><td>6</td><td></td><td></td>
</tr>
<tr><th></th><td>en</td><td></td><td>en</td><td>jp</td><td>jp</td><td>en</td><td></td><td></td>
</tr>
</tbody></table>

<p>The revision triplets would then be (1, 1, 1), (1, 2, 1), (3, 2, 3), (3, 2, 4), (3, 5, 3), (3, 5, 5), (3, 5, 6), (7, 7, 5), (7, 7, 6), (8, 7, 5), (8, 7, 6).</p>

<p>For the curious, the SQL to select all these triplets is complex but certainly not complicated:</p>

<pre><code>SELECT revision.revision_id,
       revision.content_id,
       revision.author,
       revision.timestamp,
       revision.comment,
       node.revision_id as node_revision,
       node.url,
       page_generic.revision_id as generic_revision,
       page_generic.show_in_menu,
       page_generic.show_in_search,
       page_localized.revision_id as localized_revision,
       page_localized.title,
       page_localized.text
FROM revision, node, page_generic, page_localized
WHERE node.revision_id = (
    SELECT max(node.revision_id)
    FROM node
    WHERE node.revision_id &lt;= revision.revision_id
      AND node.content_id = revision.content_id
) AND page_generic.revision_id = (
    SELECT max(page_generic.revision_id)
    FROM page_generic
    WHERE page_generic.revision_id &lt;= revision.revision_id
      AND page_generic.content_id = revision.content_id
    
) AND page_localized.revision_id IN (
    SELECT max(page_localized.revision_id)
    FROM page_localized
    WHERE page_localized.revision_id &lt;= revision.revision_id
      AND page_localized.content_id = revision.content_id
    GROUP BY page_localized.locale
) AND (
    revision.revision_id = node.revision_id OR
    revision.revision_id = page_generic.revision_id OR
    revision.revision_id = page_localized.revision_id
)
GROUP BY node.revision_id,
         page_generic.revision_id,
         page_localized.revision_id</code></pre>

<p>Notice that the <code>revision</code> table is correlated in those subqueries - that is, it refers to the <code>revision</code> table in the outer select. Very important!</p>

<p>We went through many designs of our revision model before ending up with this one, and so far it seems to alleviate all the problems our previous designs faced. I hope I've shown you that there are a lot of things to consider when using multilingual revisioned records, and I hope you believe me when I say that this solution is serving us pretty well. <a href="http://www.sqlalchemy.org">SQLAlchemy</a> handles it beautifully, although we've run across many chances for SQLAlchemy to improve along the way, and even submitted a patch.</p>

<p>Since one of our goals in <a href="http://www.pagodacms.org">Pagoda</a> is to allow people to easily create content types using this model, we have a few simple helpers that will hide many of the details. Next post: <em>eat the sandwich!</em></p>]]></content:encoded>
    </item>

    <item>
      <title>Pagoda Chat</title>
      <link>http://blog.case.edu/bmb12/2007/06/pagoda_chat</link>
      <description> Brian I&apos;m workin on a neat little diagram of our revision model to post on the interblog Ian nice....</description>
      <guid>http://blog.case.edu/bmb12/2007/06/pagoda_chat</guid>
      
      <pubDate>Wed, 27 Jun 2007 13:04:24 EST</pubDate>
      <content:encoded><![CDATA[<dl>
<dt>Brian</dt>
<dd>I'm workin on a neat little diagram of our revision model</dd>
<dd>to post on the interblog</dd>
<dt>Ian</dt>
<dd>nice. maybe we should make a diorama as well.</dd>
<dt>Brian</dt>
<dd>hahaha</dd>
<dt>Ian</dt>
<dd>i've got some real nice nativity figures that could represent various content types.</dd>
<dt>Brian</dt>
<dd>well if we do that we'll need a mobile too</dd>
<dt>Ian</dt>
<dd>we're going to beat the pants off everyone at the computer science fair.</dd>
</dl>

<p>As you may have read on <a href="http://trac.pagodacms.org/wiki/SuperSimplePagodaBlog">our "secret" wiki-blog</a>, we gave our code base a fresh start, have a kickin' new polymorphic, multilingual, revisioned content model (P.S. I totally respect the Zope guys now for building their own object database!), and Ian made us a nice little documentation generator he's calling <em>eat the sandwich</em>, which will be getting its own project space shortly.</p>

<p>I'll be posting about revisions soon...</p>]]></content:encoded>
    </item>

    <item>
      <title>Tux Droid Presentation</title>
      <link>http://blog.case.edu/bmb12/2007/06/tux_droid_presentation</link>
      <description>Tonight at Clepy I gave a presentation about Tux Droid. A few months ago I received an offer to test...</description>
      <guid>http://blog.case.edu/bmb12/2007/06/tux_droid_presentation</guid>
      
        <category domain="http://blog.case.edu/bmb12/clepy/index">Clepy</category>
      
        <category domain="http://blog.case.edu/bmb12/python/index">Python</category>
      
      <pubDate>Mon, 04 Jun 2007 22:55:32 EST</pubDate>
      <content:encoded><![CDATA[<p>Tonight at <a href="http://clepy.org/">Clepy</a> I gave a presentation about <a href="http://www.tuxisalive.com">Tux Droid</a>. A few months ago I received an offer to test and keep a free Tux Droid if I promised to do cool stuff with it. A while after responding and talking about <a href="http://wiki.case.edu/Project_Club">Case's sweet hacker club</a> it arrived in the mail! The presentation isn't much without the demonstrations, but the slides are online at <a href="http://exogen.case.edu/tux/">exogen.case.edu/tux</a>. When I come up with some nice polished Tux programs you'll hear about them here. Tux is available in <a href="http://wiki.case.edu/Project_Club">Project Club</a> for anyone to play with.</p>]]></content:encoded>
    </item>

    <item>
      <title>Just Because I Can</title>
      <link>http://blog.case.edu/bmb12/2007/06/just_because_i_can</link>
      <description> (Picture credit: Steve!)...</description>
      <guid>http://blog.case.edu/bmb12/2007/06/just_because_i_can</guid>
      
      <pubDate>Sun, 03 Jun 2007 14:37:48 EST</pubDate>
      <content:encoded><![CDATA[<p><img alt="mustache.jpg" src="http://blog.case.edu/bmb12/2007/06/03/mustache.jpg" width="424" height="459"/></p>

<p>(Picture credit: Steve!)</p>]]></content:encoded>
    </item>

    <item>
      <title>Project and apartment updates</title>
      <link>http://blog.case.edu/bmb12/2007/05/project_and_apartment_updates</link>
      <description>Some interesting bits from the past few weeks... Next Tuesday I&apos;ll be having lunch with Mike Cermak, webmaster for the...</description>
      <guid>http://blog.case.edu/bmb12/2007/05/project_and_apartment_updates</guid>
      
        <category domain="http://blog.case.edu/bmb12/cleveland/index">Cleveland</category>
      
        <category domain="http://blog.case.edu/bmb12/pagoda/index">Pagoda</category>
      
        <category domain="http://blog.case.edu/bmb12/projects/index">Projects</category>
      
        <category domain="http://blog.case.edu/bmb12/python/index">Python</category>
      
      <pubDate>Fri, 04 May 2007 01:49:21 EST</pubDate>
      <content:encoded><![CDATA[<p>Some interesting bits from the past few weeks...</p>

<p>Next Tuesday I'll be having lunch with Mike Cermak, webmaster for the <a href="http://www.gcrta.org">Greater Cleveland Regional Transit Authority</a>. In <a href="http://blog.case.edu/bmb12/2007/04/quicker_cleveland_rta_schedule_with_django">my previous entry</a> I mentioned my <a href="http://exogen.case.edu/rta/">RTA Schedule</a> project which has been gaining popularity. There were only a few routes listed on there when I posted it, and the list has been growing as people have been using the route adder. Mike wants to work together to come up with ideas and improvements that will encourage projects like mine&mdash;a very cool response, and beneficial to RTA users as well. I'm looking forward to it!</p>

<p>Remember <a href="http://blog.case.edu/bmb12/2007/04/multiple_sites_one_python_pagoda_import_tricks">those wacky import tricks I posted about</a> to get multiple database engines working nicely in <a href="http://www.pagodacms.org">Pagoda</a>? After coming up with that, Ian dug around to figure out what changes would be necessary to not have to do that. He narrowed it down to one single line of code in TurboGears! In <a href="http://svn.turbogears.org/branches/1.0/turbogears/database.py"><tt>turbogears.database</tt></a>:</p>

<pre><code>def create_session():
    "Creates a session with the appropriate engine"
    return sqlalchemy.create_session(bind_to=get_engine())</code></pre>

<p>That <tt>bind_to</tt> argument is totally unnecessary when using <tt>DynamicMetaData</tt>! Changing that to just use SQLAlchemy's <tt>create_session</tt> without arguments makes multiple database engines possible without any black magic. Unfortunately, we didn't notice <a href="http://www.blueskyonmars.com/2007/05/02/turbogears-102-released/">TurboGears 1.0.2</a> about to be released and didn't start any discussion about changing this in time. For now we use this little monkeypatch:</p>

<pre><code>session_context = turbogears.database.session.context
session_context.registry.createfunc = sqlalchemy.create_session</code></pre>

<p>So I think it works more like <a href="http://cheeseshop.python.org/pypi/Alchemyware">Alchemyware</a> now, except we don't have to write models any differently and the engines are cached. The metadata is simply pointed to the appropriate engine in each thread.</p>

<p>Speaking of <a href="http://www.pagodacms.org">Pagoda</a>, we're still at least a couple weeks away from a beta release. We're currently writing glue for all the little bits and pieces we've created over the past couple months. We've satisfied many of our original goals and learned more about (and sometimes changed) others. I'll share more about these satisfied and modified goals later.</p>

<p>Pagoda's third contributor, <a href="http://www.cshesse.com">Chris</a>, moved back home to start hunting for jobs in the California area. Good luck, Chris! Chris is a fine electrical engineer and programmer and you should hire him.  This was his plan since starting to help with Pagoda, so it doesn't really affect our development schedule.</p>

<p>After receiving practically no feedback from the release of <a href="http://code.google.com/p/dmath/">dmath</a>, there has been a small surge of interest recently, with a couple contributions, so there will likely be a new release. I put up a <a href="http://cheeseshop.python.org/pypi/dmath">new egg</a> of the old version on the Cheese Shop after learning that the Python 2.5 version was busted.</p>

<p><a href="http://exogen.case.edu/projects/geopy/">geopy</a> continues to receive patches; recently the most-requested improvement was contributed by <a href="http://latteier.com/">Amos Latteier</a> and that is the removal of <tt>print</tt> chatter in favor of logging. I'll get 0.94 out this weekend with that and other improvements.</p>

<p>Since Chris moved out, our friend Greg moved in with me and Sara. Greg went to school for art and likes to paint and draw, and might even prove his cooking talents at culinary school next semester. I'll be helping him make a website for his comics, which are very funny, but I can't decide if it's because I know Greg and imagine him coming up with them, which itself makes me laugh. You'll be the judge soon enough...</p>

<p>There are two more new, smaller residents of our apartment as well... one's a 14-inch <a href="http://en.wikipedia.org/wiki/Oscar_%28fish%29">Oscar cichlid</a> and the other's a 15-inch <a href="http://en.wikipedia.org/wiki/Plecostomus">Plecostomus</a>. They're friendly and big! Now I have fantasies about getting them a bigger aquarium with all manner of luxuries. I picked them up from someone who's graduating and they came with their 45-gallon home and necessities for free! I'll post some pictures of these guys soon.</p>]]></content:encoded>
    </item>

    <item>
      <title>Quicker Cleveland RTA Schedule with Django</title>
      <link>http://blog.case.edu/bmb12/2007/04/quicker_cleveland_rta_schedule_with_django</link>
      <description> Since moving to Cleveland Heights, Sara and I have been taking the RTA to and from campus pretty often....</description>
      <guid>http://blog.case.edu/bmb12/2007/04/quicker_cleveland_rta_schedule_with_django</guid>
      
        <category domain="http://blog.case.edu/bmb12/cleveland/index">Cleveland</category>
      
        <category domain="http://blog.case.edu/bmb12/django/index">Django</category>
      
      <pubDate>Sat, 21 Apr 2007 02:54:18 EST</pubDate>
      <content:encoded><![CDATA[<div style="text-align: center"><img alt="RTA" src="http://exogen.case.edu/rta/static/images/rta.png" width="79" height="40" /></div>

<p>Since moving to Cleveland Heights, Sara and I have been taking the <a href="http://www.gcrta.org">RTA</a> to and from campus pretty often. The stop outside our apartment can get us there via the <a href="http://www.gcrta.org/schedules/rt9xwk.html">9X</a> or <a href="http://www.gcrta.org/schedules/rt7xwk.html">7X</a>, so we usually check those big tables before catching the bus. I thought it would be a big improvement if we could just have a little desktop widget that would say when the next bus on either route was coming.</p>

<p>But what's the fun in making such a simple widget if the backend powering it isn't generally useful? So I made <a href="http://exogen.case.edu/rta/">a simple site that shows the bus schedules</a> in a format more suited to our riding patterns. The plan is to have <a href="http://en.wikipedia.org/wiki/ICalendar">iCalendar</a> feeds of the routes and stops, which the widget will read, while others can use the feeds however they prefer. For now the <a href="http://exogen.case.edu/rta/stop/15/">Superior &amp; Mayfield</a> and <a href="http://exogen.case.edu/rta/stop/22/">Mayfield &amp; Euclid</a> pages serve my needs.</p>

<p>This is after a couple days of hacking, so there are bugs, particularly in the early morning hours (due to weekday buses running until Saturday morning, for example). A trip planner would be nice too. One annoying limitation is that there isn't a list of all stops for every route that I can find. The <a href="http://exogen.case.edu/rta/route/6/">6</a> stops at <a href="http://exogen.case.edu/rta/stop/22/">Mayfield &amp; Euclid</a>, but it isn't listed in <a href="http://www.gcrta.org/schedules/rt6wk.html">the abbreviated 6 schedule</a>, so it won't show up there. Argh.</p>

<p>If the route you want isn't listed yet, just replace the <strong>7X</strong> in http://exogen.case.edu/rta/route/<strong>7X</strong> with the route number and it'll fetch it from the RTA site.</p>

<p>Happy commuting...</p>]]></content:encoded>
    </item>

    <item>
      <title>Multiple sites, one Python: Pagoda import tricks</title>
      <link>http://blog.case.edu/bmb12/2007/04/multiple_sites_one_python_pagoda_import_tricks</link>
      <description>One of our early goals when designing Pagoda was to allow a single Pagoda instance to support multiple sites. This...</description>
      <guid>http://blog.case.edu/bmb12/2007/04/multiple_sites_one_python_pagoda_import_tricks</guid>
      
        <category domain="http://blog.case.edu/bmb12/pagoda/index">Pagoda</category>
      
        <category domain="http://blog.case.edu/bmb12/programming/index">Programming</category>
      
        <category domain="http://blog.case.edu/bmb12/projects/index">Projects</category>
      
        <category domain="http://blog.case.edu/bmb12/python/index">Python</category>
      
      <pubDate>Tue, 17 Apr 2007 18:57:24 EST</pubDate>
      <content:encoded><![CDATA[<p>One of our early goals when designing <a href="http://www.pagodacms.org">Pagoda</a> was to allow a single Pagoda instance to support multiple sites. This was due to the way memory works for web servers running on Python and TurboGears. How exactly this adds up depends on your threading and web server configuration (mod_python), but traditionally hosting multiple sites means running at least one Python instance per site, each costing 10-20 MB. The more modules each instance loads, the higher the memory usage, and since Pagoda sites will likely use a bunch of modules, that adds up. The most limiting factor in many hosting services is the amount of memory your account is allowed to consume.</p>

<p>Obviously if each Pagoda site is large and running custom code, it might be a good idea to run each in its own Python instance, so one site can't bring down all the others. But the common case, we think, is a bunch of moderately sized sites using just the built-in page management tools. So we devised some ways to allow multiple sites to run from one TurboGears project...</p>

<p>The first and simplest plan involved a database model, where pages and other table rows point to whichever site they belong to. You probably already know why this is a bad idea. First of all, every single table in the database needed to have a site_id column, since nothing would be shared between sites. Unique things like usernames would need their constraints modified to only be unique per-site. That got old pretty fast. Secondly was security. How could we ensure that every piece of code touching the database, even the eventual third-party plugins, would use the correct site in their queries so as not to mess with the others? And finally, having each site's contents in one massive database would not be very convenient if the site owners wanted backups of their portion of the database.</p>

<p>So we started looking at multi-database solutions, and quickly realized we were pretty much on our own for what we wanted to do. We don't just want some models in one database, and other models in a difference database; we want the same models in every database. Every site needs a pages table, for example. Since we're mapping tables with <a href="http://www.sqlalchemy.org">SQLAlchemy</a>, and each mapper is bound to <a href="http://www.sqlalchemy.org/docs/metadata.html">metadata</a>, an engine, and a session, it seems that we'd need to run the table and mapper definitions once per site; each time, the engine would point to the appropriate site's. And now the big trick: how do we do this without modifying any model code, so that plugin writers don't have to learn any silly new details, and without doing a bunch of extra work every time a controller needs to use a model? If our controllers import <tt>pagoda.models.pages</tt>, how will it know to get the <tt>Page</tt> class bound to the current site's engine, and not another site's?</p>

<p>We looked to <a href="http://www.cherrypy.org">CherryPy</a> for inspiration. In a TurboGears controller, importing <tt>cherrypy.request</tt> and <tt>cherrypy.response</tt> will make the current thread's request and response objects available. How do these objects magically belong to the appropriate thread? They simply use a class called <tt>ThreadLocalProxy</tt>. As the name suggests, <tt>cherrypy.request</tt> and <tt>cherrypy.response</tt> are proxy objects that determine the current thread and point object access to the correct <tt>request</tt> and <tt>response</tt> instances. Similarly, we want something like <tt>SiteLocalProxy</tt>, which will make model classes available that are magically bound to the correct site's engine.</p>

<p>Using <tt>ThreadLocalProxy</tt> as inspiration, we made a clever little object called <tt>site</tt>. When anything is imported from <tt>pagoda.site</tt>, it will rebind <tt>turbogears.database.metadata</tt> and <tt>turbogears.database.session</tt> after updating <tt>sqlalchemy.dburi</tt> in the config to point to the current site's. Then the requested module is imported and cached for next time (so the models aren't reinitialized every time). No model code was changed at all! The only necessary modification was importing from <tt>pagoda.site.models</tt> instead of <tt>pagoda.models</tt> in our controllers.</p>

<p>Our first implementation looked very much like <tt>ThreadLocalProxy</tt>, but it made our import statements look funny since <tt>site</tt> wasn't a real module. So we started investigating the <tt>imp</tt>, <tt>ihooks</tt>, and <tt>imputils</tt> modules, eventually leading us to <a href="http://www.python.org/dev/peps/pep-0302/">PEP 302</a>. With help from <a href="http://peak.telecommunity.com/DevCenter/Importing">Importing</a> (to reduce the amount of code necessary), we now have a special pseudo-module called <tt>site</tt>, and Pagoda modules imported from that will take the current request's site into account instead of just being imported once for the entire process.</p>

<p>Before writing up this entry, I came across <a href="http://cheeseshop.python.org/pypi/Alchemyware">Alchemyware</a>. At first it looked promising for what we want to do, but as far as I can tell it requires modifying the way you write models and reinstantiating them on every request. Also, I don't understand how the mapped class can be "shared by everyone" if it's being mapped to multiple databases.</p>

<p>Anyway, after cleaning up our proof-of-concept I'll share the code behind our import trickery in case anyone is trying to do something similar, but mostly just because such tricks are interesting.</p>

<p>In case you forgot, we missed the end-of-March deadline we set for our demo, due in part to being burned out after PyCon. We're shooting for the end of April now.</p>]]></content:encoded>
    </item>

    <item>
      <title>New homepage at brianbeck.com</title>
      <link>http://blog.case.edu/bmb12/2007/04/new_homepage_at_brianbeckcom</link>
      <description>Yesterday I sat down and finally made myself a new homepage. This is one step toward getting all my online...</description>
      <guid>http://blog.case.edu/bmb12/2007/04/new_homepage_at_brianbeckcom</guid>
      
      <pubDate>Wed, 04 Apr 2007 04:38:57 EST</pubDate>
      <content:encoded><![CDATA[<p>Yesterday I sat down and finally made myself <a href="http://www.brianbeck.com">a new homepage</a>. This is one step toward getting all my online assets organized so I can focus more on content and projects. This blog and my project pages and repositories will also be part of the transition to <a href="http://www.brianbeck.com">brianbeck.com</a>, or at least somewhere that isn't my campus server, which won't last forever.</p>

<p>My new homepage is a small Django project that lets me edit the "business card" info at the top, add projects and details, and set my blog feed. The design isn't totally identical across browsers, but it's not bad for one coding session. Also, the time on Ian's server is wrong (see my current location at the bottom).</p>

<p>I think the only other thing I'll add is a page showing all my projects and contributions around the web, even old and defunct stuff.</p>

<p>My new blog will either be <a href="http://trac.typosphere.org/">typo</a> or a custom job.</p>]]></content:encoded>
    </item>

    <item>
      <title>Credit card systems are super annoying</title>
      <link>http://blog.case.edu/bmb12/2007/03/credit_card_systems_are_super_annoying</link>
      <description>So I have a family credit card I sometimes use for small things and emergencies. A few days ago my...</description>
      <guid>http://blog.case.edu/bmb12/2007/03/credit_card_systems_are_super_annoying</guid>
      
      <pubDate>Thu, 29 Mar 2007 22:22:57 EST</pubDate>
      <content:encoded><![CDATA[<p>So I have a family credit card I sometimes use for small things and emergencies. A few days ago my wonderful grandparents told me I could use it to buy a new laptop as a present for my eventual graduation.</p>

<p>It was a pretty easy decision to buy a MacBook. I looked at one other company's website and was disgusted by the typical electronics shopping experience&mdash;crossed out price values highlighting urgent deals and savings, and other insulting bullshit.</p>

<p>So I went to the Apple Store with Steve and Sara. I told them what I wanted at the counter; for some annoying reason they think this means you want to chat with someone instead of spending money and getting out of there, so I waited while they found someone for me to talk to.</p>

<p>Eventually they brought out the box and I gave them my credit card. It was declined with no reason given. Great. After trying to contact my family, I called Visa and spoke with someone who said that the charge attempt never showed up&mdash;Apple's system just failed to contact theirs.</p>

<p>So I went back and tried again. It made them authorize by phone this time, and after a wait, declined claiming to have insufficient funds. The Apple store employee quietly related this information to me, to save me some embarrassment, I presume.</p>

<p>Much later my family got back to me, and was puzzled to hear what happened. They contacted Visa and there was plenty in the bank and no applicable limit&mdash;Visa claimed it was just a security feature and that the next attempt would be successful. This was ten minutes before the Apple store closed, and we had driven to another location by this time, so we hurried back and I made it in at the last minute, as evidenced by the lack of music on the store speakers. By now of course I looked like a total shady character trying to use a stolen or maxed out credit card. </p>

<p>The salesperson in training fetched the laptop a third time and took my Visa and identification. Just as he was ready to send me home with a new laptop, the other employees informed us how Apple's credit card system would just decline me again, since their system only allows a card to be used once in 24 hours. The previous attempts had ruined my chances for the rest of the day. I left the store defeated a third time.</p>

<p>So thanks to Visa and Apple for costing me three hours and some dignity and leaving me empty-handed thanks to some stupid policies. Clearly you don't want to help me legitimately buy something.</p>]]></content:encoded>
    </item>

    <item>
      <title>Inkscape plugin idea: Interface mockup builder</title>
      <link>http://blog.case.edu/bmb12/2007/03/inkscape_plugin_idea_interface_mockup_builder</link>
      <description>Usually for HTML/CSS interfaces I jump right into the coding and tweak until it looks right. But for Pagoda, we...</description>
      <guid>http://blog.case.edu/bmb12/2007/03/inkscape_plugin_idea_interface_mockup_builder</guid>
      
        <category domain="http://blog.case.edu/bmb12/inkscape/index">Inkscape</category>
      
      <pubDate>Sat, 24 Mar 2007 04:56:37 EST</pubDate>
      <content:encoded><![CDATA[<p>Usually for HTML/CSS interfaces I jump right into the coding and tweak until it looks right. But for <a href="http://pagodacms.org/">Pagoda</a>, we often each have some ideas in mind about how the interface for a feature should be presented, so spending time on easily changeable mockups instead of a less flexible prototype (pixel pushing with HTML/CSS is often time consuming and creatively limiting) is a good way to see whose ideas work out the best. I'm our mockup guy, and as in my <a href="http://blog.case.edu/bmb12/2007/03/with_help_from_inkscapes_blur_filter">previous entry</a>, I use Inkscape for this. Some features actually lend themselves very well to this purpose; some that come to mind are Paste Style, the Align &amp; Distribute dialog, and Ctrl-dragging to limit the direction of movement.</p>

<p>Today after drawing up some mockups, I had an idea: an interface builder plugin for Inkscape. It would have a library of common widgets found in web interfaces and desktop toolkits. You could drag these pre-built graphics onto the canvas and be able to change their state easily. For example, drag on a checkbox and change it to be unchecked, or drag on a tab and change it to be the selected tab. Tree widgets are especially time consuming to draw, don't get me started about changing the items later...</p>

<p>This would be nice because unlike various toolkit-specific interface builders, you still have all the power of Inkscape to think outside the realm of just widget layout, which is a big factor of web interfaces. Being free-form is a big plus.</p>

<p>So, some of the features in list form:<br />
<ul><br />
<li>Widget library</li><br />
<li>Transformations to change widget state (selected, disabled, etc.)</li><br />
<li>Some additional align &amp; distribute options that are useful for interface building, and resize options to make resizing grouped shapes easier (for many widgets you only want some shapes to resize in certain dimensions)</li><br />
<li>Something with layers to show different stages of an interface</li><br />
</ul></p>

<p>Any other ideas? Is there something out there like this?</p>]]></content:encoded>
    </item>


</channel>
</rss>