Birdhouse Updates is Back

A few years ago, before Twitter and Facebook cleaned the clock of the blogosphere, I used to do all my posting here, and sent regular email updates summarizing activity at Birdhouse. But like most bloggers, the meteoric rise of Twitter and Facebook took a lot of the wind out of the sails of this site, and I started posting a lot of content at social networks instead.

Contrary to popular belief, Twitter did not “kill” this blog. I’ve been posting here all along, just with much less frequency. If you stopped reading when I started Tweeting, scroll back through the past couple of years – there’s still been action on Birdhouse.

Still, I’ve never quite felt comfortable with putting so much on the social networks. I want to own my content. I want to control the design. I want to control how media and code samples are embedded. I don’t always like being restricted to length limits. I really really don’t like that almost everything posted to Facebook is invisible to most of the world (I feel strongly that the internet is at its best when it’s a public square).

Long story short, in 2011 I intend to start posting more content to Birdhouse. And I’m resurrecting Birdhouse Updates, the email digest version of this site. Want to get occasional updates of new stuff happening here? Enter your email on the Birdhouse Subscribe page and reply to the confirmation email you’ll receive. (If you’re reading this via email, you’re already subscribed).

For the geeks among you, I’ve written a new WordPress plugin called SoloMail, which I’ll be using to send the updates. Let me know if you find it useful.

Blogging isn’t dead! (it just smells funny :) Happy 2011.

How to Talk About Cycling to a Conservative

Excellent piece at commutebybike.com: How to Talk About Cycling to a Conservative, making the case that it’s actually quite easy to make the case for bicycle commuting to a conservative, if you frame it right. What conservatives don’t want to hear: Knee-jerk blanket statements like “Oil corporations are evil” and “Cars are stupid.” Points to make instead: “Bicycling reduces dependence on foreign oil” and “Bicycling builds self-reliance” and “Bicycling reduces traffic congestion.” Worth a read. xxx

This response at the LinkedIn bicycle commuters’ group by Joshua Putnam sums it up well:

The conservative arguments for cycling are really quite strong — it’s an exercise in self-reliance, it builds character and physical fitness in a society where a majority of young adults are unfit for military service, it reduces consumption of foreign oil, it reduces public expense for roads and health care, it extends the productive working lives of bicycle commuters, and it increases the workplace productivity of bicycle commuters while reducing absenteeism. Those are all valid, documented benefits that make bicycle commuting beneficial to the health of the republic, as well as to individual cyclists.

Woke Up in Another Lifetime

It’s been a while since I’ve posted recent goodies from our little music-writing enclave Stuck Between Stations. Happening:

Scot: Practice in Front of a Bush: Stuck on Beefheart, paying homage to “the only true dadaist in rock.”

Roger: “Woke Up In Another Lifetime,” on re-thinking James Taylor (read before reacting).

Scot: 4’33″ and the Copyright Cops, on Warner’s attempt to silence John Cage’s seminal silent work on YouTube.

Scot: Let’s Get It On (Ukulele Style) on an incredible recent performance by Hawaiian uke master Aldrine Guerrero in Berkeley.

Roger: Zorn in the USA: My Top Three John Zorn Moments, a tribute to the genius of recently passed sax god John Zorn.

Roger: How the Cedars Invaded the Land of Blue Pajamas, on legendary late-sixties Israeli garage band called the Seders.

Scot: Music From a Bonsai. “In the tradition of Harry Partch, whose microtonal scales played on gorgeous one-of-a-kind instruments my son once described as sounding like “space chimps driving a broken car,” Diego Stocco bought a bonsai tree and went at it with piano hammers, bows of various sizes, and a paint brush. And a MacBook Pro.”

More at the site.

Python Gift Circle

Holiday Python geekiness…

If your family (or classroom or workplace) does “gift circles,” where everyone buys a gift for exactly one other person in the group, you could do (and probably already do do) the old “pull a name out of a hat” thing. But that takes setup time: writing down names, cutting them out, finding a hat, passing it around… shouldn’t this process be automated? Here’s a little Python script to get it done quick.

On my MacBook, the script runs for ten people in 27 milliseconds – think of all the egg nog you could drink in the time you save!

Populate the “recipients” list with real names and run ./gift-circle.py.

Update: This script is now available at github.

#!/usr/bin/python
import random

'''
Gift exchange randomizer in Python.
Step through a list of people and, for each member of that list,
select someone else to be a recipient of their gift. That recipient:

    A) Must not be themselves (no self-gifting)
    B) Must not already have been assigned as a recipient

Due to randomization, we can't prevent the possibility that 7/8 of people
will all give to each other, leaving the 8th to give to themselves. Therefore
we keep running the function until we get full distribution.
'''

def give():
    str = ''

    givers = ['Leslie', 'Jamie', 'Avis', 'Jim', 'Amy', 'Scot', 'Mike', 'Miles', 'Buford', 'Momo']
    recipients = list(givers)  # Make a copy

    for idx, giver in enumerate(givers):

        # Grab random person from the recipients
        recipient = random.choice(recipients)

        # Make sure we haven't either randomly chosen the same recipient and giver OR
        # ended up with only one un-gifted person in the list.
        if recipient == giver:
            return False
        else:
            # Remove this recipient from the pool and build the results string
            recipients.remove(recipient)
            str = str + "{idx}: {giver} gives to {recipient}\n".format(
                idx=idx+1, giver=giver, recipient=recipient
                )
    return str


# Keep trying until we get through the set with no failures
results=give()
while not results:
    results = give()

print results

Output looks like this:

1: Leslie gives to Amy
2: Jamie gives to Leslie
3: Avis gives to Momo
4: Jim gives to Jamie
5: Amy gives to Buford
6: Scot gives to Mike
7: Mike gives to Scot
8: Miles gives to Avis
9: Buford gives to Jim
10: Momo gives to Miles

Happy holidays, you big nerd!

Instagram on Flickr

I’ve fallen in love with the iPhone-only photo sharing service Instagram. It’s kind of like Twitter, but for images only. Instead of a post being limited to 140 characters, posts are limited to one photograph and a title — that’s it! Users build social networks within the system by “Liking” images and following other users.

Sounds almost too simple to be interesting, but I find it incredibly refreshing – at a certain point, we all get overwhelmed by the deluge of words in our lives, and can’t possibly consume another tweet or Facebook post. Instagram lets us share tiny corners of our lives, those little magic moments that otherwise would pass unnoticed, as a pure, nearly wordless photostream. There’s something very simple and refreshing about it. And for me, it’s encouraged use of the iPhone’s built-in camera as a more artistic tool (but be careful of over-using Instagram’s built-in filters).

Unfortunately, Instagram has no real web interface, which means there’s no easy way to share your images with non-iPhone users. But images are still saved on the phone itself, so there’s nothing stopping you from uploading them to Flickr or other services manually. I’ve started a Flickr set, and will upload my saved Instagrams to it periodically.

Leaves after rain

Flickr set

I’m shacker on Instagram if you want to connect.

Mt. Tamalpais, West Point Inn

Absolutely glorious day hiking Mt. Tamalpais with family yesterday. Started at the peak, heading up to the old fire lookout station first for 360 degree views of the entire Bay Area first, then wound our way down on Fern Creek trail – so steep our thighs turned to rubber by the time we made it to the main fire road. From there, North 1.5 miles to the West Point Inn, which was once a thriving mountain getaway when the Gravity Railway served it until 1942. When the railway was decommissioned, maintenance of the Inn was taken over by volunteers, who have run it ever since. Beautiful piece of history nestled into the Bay Area’s greatest view spot. Apparently you can still spend the night in the cabins here! Ate our picnic lunch of macadamia nuts and peanut butter sandwiches and headed back. All told, just a 4-5 mile loop, but we were pooped! Did a bit of geocaching along the way.

Photo set with captions on Flickr

Bicycle Commuters per Region

Just had a fascinating 2-hour session on working with geocommons.com, which lets you create all kinds of amazing map/data mashups, using publicly uploaded and shared data sets and shape files. The data behind this map of Male bicycle commuters per region is very old (from the 2000 census) – would love to do the mashup again when the 2010 data comes out in March, to see how it compares.

Takes some tweaking to get the population distributions to tell the story, but here you can see how dramatically bicycle usage increases in urban centers, i.e. where bicycle commuting is feasible, then drops precipitously as you head out toward the ‘burbs.

Be sure to enabled the Legend at lower right to make sense of the shaded regions. My only complaint is that the map has to load all of its data before it can draw shaded regions for the current viewport. But otherwise, wow – this was incredibly easy to do.

View full map

Shorter URLs with Base62 in Django

Update, 4/2017: See this StackOverflow answer for a different (and probably shorter) approach to this problem.

URL shorteners have become a hot commodity in the age of Twitter, where every byte counts. Shorteners have their uses, but they can also be potentially dangerous, since they mask the true destination of a link from users until it’s too late (shorteners are a malware installer’s wet dream). In addition, they work almost as a second layer of DNS on top of the internet, and a fragile one at that – if a shortening company goes out of business, all the links they handle could potentially break.

On bucketlist.org, a Django site that lets users catalog life goals, I’ve been using numerical IDs in URLs. As the number of items stored started to rise, I watched my URLs getting longer. Thinking optimistically about a hypothetical future with tens of millions of records to serve, and inspired by the URL structure at the Django-powered photo-sharing site Instagr.am, decided to do some trimming now, while the site’s still young. Rather than rely on a shortening service, decided to switch to a native Base 62 URL schema, with goal page URIs consisting of characters from this set:

BASE62 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

rather than just the digits 0-9. The compression is significant. Car license plates use just seven characters and no lower-case letters (base 36), and are able to represent tens of millions of cars without exhausting the character space. With base 62, the namespace is far larger. Here are some sample encodings – watch as the number of characters saved increases as the length of the encoded number rises:

Numeric Base 62
1 b
22 w
333 fx
4444 bjG
55555 o2d
666666 cN0G
7777777 6Dwb
88888888 gaYdK
999999999 bfFTGp
1234567890 bv8h5u

I was able to find several Django-based URL shortening apps, but I didn’t want redirection – I wanted native Base62 URLs. Fortunately, it wasn’t hard to roll up a system from scratch. Started by finding a python function to do the basic encoding – this one did the trick. I saved that in a utils.py in my app’s directory.

Of course we need a new field to store the hashed strings in – I created a 5-character varchar called “urlhash” … but there’s a catch – we’ll come back to this.

The best place to call the function is from the Item model’s save() method. Any time an Item is saved, we grab the record ID, encode it, and store the return value in urlhash. By putting it on the save() method, we know we’ll never end up with an empty urlhash field if the item gets stored in an unpredictable way (site users can either create new items, or copy items from other people’s lists into their own, for example, and there may be other ways in the future — we don’t want to have to remember to call the baseconvert() function from everywhere when a single place will do — keep it DRY!)).

Generating hashes

So in models.py:

from bucket.utils import BASE10, BASE62, baseconvert

...

def save(self):

    # Do a bunch of stuff not relevant here...

    # Initial save so the record gets an ID returned from the db
    super(Item, self).save()

    if not self.urlhash:
        self.urlhash = baseconvert(str(self.id),BASE10,BASE62)
        self.save()     

Now create a new record in the usual way and verify that it always gets an accompanying urlhash stored. We also need to back-fill all the existing records. Easy enough via python manage.py shell:

from bucket.models import Item
from bucket.utils import BASE10, BASE62, baseconvert

items = Item.objects.all()
for i in items:
    print i.id
    i.urlhash = baseconvert(str(i.id),BASE10,BASE62)
    print i.urlhash
    print
    i.save()

Examine your database to make sure all fields have been populated.

About that MySQL snag

About that “snag” I mentioned earlier: The hashes will have been stored with mixed-case letters (and numbers), and they’re guaranteed to be unique if the IDs you generated them from were. But if you have two records in your table with urlhashes ‘U3b’ and ‘U3B’, and you do a Django query like :


urlhash = 'U3b'
item = Item.objects.get(urlhash__exact=urlhash)

Django complains that it finds two records rather than one. That’s because the default collation for MySQL tables is case-insensitive, even when specifying case-sensitive queries with Django! This issue is described in the Django documentation and there’s nothing Django can do about it – you need to change the collation of the urlhash column to utf8_bin. You can do this easily with a good database GUI, or with a query similar to this:

ALTER TABLE `db_name`.`db_table_name` CHANGE COLUMN `urlhash` `urlhash` VARCHAR(5) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '' AFTER `id`;

or, if you’re creating the column fresh on an existing table:

ALTER TABLE `bucket_item` ADD `urlhash` VARCHAR( 5 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL AFTER `id` , ADD INDEX ( `urlhash` )

Season to taste. It’s important to get that index in there for performance reasons, since this will be your primary lookup field from now on.

Tweak URL patterns and views

Since the goal is to keep URLs as short as possible, you have two options. You could put a one-character preface on the URL to prevent it from matching other word-like URL strings, like:

foo.org/i/B3j

but I wanted the shortest URLs possible, with no preface, just:

foo.org/B3j

Since I have lots of other word-like URLs, and can’t know in advance how many characters the url hashes will be, I simply moved the regex to the very last position in urls.py – this becomes the last pattern matched before handing over to 404.

url(r'^(?P<urlhash>\w+)/$', 'bucket.views.item_view', name="item_view"),

Unfortunately, I quickly discovered that this removed the site’s ability to use Flat Pages, which rely on the same fall-through mechanism, so I switched to the “/i/B3j” technique instead.

url(r'^i/(?P<urlhash>\w+)/$', 'bucket.views.item_view', name="item_view"),

Now we need to tweak the view that handles the item details a bit, to query for the urlhash rather than the record ID:


from django.shortcuts import get_object_or_404
...

def item_view(request,urlhash):        
    item = get_object_or_404(Item,urlhash=urlhash)
	...

It’s important to use get_object_or_404 here rather than objects.get(). That way we can still return 404 if someone types in a word-like URL string that the regex in urls.py can’t catch due to its open-endedness. Note also that we didn’t specify urlhash__exact=urlhash — case-sensitive lookups are the default in Django queries, and there’s no need to specify the default.

If you’ve been using something like {% url item_view item.id %} in your templates, you’ll obviously need to change all instances of that to {% url item_view item.urlhash %} (you may have to make similar changes in your view code if you’ve been using reverses with HttpResponseRedirect).

Handling the old URLs

Of course we still want to handle all of those old incoming links to the numeric URLs. We just need a variant of the original ID-matching pattern:

url(r'^(?P\d+)/$', 'bucket.views.item_view_redirect', name="item_view_numeric"),

which points to a simple view item_view_redirect that does the redirection:


def item_view_redirect(request,item_id):
    '''
    Handle old numeric URLs by redirecting to new hashed versions
    '''
    item = get_object_or_404(Item,id=item_id)
    return HttpResponseRedirect(reverse('item_view',args=[item.urlhash]))

Bingo – all newly created items get the new, permanently shortened URLs, and all old incoming links are handled transparently.

Bamboo Bike – Renovo Pandurban

Back in January 2010, I donated my old Gary Fisher mountain bike to the Peace Corps in Africa and took a leap for my next ride – decided to buy a custom-built bike from a small shop in Portland called Renovo, who specialize in wooden and bamboo bikes (laminated, not raw bamboo stalk like some other bike makers do). Renovo sent me a body measurement chart and the wife diligently took to me with a tape measure, so the resulting frame and parts would be dialed in perfectly for my dimensions.

Renovo builds some incredible stuff – every one of their bikes, from road bikes to mountain bikes to commuters, is a work of art, made with love and incredible craftsmanship. These guys know what they’re doing – in a former life, the Renovo guys were building wooden airplanes.
Continue reading “Bamboo Bike – Renovo Pandurban”