<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>scot hacker's scripts and utils</title>
	<atom:link href="http://birdhouse.org/software/feed/" rel="self" type="application/rss+xml" />
	<link>http://birdhouse.org/software</link>
	<description>Scot Hacker's Misc Scripts and Utils</description>
	<lastBuildDate>Fri, 06 Aug 2010 16:00:03 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>WordPress Mass Management Tools</title>
		<link>http://birdhouse.org/software/2010/08/wordpress-mass-management/</link>
		<comments>http://birdhouse.org/software/2010/08/wordpress-mass-management/#comments</comments>
		<pubDate>Wed, 04 Aug 2010 16:32:38 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=133</guid>
		<description><![CDATA[WordPress Mass Management Tools is a small collection of tools for managing multiple WordPress installations on a Unix/Linux/Mac server (or perhaps a Windows server with Cygwin (untested)). Yes, users can often self-install via Fantastico or similar programs, but what guarantee do you have that they&#8217;ll upgrade as soon as new releases become available? Letting users [...]]]></description>
			<content:encoded><![CDATA[<p>WordPress Mass Management Tools is a small collection of tools for managing multiple WordPress installations on a Unix/Linux/Mac server (or perhaps a Windows server with Cygwin (untested)). </p>
<p>Yes, users can often self-install via Fantastico or similar programs, but what guarantee do you have that they&#8217;ll upgrade as soon as new releases become available? Letting users run old versions of web software is a great way to get hacked. This kit lets you take control of users&#8217; installations by checking them out via svn and upgrading them <em>en masse</em>.</p>
<p>At <a href="http://hosting.birdhouse.org">Birdhouse Hosting</a> we use WordPress Mass Management Tools to create all new installations. When WP updates are released, we&#8217;re able to upgrade more than 100 WP installations belonging to 80+ users in a few minutes flat. </p>
<p>The kit is designed to be run by root &#8211; these are not tools for WordPress end users. The kit assumes basic knowledge of the bash shell.</p>
<p>This kit <strong>replaces</strong> the old wp-create and wp-mass-upgrade scripts which were distributed separately and are no longer supported. Includes:</p>
<p><strong>wp-create.sh</strong>: Super fast way to install WordPress for clients, via subversion. Performs the following tasks:</p>
<ul>
<li>Gather installation info</li>
<li>Create install dir and check out a copy of WordPress</li>
<li>Create database, db user, set db privs via external .sql file</li>
<li>Create WP config file</li>
<li>Create upload dir and set filesystem permissions</li>
<li>Generate array line for wp-sites.sh</li>
</ul>
<p>Final setup is done via browser.</p>
<p><strong>wp-mass-upgrade.sh</strong>: Iterates through all sites listed in wp-sites.sh, backs up the site&#8217;s database, upgrades to a specified version of WordPress, and sends the site owner email announcing the upgrade.</p>
<p><strong>wp-mass-plugins.sh</strong>: Generates a list of all active plugins  used by all sites on the server. The list can be compared to lists of known-incompatible plugins to help you make decisions about whether to skip any sites on the list.</p>
<p><strong><a href='http://birdhouse.org/software/wp-content/uploads/2010/08/wp-mass-tools.zip'>Download wp-mass-tools 1.1</a></strong></p>
<p>See the included readme.txt for documentation.</p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2010/08/wordpress-mass-management/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Twitter Favorites Bankruptcy</title>
		<link>http://birdhouse.org/software/2010/07/twitter-favorites-bankruptcy/</link>
		<comments>http://birdhouse.org/software/2010/07/twitter-favorites-bankruptcy/#comments</comments>
		<pubDate>Mon, 26 Jul 2010 08:06:18 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Twitter]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=115</guid>
		<description><![CDATA[I read Twitter primarily on the iPhone, and find tons of great links I want to read in a proper browser later on (I personally find reading most web sites on an iPhone to be more hassle than it&#8217;s worth). Perfect solution: Side-swipe an item in Tweetie and tap the star icon to mark it [...]]]></description>
			<content:encoded><![CDATA[<p>I read Twitter primarily on the iPhone, and find tons of great links I want to read in a proper browser later on (I personally find reading most web sites on an iPhone to be more hassle than it&#8217;s worth). Perfect solution: Side-swipe an item in Tweetie and tap the star icon to mark it as a favorite. Later, visit the Favorites section at twitter.com to follow up.</p>
<p>Unfortunately, over the past couple of years I&#8217;ve favorited <em>way</em> more things than I&#8217;ll ever have time to read. As of now, I&#8217;ve got 1600 favorites waiting to be read. Ain&#8217;t never gonna happen. I declare <a href="http://valleywag.gawker.com/254608/declaring-e+mail-bankruptcy">Twitter Favorite bankruptcy!</a> Needed a way to  batch-unfavorite the whole collection, and twitter.com doesn&#8217;t provide a tool for that. Time to dive into the Twitter API. <span id="more-115"></span></p>
<p>I heart Python, and <a href="http://code.google.com/p/tweepy/">Tweepy</a> is the best Twitter API tool for Python (documentation <a href="http://joshthecoder.github.com/tweepy/docs/api.html">here</a>). So start by getting Tweepy installed and on your Python path. If it&#8217;s installed correctly you should be able to type &#8220;import tweepy&#8221; in a python shell and get no errors. </p>
<p>Once installed, you&#8217;ll need to authenticate to Twitter. Note that we&#8217;re using basic authentication here. The &#8220;right&#8221; way is to use OAuth, but that requires registering your application with twitter.com &#8211; something users with this simple need won&#8217;t want to get into. Let&#8217;s explore.</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">import</span> tweepy
auth = tweepy.<span style="color: black;">BasicAuthHandler</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'your_username'</span>, <span style="color: #483d8b;">'your_password'</span><span style="color: black;">&#41;</span>
api = tweepy.<span style="color: black;">API</span><span style="color: black;">&#40;</span>auth<span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># Get recent items from your timeline:</span>
posts = api.<span style="color: black;">user_timeline</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'shacker'</span><span style="color: black;">&#41;</span>
<span style="color: black;">&#91;</span>p.<span style="color: black;">text</span> <span style="color: #ff7700;font-weight:bold;">for</span> p <span style="color: #ff7700;font-weight:bold;">in</span> posts<span style="color: black;">&#93;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># Cool, it works. Now get the list of  favorites for the </span>
<span style="color: #808080; font-style: italic;"># authenticating user (you).</span>
favs = api.<span style="color: black;">favorites</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
<span style="color: black;">&#91;</span>p.<span style="color: black;">text</span> <span style="color: #ff7700;font-weight:bold;">for</span> p <span style="color: #ff7700;font-weight:bold;">in</span> favs<span style="color: black;">&#93;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># How many did you get?</span>
<span style="color: #66cc66;">&gt;&gt;&gt;</span> <span style="color: #008000;">len</span><span style="color: black;">&#40;</span>favs<span style="color: black;">&#41;</span>
<span style="color: #ff4500;">20</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># Oops, that's only 20. But we need all 1600! Unfortunately, </span>
<span style="color: #808080; font-style: italic;"># you can only get a &quot;page&quot; of favorites at a time - which </span>
<span style="color: #808080; font-style: italic;"># is usually a batch of 20. We're going to have to cycle </span>
<span style="color: #808080; font-style: italic;"># through them. The docs say to use:</span>
&nbsp;
API.<span style="color: black;">favorites</span><span style="color: black;">&#40;</span><span style="color: black;">&#91;</span><span style="color: #008000;">id</span><span style="color: black;">&#93;</span><span style="color: black;">&#91;</span>, page<span style="color: black;">&#93;</span><span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># So since page is the 2nd arg, we're going to need </span>
<span style="color: #808080; font-style: italic;"># your user ID in order to access pages. So find your user ID:</span>
&nbsp;
<span style="color: #66cc66;">&gt;&gt;&gt;</span> api.<span style="color: black;">get_user</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'your_username'</span><span style="color: black;">&#41;</span>.<span style="color: #008000;">id</span>
<span style="color: #ff4500;">14098497</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># So 14098497 is your user ID. Now you can go back and </span>
<span style="color: #808080; font-style: italic;"># use the favorites API to get things in pages. </span>
<span style="color: #808080; font-style: italic;"># IOTW for page 2 of the results, use:</span>
&nbsp;
<span style="color: #66cc66;">&gt;&gt;&gt;</span> favs = api.<span style="color: black;">favorites</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'14098497'</span>, <span style="color: #483d8b;">'2'</span><span style="color: black;">&#41;</span>
<span style="color: #66cc66;">&gt;&gt;&gt;</span> <span style="color: black;">&#91;</span>t.<span style="color: black;">text</span> <span style="color: #ff7700;font-weight:bold;">for</span> t <span style="color: #ff7700;font-weight:bold;">in</span> favs<span style="color: black;">&#93;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># So how do we un-favorite a tweet? The docs say:</span>
<span style="color: #808080; font-style: italic;"># API.destroy_favorite(id)</span>
<span style="color: #808080; font-style: italic;"># So if we instead list the tweet IDs of the result set:</span>
&nbsp;
<span style="color: #66cc66;">&gt;&gt;&gt;</span> <span style="color: black;">&#91;</span>t.<span style="color: #008000;">id</span> <span style="color: #ff7700;font-weight:bold;">for</span> t <span style="color: #ff7700;font-weight:bold;">in</span> results<span style="color: black;">&#93;</span>
<span style="color: black;">&#91;</span><span style="color: #ff4500;">4601622480</span>, <span style="color: #ff4500;">4587326808</span>, <span style="color: #ff4500;">4527173532</span>, <span style="color: #ff4500;">4514186302</span>, <span style="color: #ff4500;">4497778507</span>, <span style="color: #ff4500;">4472272715</span>, <span style="color: #ff4500;">4431075519</span>, <span style="color: #ff4500;">4425332211</span>, <span style="color: #ff4500;">4048507627</span>, <span style="color: #ff4500;">4043010657</span>, <span style="color: #ff4500;">4023672253</span>, <span style="color: #ff4500;">4023524092</span>, <span style="color: #ff4500;">4022466872</span>, <span style="color: #ff4500;">4022334337</span>, <span style="color: #ff4500;">4016169277</span>, <span style="color: #ff4500;">4015641948</span><span style="color: black;">&#93;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># We can grab the first ID in the list and test with that. </span>
<span style="color: #808080; font-style: italic;"># Go to twitter.com, click on Favorites, find the first </span>
<span style="color: #808080; font-style: italic;"># tweet listed, and click the timestamp. The URL should </span>
<span style="color: #808080; font-style: italic;"># include the ID of the first tweet in the list. </span>
<span style="color: #808080; font-style: italic;"># Back in the shell, try and unfavorite it:</span>
&nbsp;
api.<span style="color: black;">destroy_favorite</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">4601622480</span><span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># Now refresh your Favorites page and notice that the </span>
<span style="color: #808080; font-style: italic;"># first tweet is gone from the list. Groovy. All we </span>
<span style="color: #808080; font-style: italic;"># have to do now is script the process.</span></pre></td></tr></table></div>

<p>Some of the tweepy methods provide a &#8216;cursor&#8217; argument to help you iterate through pages, but the favorites() method does not. But we don&#8217;t really need one &#8211; we can simply iterate through pages until the results list comes back empty. </p>
<p>But there&#8217;s a hitch: Twitter&#8217;s basic authentication is <a href="http://dev.twitter.com/pages/rate-limiting#rest">limited to 150 API requests per hour</a>, and each un-favorite counts as one request. If you have a lot of favorites like I did, you&#8217;ll end up hitting your API limit pretty quickly. The solution is to &#8220;sleep&#8221; the script between each page of results. The math says we should be able to sleep for 8-10 minutes per page and come in under the 150/hour limit. But in practice, I found myself hitting the limit anyway (browser and desktop client running at the same time skew the results, and we want to be safe). In other words, this is not a script that will complete in a few seconds &#8211; with a 15 minute pause between each cycle, you&#8217;ll want to run it in the background while you do other things. The 15-minute pause is designed to allow for enough spare cycles every hour that you can continue using Twitter normally. No rush, right? Chillax!</p>
<p>So save the following as a text file (in the same directory as tweepy.py if you don&#8217;t grok python paths), change the username and password at the top, and run it with <code>python filename.py</code>.</p>
<p><strong>Warning:</strong> This script commits total bankruptcy on your favorites &#8211; it will remove ALL of them. Run it only if you&#8217;re sure you want to do that.</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">import</span> tweepy
<span style="color: #ff7700;font-weight:bold;">from</span> <span style="color: #dc143c;">time</span> <span style="color: #ff7700;font-weight:bold;">import</span> sleep
&nbsp;
username = <span style="color: #483d8b;">'your_username'</span>
password = <span style="color: #483d8b;">'your_password'</span>
&nbsp;
auth = tweepy.<span style="color: black;">BasicAuthHandler</span><span style="color: black;">&#40;</span>username, password<span style="color: black;">&#41;</span>
api = tweepy.<span style="color: black;">API</span><span style="color: black;">&#40;</span>auth<span style="color: black;">&#41;</span>
userid = api.<span style="color: black;">get_user</span><span style="color: black;">&#40;</span>username<span style="color: black;">&#41;</span>.<span style="color: #008000;">id</span>
&nbsp;
<span style="color: #808080; font-style: italic;"># Counter is used for reporting purposes only</span>
i = <span style="color: #ff4500;">1</span>
&nbsp;
<span style="color: #483d8b;">''</span><span style="color: #483d8b;">'
Tweepy will return pages of favorites as lists.
In Python, an empty list is considered false, 
so we we don'</span>t need to know the number of result pages -
just keep going until the results <span style="color: #008000;">list</span> <span style="color: #ff7700;font-weight:bold;">is</span> empty. 
<span style="color: #483d8b;">''</span><span style="color: #483d8b;">'
&nbsp;
# Initiate an infinite loop, to be broken when we run out of favorites.
while True: 
&nbsp;
    results = api.favorites(userid)
    if results:
        print &quot;Page %d exists. Unfavoriting %d tweets.&quot; % (i, len(results))
        for r in results:
            api.destroy_favorite(r.id) # Here'</span>s the money shot.
&nbsp;
        <span style="color: #808080; font-style: italic;"># Rest for a while.</span>
        <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Script is sleeping for 15 minutes to accommodate Twitter rate limits.&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">print</span>
        i += <span style="color: #ff4500;">1</span>        
        sleep<span style="color: black;">&#40;</span><span style="color: #ff4500;">900</span><span style="color: black;">&#41;</span>       <span style="color: #808080; font-style: italic;"># 900 seconds = 15 minutes</span>
    <span style="color: #ff7700;font-weight:bold;">else</span>:
        <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;No more favorites. Buh-bye.&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">break</span></pre></td></tr></table></div>

<p>By morning, you should have no favorites left. Enjoy the clean slate while it lasts!</p>
<p>Oh, by the way, I&#8217;m <a href="http://twitter.com/shacker/">shacker</a> on Twitter.</p>
<p><strong>Update:</strong> If you have a lot of favorites, you may encounter some weirdness after running this script for a while. I found that after the first thousand un-favorite requests, Twitter started rate-limiting me down from 20 tweets per page to 14, then to 8, then 2, then 1. When it got to one, I tweaked the script to request one per minute instead of waiting 15 minutes between. At that rate, I was making only 60 requests per hour. But eventually, twitter started returning zero. And lo and behold, when I accessed twitter.com/favorites in a browser, I got a &#8220;Something is technically wrong&#8221; message from Twitter.</p>
<p>I logged out and then in again with another account and /favorites was working fine &#8211; they had basically made it impossible to access my favorites in any way from the account that had been making all the un-favorite requests. This was puzzling and troubling. I had taken great pains to play with the API by the stated rules, to not exceed the rate limits, and to be gentle on their system&#8230; but still they treated this script like it was a bad actor. If anyone from Twitter reads this and can explain what&#8217;s going on, I&#8217;d love to hear what I can do to make it better. </p>
<p>A post at TechCrunch explains that Twitter has been <a href="http://techcrunch.com/2010/06/29/twitter-api-limit/">slashing</a> its rate limits lately, which is probably related to the behavior I&#8217;m experiencing.</p>
<p>If you have fewer than a thousand favorites, this script should still work fine, with no ill effects.</p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2010/07/twitter-favorites-bankruptcy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>django-treedata: A web app for municipal tree tracking</title>
		<link>http://birdhouse.org/software/2009/11/django-treedata/</link>
		<comments>http://birdhouse.org/software/2009/11/django-treedata/#comments</comments>
		<pubDate>Sun, 08 Nov 2009 19:22:04 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=98</guid>
		<description><![CDATA[Recently I was invited to participate in the California Data Camp and DataSF App Contest hosted by California Watch and spot.us. The unconference would feature lots of discussion about making use of publicly available data sets to improve quality of life. The App Contest challenged developers to choose one of the many data sets available [...]]]></description>
			<content:encoded><![CDATA[<p>Recently I was invited to participate in the<a href="http://datacamp.eventbrite.com/"> California Data Camp and DataSF App Contest</a> hosted by <a href="http://www.centerforinvestigativereporting.org/projects/californiawatch/">California Watch</a> and <a href="http://spot.us/">spot.us</a>. The unconference would feature lots of discussion about making use of publicly available data sets to improve quality of life. The App Contest challenged developers to choose one of the many data sets available at <a href="http://datasf.org/">DataSF.org</a> and build something cool with it in a relatively short period of time. Here&#8217;s a <a href="http://datasf.org/showcase/">showcase</a> of existing apps built on those data sets.</p>
<p>The <a href="http://multimedia.journalism.berkeley.edu/">Knight Digital Media Center</a> (where I work) invited me to take part, and I chose a database of <a href="http://datasf.org/story.php?title=street-tree-list">64,000 San Francisco trees and plants</a>. The goal of the project was to:</p>
<ul>
<li>Make it easy for citizens to explore and discover the huge number of plant species and individual trees maintained by the city</li>
<li>Make it easy for citizens to &#8220;flag&#8221; a tree as needing maintenance, water, food, etc.</li>
<li>Make it easy for citizens to request a tree at a particular location</li>
<li>Provide data visualization tools to let citizens explore and understand the plant variety visually</li>
<li>Make it easy to see what a given species will look like in 5,10,15,20 years when requesting a tree</li>
<li>Ideally, a future version of the app would include ecology data on all species, listing the water consumption and carbon offset of each</li>
</ul>
<p>I decided to build the project on Django, of course. Put a total of around 15 hours into the project, about half of which was spent massaging and cleaning the provided data, which had multiple pieces of information stuffed into single fields, non-standard date formats, and was completely non-relational. Cities implementing django-treedata &#8220;fresh,&#8221; without having to be compatible with an existing data entry system, won&#8217;t have to worry about data conversion/format issues.</p>
<p>Once the data was clean, the rest was pretty straightforward Django stuff. The one non-standard aspect is the external &#8220;lastcount&#8221; script, which counts the number of instances of each species and stores the result on a field in the Species model. Doing this in real time for such a large number of trees turned out to be very computationally expensive, so the script needs to be run from a crontab periodically.</p>
<p>Because dev time was so limited, all of it went into data cleaning and building out the models and views. We&#8217;ve put ZERO work into design considerations, so please don&#8217;t crucify us for that. The CSS is built on top of the excellent <a href="http://960.gs/">960 Grid</a> framework, so layout will be easy. Some of the data visualization is done via the excellent <a href="http://code.google.com/apis/chart/">Google Charts API</a>.</p>
<p>Much to our surprise, the django-treedata app won the competition!</p>
<p>Please note that the project has only been run in a development environment and has never been publicly deployed &#8211; the project as it stands should be considred a starting point for cities to built on. The <a href="http://code.google.com/p/django-treedata/source/browse/trunk/README.TXT">readme</a> explains more. The project is completely open source and is released under the very liberal <a href="http://www.opensource.org/licenses/bsd-license.php">BSD</a> license &#8211; do with it as you will.</p>
<p>Thanks also to J-School webmaster for Chuck Harris for his contributions to the project</p>
<p><span id="more-98"></span></p>
<h3>Screenshots</h3>

<a href='http://birdhouse.org/software/2009/11/django-treedata/caretakers/' title='caretakers'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/caretakers-150x150.png" class="attachment-thumbnail" alt="caretakers" title="caretakers" /></a>
<a href='http://birdhouse.org/software/2009/11/django-treedata/details/' title='details'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/details-150x150.png" class="attachment-thumbnail" alt="details" title="details" /></a>
<a href='http://birdhouse.org/software/2009/11/django-treedata/paginated-table/' title='paginated-table'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/paginated-table-150x150.png" class="attachment-thumbnail" alt="paginated-table" title="paginated-table" /></a>
<a href='http://birdhouse.org/software/2009/11/django-treedata/popular-species/' title='popular-species'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/popular-species-150x150.png" class="attachment-thumbnail" alt="popular-species" title="popular-species" /></a>
<a href='http://birdhouse.org/software/2009/11/django-treedata/rx/' title='rx'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/rx-150x150.png" class="attachment-thumbnail" alt="rx" title="rx" /></a>
<a href='http://birdhouse.org/software/2009/11/django-treedata/treeadmin/' title='treeadmin'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/treeadmin-150x150.png" class="attachment-thumbnail" alt="treeadmin" title="treeadmin" /></a>
<a href='http://birdhouse.org/software/2009/11/django-treedata/treewordle/' title='treewordle'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2009/11/treewordle-150x150.png" class="attachment-thumbnail" alt="treewordle" title="treewordle" /></a>

<h3>Download</h3>
<p>django-treedata is available on <a href="http://code.google.com/p/django-treedata">Google Code</a>, via svn.</p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2009/11/django-treedata/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Populate Mailman Lists from Django Projects</title>
		<link>http://birdhouse.org/software/2009/09/listgen/</link>
		<comments>http://birdhouse.org/software/2009/09/listgen/#comments</comments>
		<pubDate>Mon, 07 Sep 2009 08:19:51 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=67</guid>
		<description><![CDATA[Django projects can end up with complex sets of Users, Groups, and multiple profile types representing different types of people. For example a school site might have Students, Parents, Teachers, Staff, and Alumni. The mailing lists for that school will live completely outside of the Django project, but there&#8217;s a good chance you&#8217;d like to [...]]]></description>
			<content:encoded><![CDATA[<p>Django projects can end up with complex sets of Users, Groups, and multiple profile types representing different types of people. For example a school site might have Students, Parents, Teachers, Staff, and Alumni. The mailing lists for that school will live completely outside of the Django project, but there&#8217;s a good chance you&#8217;d like to be able to populate list membership from your membership database rather than maintaining lists by hand. And you&#8217;d like to be able to use any combination of criteria to populate your lists (Group membership,  profile types, join date, privileges, etc.)</p>
<p>Since Mailman is installed on so many web hosts by default, there&#8217;s a good chance you&#8217;re using it, and have lots of overlapping and non-overlapping groups subscribed to various lists. I recently went through the process of integrating a bunch of  Mailman lists with the membership representing a school intranet built in Django and thought I&#8217;d document it for anyone going through a similar process.</p>
<p>Once everything is set up, you&#8217;ll never need to use the Mailman interface to manage lists again &#8211; everything will be automated and self-maintaining.<br />
<span id="more-67"></span><strong> </strong></p>
<p><strong>Manual Overrides / Prerequisites<br />
</strong></p>
<ul>
<li>The simple queries are easy. But in the real world you end up in situations where, say, the Maintenance person needs to be able to write to the Teachers list but not receive mail from it. Or maybe teachers need to be able to write to the Board, but not be privy to the Board&#8217;s list traffic.</li>
<li>Some people may fit a particular group query but not want to receive mail from lists for those groups (because maybe their spouse handles that stuff).</li>
<li>Some people don&#8217;t match a particular query group but need to be made into regular list members anyway.  For example you might want to have the principal be on all mailing lists, even though he&#8217;s not a parent of any child enrolled in a class.</li>
</ul>
<p>The key to making some of this happen is a feature of Mailman called &#8220;nomail&#8221; mode, which allows certain subscriber addresses to send to a list but not receive mail from it. This recipe interfaces with Mailman from the command line, using the <code>add_members</code> command. But here&#8217;s the rub &#8211; the official Mailman distribution&#8217;s <code>add_members</code> command is missing the option to add a person to a list in nomail mode (grrrr). However, the version of <code>add_members</code> that comes with OS X Server <em>does</em> include this option. I have no idea why Mailman hasn&#8217;t seen fit to integrate Apple&#8217;s changes into the official distro, since it&#8217;s insanely useful.  But since <code>add_members</code> is just a Python script, you can replace the version of <code>add_members</code> that comes with the official distro with Apple&#8217;s version and it&#8217;ll work fine on any server platform.</p>
<p>Because the Mailman command-line scripts must be run as root, you must be the administrator of the server this recipe runs on.</p>
<p>To handle all the exceptions and special cases your organization probably has, you&#8217;re going to need two tweaks to your apps/models. First, on your Profile model(s), create a new field for people who don&#8217;t want to be on the lists at all:</p>
<pre>no_lists = models.BooleanField(default=False,
     help_text="When checked, this parent will NOT be subscribed
     to the mailing lists they normally would be.")</pre>
<p>You can now check this box in any user profile for people who show up in group queries but don&#8217;t wish to receive mail from any of the organization&#8217;s lists.</p>
<p>You&#8217;re also going to need an additional model to store the exceptions &#8211; additional people to add to lists, either in yesmail or nomail mode:</p>
<pre>class ListExtra(models.Model):
    """
    Extra email addresses to be added to mailing lists.
    """
    list = models.SlugField()
    addresses = models.TextField(blank=True,
         help_text='Add addresses here, one per line.')

    def __unicode__(self):
        return u'%s' % (self.list)</pre>
<p><img src="http://birdhouse.org/software/wp-content/uploads/2009/09/extras.jpg" alt="extras" title="extras"  class="alignright size-full wp-image-86" / > Once the model has been created and you&#8217;ve run syncdb, create a record for each list you want to track, e.g. &#8220;teachers&#8221; for the list &#8220;teachers@ourschool.org&#8221; .  Do not enter real teachers here &#8211; they&#8217;ll come from ORM queries on the regular User table.  Instead, this is where you&#8217;ll add <em>non</em>-teachers  who should be regular members of the teachers@ mailing list anyway. Add such people&#8217;s addresses to the field one-per-line.</p>
<p>If you have the need to let certain people write to the teachers list but NOT receive mail from it, create an additional record called &#8220;teachers-nomail&#8221;.</p>
<p><strong>How It Works</strong></p>
<p>This recipe is really two scripts &#8211; one in Python and the other bash &#8211; that I end up running from a single shell alias as needed. The two scripts I use can be downloaded at the end of this post and are well commented.</p>
<p>The Python script (listgen.py) interacts with the Django ORM, extracting list memberships out into text files. The bash script (listgen.sh) iterates through that set of text files and, for each, quickly unsubscribes the entire membership and then re-subscribes them from the text files generated by the python script. On my server, the whole process unsubs and resubs 14 lists covering 125 people in  5 seconds flat, so there&#8217;s no real danger of anyone freaking out because they were temporarily unsubscribed.  And  listgen.sh makes sure that subs and unsubs occur without notifying users of list membership changes, so they don&#8217;t get scary messages.</p>
<p><code>listgen.py</code> sets up the Django environment for the given project, defines a function for processing query results into text files, then iterates through a list of mailing list names (without the domain name). For each, it runs a Django ORM query. Then it excludes from that queryset anyone who has opted out of all lists.</p>
<p>Next, it looks for a corresponding ListExtras record and extracts the &#8220;extra&#8221; addresses it contains. Finally, it looks for a corresponding &#8220;nomail&#8221; ListExtra record (e.g. <code>teachers-nomail</code>). It then removes any duplicate members and passes the final query data to the file processing function and writes text files to a directory on the server.</p>
<p><code>listgen.sh</code> iterates through an array of all mailing lists and, for each, unsbuscribes all members (without sending unsub messages). It then locates the text file corresponding to the current list and subscribes all regular members in yesmail mode. Finally, it looks for a corresponding <code>listname-nomail.txt</code> file and subscribes the addresses it contains in nomail mode.</p>
<p><strong>Deployment</strong></p>
<p>The scripts I&#8217;m using are linked below.</p>
<p>First, check to see whether the version of <code>add_members</code> on your server supports the <code>-e</code> flag (for nomail mode). If it does not, rename the <code>add_members</code> script on your server to <code>-old</code> and put the replacement <code>add_members</code> script in its place. Run &#8220;<code>locate add_members</code>&#8221; if not sure where to put it. If it complains when you try to run it, make sure its first line matches the python path shown on other scripts in the same dir.</p>
<p>Put <code>listgen.py</code> in a &#8220;scripts&#8221; directory in your Django project.</p>
<p>Put <code>listgen.sh</code> somewhere where root can get to it.</p>
<p>You&#8217;ll need to modify the list of mailing lists in each script, as well as the most important bit &#8211; the Django ORM queries in <code>listgen.py</code>. This will probably take quite a bit of testing and experimentation &#8211; make sure you&#8217;re working with test data until everything is humming &#8211; users will get righteously pissed if you break their lists.</p>
<p>You&#8217;ll also need to make sure the two scripts agree on the filesystem location for the output  (for the generated files that <code>listgen.py</code> creates and <code>listgen.sh</code> reads).  By default this will be a dir at <code>myproject/scripts/listgen</code>. You&#8217;ll need to edit the python path vars at the top of listgen.py.</p>
<p>During testing you&#8217;ll probably want to just run <code>listgen.py</code> and not <code>listgen.sh</code> &#8212; then you can study the text file output without actually altering the lists. When you&#8217;re confident <code>listgen.py</code> is doing the right thing, add the <code>listgen.sh</code> step.</p>
<p>Once it&#8217;s all working, create an alias for root that runs both scripts, something like:</p>
<pre>
alias schoollist='python /path/to/scripts/listgen.py; \
/bin/sh /root/scripts/for_customers/school/listgen.sh'</pre>
<p>You <em>could</em> set this up to run as a cron job, but there&#8217;s a good reason not to: Mailman keeps track of bad addresses with its bounce processing feature, which counts the number of times a message has bounced from a bad address and then unsubscribes that user and alerts the list administrator. If you run listgen.sh every night, every member will always be &#8220;new&#8221; as far as Mailman is concerned and bounce thresholds will never be reached.  You&#8217;ll never be informed when/if you have bad addresses in your Django system. To address this, I wrote a Django signal handler that sends me email whenever a User object has been updated. I then run the <code>schoollist</code> command whenever I know something has changed (after the system stabilizes this shouldn&#8217;t happen very often).</p>
<p><strong>The Scripts</strong></p>
<p><a href="http://birdhouse.org/software/wp-content/uploads/2009/09/add_members.txt">add_members</a> (copied from OS X Server&#8217;s Mailman distro, but works on any platform)<strong></strong></p>
<p><a href="http://birdhouse.org/software/wp-content/uploads/2009/09/listgen.py.txt">listgen.py</a> (put in a scripts dir inside your Django project)</p>
<p><a href="http://birdhouse.org/software/wp-content/uploads/2009/09/listgen.sh.txt">listgen.sh</a> (put somewhere root can access easily)<strong><br />
</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2009/09/listgen/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Django-Todo</title>
		<link>http://birdhouse.org/software/2008/09/django-todo/</link>
		<comments>http://birdhouse.org/software/2008/09/django-todo/#comments</comments>
		<pubDate>Sun, 14 Sep 2008 08:11:13 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=39</guid>
		<description><![CDATA[django-todo is a multi-user, multi-group task management and assignment system for the Django web application framework. Designed as a &#8220;pluggable&#8221; application ready to import into existing Django projects. You must already have a  login/authorization system installed and working in your project before installing django-todo. This version supports drag and drop task prioritization, email notification to [...]]]></description>
			<content:encoded><![CDATA[<p>django-todo is a multi-user, multi-group task management and assignment system for the <a href="http://www.djangoproject.com/">Django</a> web application framework.</p>
<p>Designed as a &#8220;pluggable&#8221; application ready to import into existing Django projects.</p>
<p>You must already have a  login/authorization system installed and working in your project before installing django-todo.</p>
<p>This version supports drag and drop task prioritization, email notification to task assignees, and lots more. Feedback welcome.</p>
<p>The code is <a href="http://code.google.com/p/django-todo/">hosted on Google Code</a> and can be checked out via svn.</p>
<p><span id="more-39"></span></p>
<p><strong>Screenshots</strong></p>

<a href='http://birdhouse.org/software/2008/09/django-todo/django-todo-edittask/' title='django-todo-edittask'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/django-todo-edittask-150x150.png" class="attachment-thumbnail" alt="django-todo-edittask" title="django-todo-edittask" /></a>
<a href='http://birdhouse.org/software/2008/09/django-todo/django-todo-listlists/' title='django-todo-listlists'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/django-todo-listlists-150x150.png" class="attachment-thumbnail" alt="django-todo-listlists" title="django-todo-listlists" /></a>
<a href='http://birdhouse.org/software/2008/09/django-todo/django-todo-login/' title='django-todo-login'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/django-todo-login-150x150.png" class="attachment-thumbnail" alt="django-todo-login" title="django-todo-login" /></a>
<a href='http://birdhouse.org/software/2008/09/django-todo/django-todo-mytasks/' title='django-todo-mytasks'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/django-todo-mytasks-150x150.png" class="attachment-thumbnail" alt="django-todo-mytasks" title="django-todo-mytasks" /></a>
<a href='http://birdhouse.org/software/2008/09/django-todo/incomplete/' title='incomplete'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/incomplete-150x150.png" class="attachment-thumbnail" alt="incomplete" title="incomplete" /></a>
<a href='http://birdhouse.org/software/2008/09/django-todo/datepicker/' title='datepicker'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/datepicker-150x150.png" class="attachment-thumbnail" alt="datepicker" title="datepicker" /></a>
<a href='http://birdhouse.org/software/2008/09/django-todo/main/' title='main'><img width="150" height="150" src="http://birdhouse.org/software/wp-content/uploads/2008/09/main-150x150.png" class="attachment-thumbnail" alt="main" title="main" /></a>

]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2008/09/django-todo/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Cleancats</title>
		<link>http://birdhouse.org/software/2008/04/cleancats/</link>
		<comments>http://birdhouse.org/software/2008/04/cleancats/#comments</comments>
		<pubDate>Wed, 23 Apr 2008 08:12:56 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=34</guid>
		<description><![CDATA[This is a very crude mass category deletion script for WordPress. I recently inherited a WordPress site with more than 6000 categories. After deleting a ton of old posts, we were left with more than 4000 unused categories, and I needed a way to remove them quickly. Couldn&#8217;t find a plugin up to the task, [...]]]></description>
			<content:encoded><![CDATA[<p>This is a very crude mass category deletion script for WordPress. I recently inherited a WordPress site with more than 6000 categories. After deleting a ton of old posts, we were left with more than 4000 unused categories, and I needed a way to remove them quickly. Couldn&#8217;t find a plugin up to the task, so wrote this. Very crude, but effective. Not a plugin, but a small WP companion script. See instructions in the comment at top of script.</p>
<p><strong><a href='http://birdhouse.org/software/wp-content/uploads/2008/04/cleancatsphp.txt'>Download Cleancats</a></strong></p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2008/04/cleancats/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WP-Create</title>
		<link>http://birdhouse.org/software/2008/04/wp-create/</link>
		<comments>http://birdhouse.org/software/2008/04/wp-create/#comments</comments>
		<pubDate>Sun, 13 Apr 2008 21:26:19 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=28</guid>
		<description><![CDATA[WP-Create has been folded into the WordPress Mass Management Tools kit.]]></description>
			<content:encoded><![CDATA[<p>WP-Create has been folded into the <a href="http://birdhouse.org/software/2010/08/wordpress-mass-management/">WordPress Mass Management Tools</a> kit.</p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2008/04/wp-create/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>WP Workflow Docs</title>
		<link>http://birdhouse.org/software/2008/01/wp-workflow-docs/</link>
		<comments>http://birdhouse.org/software/2008/01/wp-workflow-docs/#comments</comments>
		<pubDate>Wed, 23 Jan 2008 05:30:33 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=25</guid>
		<description><![CDATA[A dirt-simple WordPress plugin that lets you provide workflow docs to your authors and editors from within the WP admin interface. Instructions: Unpack this folder to your plugins directory and activate it. Click on the Dashboard and you&#8217;ll see a new tab called &#8220;NGNO Docs.&#8221; Within that, you&#8217;ll find documentation I wrote for my authors [...]]]></description>
			<content:encoded><![CDATA[<p>A dirt-simple WordPress plugin that lets you provide workflow docs to your authors and editors from within the WP admin interface.</p>
<p><span id="more-25"></span></p>
<p><strong>Instructions:</strong> Unpack this folder to your plugins directory and activate it. Click on the Dashboard and you&#8217;ll see a new tab called &#8220;NGNO Docs.&#8221; Within that, you&#8217;ll find documentation I wrote for <em>my</em> authors  for  <em>one particular site</em>. In order for this to be useful, you&#8217;ll need to replace  the HTML it contains with your  own by editing the file <code>docs.html</code>.</p>
<p>You&#8217;ll want to remove the image files I&#8217;ve included and, if desired, add your own. Note: I found it tricky to get image paths working &#8211; you&#8217;ll need to embed images like this:</p>
<p><code>img src="../wp-content/plugins/wp-workflow-docs/more-result.gif"</code></p>
<p>You can change the plugin name and tab name by editing the plugin file itself &#8211; it&#8217;s a tiny plugin and it will be pretty obvious where to do this in the file. Then de-activate and re-activate it to see the new tab name.</p>
<p><strong><a href="http://birdhouse.org/software/wp-content/uploads/2008/04/wp-workflow-docs.zip">Download wp-workflow-docs</a></strong></p>
<p>Contributions / improvements welcome!</p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2008/01/wp-workflow-docs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FuturePost</title>
		<link>http://birdhouse.org/software/2007/12/futurepost/</link>
		<comments>http://birdhouse.org/software/2007/12/futurepost/#comments</comments>
		<pubDate>Tue, 18 Dec 2007 05:27:40 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=24</guid>
		<description><![CDATA[A WordPress plugin aimed primarily at events sites, where you want to be able to timestamp posts in the future but have them appear immediately (by default, WordPress will not display a future timestamped post until its go-live date rolls around). This plugin sets the post_status field to &#8220;publish&#8221; rather than &#8220;future&#8221; when publishing a [...]]]></description>
			<content:encoded><![CDATA[<p>A WordPress plugin aimed primarily at events sites, where you want to be able to timestamp posts in the future but have them appear immediately (by default, WordPress will not display a future timestamped post until its go-live date rolls around). This plugin sets the post_status field to &#8220;publish&#8221; rather than &#8220;future&#8221; when publishing a post, even if its timestamp is in the future. Written by Ryan Boren &#8211; I&#8217;m just hosting it.</p>
<h4><span id="more-24"></span></h4>
<p><strong>Instructions:</strong> Place future-post.php in your plugins directory and activate it. Write a post with a future timestamp and hit publish. Notice that it goes live on your site immediately.</p>
<p><strong>Note:</strong> This seemingly simple plugin was graciously written by the magical <a title="boren.nu" href="http://boren.nu/">Ryan Boren</a> when I was facing a deadline. He doesn&#8217;t have time to maintain/host it, so I agreed to.</p>
<p><strong><a href="http://wordpress.org/extend/plugins/the-future-is-now/">Download version 1.0</a></strong></p>
<p>Contributions / improvements welcome!</p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2007/12/futurepost/feed/</wfw:commentRss>
		<slash:comments>78</slash:comments>
		</item>
		<item>
		<title>gpx2ipod</title>
		<link>http://birdhouse.org/software/2007/11/gpx2ipod/</link>
		<comments>http://birdhouse.org/software/2007/11/gpx2ipod/#comments</comments>
		<pubDate>Sun, 11 Nov 2007 08:38:57 +0000</pubDate>
		<dc:creator>shacker</dc:creator>
				<category><![CDATA[Geo]]></category>

		<guid isPermaLink="false">http://birdhouse.org/software/?p=19</guid>
		<description><![CDATA[Designed for Mac users with an iPod but no PDA who want to do paperless geocaching complete with descriptions, log entries and hints. Uses the iPod&#8217;s &#8220;Notes&#8221; feature to display complete cache description pages. gpx2ipod is built on top of gpsbabel. Note: I&#8217;m not likely to continue development of gpx2ipod, for two reasons: 1) MacCaching [...]]]></description>
			<content:encoded><![CDATA[<p>Designed for Mac users with an iPod but no PDA who want to do paperless geocaching complete with descriptions, log entries and hints. Uses the iPod&#8217;s &#8220;Notes&#8221; feature to display complete cache description pages. gpx2ipod is built on top of <a title="GPSBabel: convert, upload, download data from GPS and Map programs" href="http://www.gpsbabel.org/">gpsbabel</a>.</p>
<p><strong>Note:</strong> I&#8217;m not likely to continue development of gpx2ipod, for two reasons: 1) <a href="http://www.maccaching.com/">MacCaching</a> finally grew the ability to store cache metadata in the iPod&#8217;s &#8220;Notes&#8221; feature rather than in the address book (which was the whole impetus behind my developing gpx2ipod to begin with), and 2) I switched to a <a href="https://buy.garmin.com/shop/shop.do?pID=11022">Garmin Colorado</a> a few months ago, which displays description, logs, and hints directly in the GPS. I no longer need to offload that data to the iPod, so my itch has been scratched. Do with this software what you will!</p>
<p><span id="more-19"></span></p>
<h2>Overview</h2>
<p>Mac-based paperless caching for people who own an iPod but not a PDA.<br />
Batch-converts a pile of .gpx files to plain text for use with the iPod&#8217;s &#8220;Notes&#8221; feature.<br />
<em>Super</em>-fast &#8212; cut your geocaching prep time to a few minutes.</p>
<p>gpx2ipod handles both individual and Pocket Query (multiple-cache).gpx files.<br />
Cache files will display alphabetically on the iPod for easy access in the field.</p>
<p>gpx2ipod can inject generated text files directly into your iPod (most users) or into a local &#8220;output&#8221; folder (you might not have an iPod but might still want the text files for other purposes).</p>
<p>gpx2ipod is a Terminal application (shell script), but can be run painlessly with a double-click &#8212; no shell experience required.</p>
<p>gpx2ipod is available either with or without the <a title="GPSBabel: convert, upload, download data from GPS and Map	programs" href="http://www.gpsbabel.org/">gpsbabel</a> command-line binary bundled. gpsbabel is licenced under the GPL, and thus so is gpx2ipod. See LICENSE for info.</p>
<h2>Who this is for</h2>
<ul>
<li>You&#8217;re a Mac user who has an iPod but not a PDA.</li>
<li>You want to do paperless geocaching.</li>
<li>You want complete descriptions, log entries and hints while out on the trail.</li>
<li>Your iPod has this great &#8220;Notes&#8221; feature, but you haven&#8217;t found any OS X software to get GPX files into a format you can read in the iPod&#8217;s Notes reader. <a title="Welcome « MacCaching Free GeoCache Manager for Mac OS X" href="http://www.maccaching.com/">MacCaching</a> is wonderful, but it outputs to the iPod&#8217;s &#8220;Contacts&#8221; system, which means no logs and no choice of whether to encrypt hints. It also intermingles hundreds of geocache data files with your existing pile of Contacts &#8211; very messy. GPSBabel+ has a similar &#8220;Export to v-card feature&#8221; with similar problems. The iPod&#8217;s &#8220;Notes&#8221; feature seems a much better choice to me.</li>
</ul>
<h2>Requirements</h2>
<p>Mac OS X. This script will not run on Windows or Linux (with modifications, gpx2ipod could be made to run under <a title="Cygwin Information and Installation" href="http://www.cygwin.com/">Cygwin</a> or Linux).</p>
<p>A copy of the free and open source <a href="http://www.gpsbabel.org/">gpsbabel</a> (<strong>version 1.3.4 or higher</strong>). Thanks to all contributors to gpsbabel for your hard work on this amazing workhorse! Huge thanks also to Robert Lipe, who added gpsbabel&#8217;s ability to split multi-location files into individual files, so gpx2ipod wouldn&#8217;t be stuck with the 99-split limitation in the Unix &#8220;<code>cut</code>&#8221; command.</p>
<p>An iPod with Storage Mode enabled. Storage Mode lets you use your iPod as a hard drive. If your iPod does not show up as disk volume on the Desktop when plugged in, go to iTunes and select &#8220;Enable disk use,&#8221; then click Apply.</p>
<h2>Installation</h2>
<p>Unzip this folder and drag it into your Applications folder (or wherever you like). Then drag the gpx2ipod <strong>folder</strong> icon from your Applications folder into your Dock for quick access.</p>
<ul>
<li><strong>If you downloaded gpx2ipod <em>with</em> gpsbabel:</strong>You&#8217;re good to go! Skip to Usage.</li>
<li><strong>If you downloaded gpx2ipod <em>without</em> gpsbabel:</strong>gpx2ipod requires the <a title="GPSBabel: convert, upload, download data from GPS and Map programs" href="http://www.gpsbabel.org/">gpsbabel</a> command line application, (<strong>version 1.3.4 or higher</strong>). When you install GPSBabel (probably into your Applications folder), you&#8217;ll see two files: GPSBabel+, which is a graphical application, and gpsbabel itself, which is a command-line application. There are two ways to make gpsbabel available to gpx2ipod:<strong>The easy way</strong>: Select the file &#8220;gpsbabel&#8221; in that folder and copy it (don&#8217;t move it!) to the <code>bin</code> folder inside the gpx2ipod folder.<strong>The hard (but better) way</strong>: If you&#8217;re comfortable at the command line and prefer not to duplicate the binary, you can create a symlink instead, for example:
<p><code>ln -s "/Applications/GPS Babel/gpsbabel" /Users/[yourlogin]/Desktop/gpx2ipod/bin/gpsbabel</code></p>
<p>Modify that command to match your setup. Note: Mac aliases won&#8217;t work &#8211; you&#8217;ll need a proper symlink.</li>
</ul>
<h2>Usage</h2>
<ol>
<li>Download .gpx files from <a title="Geocaching - The Official Global GPS Cache Hunt Site" href="http://www.geocaching.com/">geocaching.com</a> and move or copy them into the &#8220;input&#8221; folder. You can mix and match individual .gpx files and Pocket Query (multi-cache) files in the same input folder. gpx2ipod includes a &#8220;samples&#8221; folder for testing &#8211; copy some or all of these into the &#8220;input&#8221; folder and let &#8216;er rip.</li>
<li>Double-click the <code>gpx2ipod</code> &#8220;squid&#8221; and let it do its thing.</li>
<li style="list-style-type: none; list-style-image: none; list-style-position: outside;">
<ul>
<li>If this is the first time you&#8217;ve run gpx2ipod, you&#8217;ll be asked to set a few preferences, such as whether you want to include log files, whether to encrypt hints, whether to copy the original .gpx files into an &#8220;archive&#8221; folder, and (most importantly) the system path to your iPod (the script will let you choose your iPod from a list). For the yes/no questions, use 0 for &#8220;no&#8221; and 1 for &#8220;yes.&#8221;</li>
<li>If the gpsbabel binary can&#8217;t be found, or is an old version, the script will tell you and then halt.</li>
<li>If the iPod you selected during Setup can&#8217;t be found, you&#8217;ll be given the option to mount it now, or, alternatively, to have generated files sent to an &#8220;output&#8221; folder instead.</li>
</ul>
</li>
<li>When everything&#8217;s in order, gpx2ipod will crunch through all those .gpx files, spit out text files optimally formatted for iPod use, and copy them into the Notes folder on your iPod. On future runs of gpx2ipod, your last-used preferences will be displayed as the script launches. Hit [Return] to accept the settings, or C to change them.</li>
<li>Go geocaching! You&#8217;ll find the cache entries on the iPod in Extras &gt; Notes.</li>
</ol>
<h2>Limitations</h2>
<p>This version does not load .gpx files into your GPSr &#8211; it just generates text files for use on the iPod. A future version may load the same files into the GPSr.</p>
<h2>History</h2>
<p>v1.3 &#8211; 9/15/2007<br />
Now retains character formatting for international character sets. Default is UTF-8, but any character set supported by gpsbabel can be selected in preferences. Tested against Swedish .gpx samples (see GCY7XZ-Swedish_chars.gpx in Samples folder). Many thanks to Bengt Bäverman for excellent contributions on this feature.</p>
<p>v1.2 &#8211; 9/15/2007<br />
Bug fix for other cache name characters that would generate illegal filenames.</p>
<p>v1.1 &#8211; 9/08/2007<br />
No longer generates errors when encountering caches with slashes in their names. Now works properly when installed in a path containing a space (such as &#8220;/Applications/GPS Apps&#8221;).</p>
<p>v1.0 &#8211; 8/03/2007<br />
Renamed project to &#8220;gpx2ipod.&#8221; Now requires gpsbabel 1.3.4 or higher, taking advantage of new gpsbabel &#8220;splitoutput&#8221; feature in order to overcome the <code>cut</code> command&#8217;s 99-split limit (thanks Robert Lipe!). Now double-clickable &#8211; much better user experience. Now copies generated files directly to iPod. Now stores user preferences in a config file. Now optionally copies original .gpx files into an &#8220;archive&#8221; folder for future use.</p>
<p>v0.2 &#8211; 7/17/2007<br />
Now handles both individual .gpx files and Pocket Query (multiple-cache) .gpx files.</p>
<p>v0.1 &#8211; 7/14/2007<br />
Original version</p>
<h2>Download</h2>
<p><strong><a href="http://birdhouse.org/software/wp-content/uploads/2008/04/gpx2ipod_min.dmg">Download without GPSBabel</a></strong></p>
<p><strong><a href="http://birdhouse.org/software/wp-content/uploads/2008/04/gpx2ipod.dmg">Download with GPSBabel</a></strong></p>
]]></content:encoded>
			<wfw:commentRss>http://birdhouse.org/software/2007/11/gpx2ipod/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
