<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Dave Dash</title>
 <link href="http://davedash.com/tag/spindrop/atom.xml" rel="self"/>
 <link href="http://davedash.com/tag/spindrop"/>
 <updated>2012-01-17T21:54:19-08:00</updated>
 <id>http://davedash.com/</id>
 <author>
   <name>Dave Dash</name>
   <email>dd+atom1@davedash.com</email>
 </author>

 
 <entry>
   <title>Alphabetical sorting in Sphinx</title>
   <link href="http://davedash.com/2010/04/21/alphabetical-sorting-in-sphinx/"/>
   <updated>2010-04-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/04/21/alphabetical-sorting-in-sphinx</id>
   <content type="html">&lt;p&gt;Sphinx 0.9.9 is great at searching full text, but treating actual strings as attributes takes some work.&lt;/p&gt;

&lt;p&gt;Initially I employed the strategy of indexing my full text fields &lt;em&gt;and&lt;/em&gt; storing them as attributes.  E.g.:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sql_query = SELECT name, name AS name_ord FROM documents
sql_attr_str2ordinal = name_ord
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This stores each attribute in lexical order.  Meaning if your name's are Apple, Aardvark, Button, Choco-room they would be given the ordinal 2, 1, 3, 4 respectively.&lt;/p&gt;

&lt;p&gt;However, this is case-insensitive.  So trying this approach:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sql_query = SELECT name, UPPER(name) AS name_ord FROM documents
sql_attr_str2ordinal = name_ord
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Will allow for case-insensitive alphabetical sorting in Sphinx.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Test Driven Confidence</title>
   <link href="http://davedash.com/2010/04/20/test-driven-confidence/"/>
   <updated>2010-04-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/04/20/test-driven-confidence</id>
   <content type="html">&lt;p&gt;If you're already testing your web applications, you can skip this post.&lt;/p&gt;

&lt;p&gt;One of the bugs I am working for &lt;a href=&quot;https://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; on involves porting a small, but moderately complicated checkbox from our PHP site and rewriting it for Django.&lt;/p&gt;

&lt;p&gt;I decided to look at the existing implementation and found it to not work correctly at all.  This was frustrating, especially since I verified that my own code worked, and that QA verified that it worked as well.&lt;/p&gt;

&lt;p&gt;This is frustrating on many levels.  Chances are some minor assumption I made changed, and thus broke this functionality.  Discovering regressions is never fun, and fixing them is can be long and tedious if you can't automatically verify that everything is working correctly.&lt;/p&gt;

&lt;p&gt;Lucky for me, coming up with tests is easy, you just do what you would do to verify the code satisfies the requirements and then code it.  Sometimes the tests can take longer than writing the actual code, but ultimately you can ship with confidence.  You can be confident that your feature won't break in the future without immediate notice, and you can be confident that your new code won't break anything else.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Finding the most common Firefox issues</title>
   <link href="http://davedash.com/2010/03/18/finding-the-most-common-firefox-issues/"/>
   <updated>2010-03-18T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/03/18/finding-the-most-common-firefox-issues</id>
   <content type="html">&lt;p&gt;Cheng Wang of the Mozilla Support team, a few months back, decided to present on some design ideas for &lt;a href=&quot;http://support.mozilla.com/en-US/kb/&quot;&gt;Firefox Support&lt;/a&gt;.  One of the issues he noted was that there are a lot of repeated issues and that it would be useful to group them.  Grouping them lets you see how often something occurs, and secondly let's you see how urgent it might be.&lt;/p&gt;

&lt;p&gt;Luckily grouping and clustering text is something computers can do.  So I wrote &lt;a href=&quot;http://github.com/davedash/SUMO-issues&quot;&gt;this utility&lt;/a&gt; that does just that.&lt;/p&gt;

&lt;p&gt;I ran this script over a sampling of data from the last week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firefox won't start after update. (65 related issues)

&lt;ul&gt;
&lt;li&gt;5.6:  Firefox updated, Gmail not delivering mails&lt;/li&gt;
&lt;li&gt;5.6:  How to change My Profile when Firefox won't load?&lt;/li&gt;
&lt;li&gt;7.5:  Once I close firefox, cannot start firefox again except system restart&lt;/li&gt;
&lt;li&gt;5.6:  When intalling updates Firefox uninstalls itself&lt;/li&gt;
&lt;li&gt;16.8:  firefox won't start after update 3.6&lt;/li&gt;
&lt;li&gt;11.2:  Upgraded to Firefox 3.6 and now it won't start&lt;/li&gt;
&lt;li&gt;14.9:  Firefox won't start with most extensions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;How do I add a bookmark to more than one folder? (64 related issues)

&lt;ul&gt;
&lt;li&gt;8.9:  How do I get my bookmarks on the bookmarks toolbar to show up as an icon only with no text?&lt;/li&gt;
&lt;li&gt;7.5:  Bookmarks lost after upgrade and cannot save new bookmarks&lt;/li&gt;
&lt;li&gt;7.5:  why do i have to add the .com now to addy's?&lt;/li&gt;
&lt;li&gt;8.7:  When I open sidebar to edit bookmarks, I only see the folder for Bookmarks Toolbar. I do not see a folder just called Bookmarks nor do I see my list of bookmarks, that separately appear under bookmarks menu at top of screen&lt;/li&gt;
&lt;li&gt;7.5:  All my impoted bookmarks go to the same webpage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;How do I remove the \ask toolbar\&quot;?&quot; (50 related issues)

&lt;ul&gt;
&lt;li&gt;14.9:  How do I remove an unwanted toolbar?&lt;/li&gt;
&lt;li&gt;5.6:  how to remove temporary video files from computer&lt;/li&gt;
&lt;li&gt;7.5:  I have no Toolbars or searchbar and i cant bring them back&lt;/li&gt;
&lt;li&gt;7.5:  nowhere says how to REMOVE a toolbar - only how to add or modify one&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;not able to open youtube videos (45 related issues)

&lt;ul&gt;
&lt;li&gt;5.6:  Cannot open bookmark/history sidebar&lt;/li&gt;
&lt;li&gt;5.6:  After working well for years Firefox will now not open&lt;/li&gt;
&lt;li&gt;6.7:  opening bookmarks do not open in new tab&lt;/li&gt;
&lt;li&gt;5.6:  I can't watch videos on youtube with firefox, but on internet explorer i can&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I cannot download Firefox 3.6.  I've tried erasing the download file.  I cannot get beyond logging out of Firefox. (44 related issues)

&lt;ul&gt;
&lt;li&gt;8.4:  when downloading files firefox download manager will freeze and i will have to start over the file download&lt;/li&gt;
&lt;li&gt;5.6:  Firefox will not let me download anything! Can someone help?&lt;/li&gt;
&lt;li&gt;6.3:  cannot download epixHD.com: not compatible with firefox 3.6&lt;/li&gt;
&lt;li&gt;5.0:  Several tabs are coming up when i try to downloads things&lt;/li&gt;
&lt;li&gt;5.0:  Firefox wont open since I downloaded the 3.6 update.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;The number on the right of the related issue is a score of how strongly it relates to the main issue.&lt;/p&gt;

&lt;p&gt;The full sample is 352 clusters from an original 3000+ issues.  That's a lot less stuff to go through.  We can tune this to have either less clusters, and more related issues in a cluster, or we can make more clusters of issues and that might result in more accuracy.&lt;/p&gt;

&lt;p&gt;Despite the inaccuracy of clustering we can make some general observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firefox not starting is a big issue.&lt;/li&gt;
&lt;li&gt;Bookmarks are either confusing or broken.&lt;/li&gt;
&lt;li&gt;People don't like toolbars&lt;/li&gt;
&lt;li&gt;Opening things is hard&lt;/li&gt;
&lt;li&gt;Downloading things or Firefox is hard&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Hopefully we can fine tune these reports and have them run regularly... maybe automatically posting to Tumblr?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A few weeks in Chrome</title>
   <link href="http://davedash.com/2010/03/17/a-few-weeks-in-chrome/"/>
   <updated>2010-03-17T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/03/17/a-few-weeks-in-chrome</id>
   <content type="html">&lt;p&gt;A number of weeks ago I got annoyed with Firefox and decided to use Chrome for a while.  This reminded me of the olden days where I used Netscape for a while, and then IE6 came out, and then Phoenix came out all the while I'd keep switching to the newest shiniest thing (note: I'm not sure about the timeline of all the browsers either).&lt;/p&gt;

&lt;p&gt;My browser of choice since Firefox was released has been Firefox.  For some time - nothing shiny in browser-land was coming out.  Little UI things in Safari kept me away (and the lack of extensions), but Chrome finally showed promise.  WebKit, out of process plugins, process separated tabs and now extensions.  This was great.&lt;/p&gt;

&lt;p&gt;I immediately felt like I was going to really love Chrome, and be &lt;em&gt;that&lt;/em&gt; guy at the office (I work at Mozilla) who insists on using Chrome (just like I was &lt;em&gt;that&lt;/em&gt; guy at Yahoo! who used Google for everything).  I also wanted to answer the question as to why so many people really like Firefox in spite of Chrome's amazing speed -- even many Googlers will admit to preferring Firefox.&lt;/p&gt;

&lt;p&gt;Overall I'm happy with Chrome, but I'm switching back to Firefox for now.  Here's some things I observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&quot;&lt;code&gt;/&lt;/code&gt;&quot; in Firefox let's you search.  Which to me seems more natural than Ctrl-F.  I am pleased that Chrome supports a lot of Firefox's shortcuts, like &lt;code&gt;Cmd-1..9&lt;/code&gt; for switching tabs, or &lt;code&gt;Cmd-Shift-T&lt;/code&gt; for reopening a closed tab.&lt;/li&gt;
&lt;li&gt;No titlebar... I kind of miss it.&lt;/li&gt;
&lt;li&gt;XML is way easier to work with in Firefox.  It's collapsable and always looks pretty.&lt;/li&gt;
&lt;li&gt;Certain sites don't work well in Chrome, like Rypple or the AmericanExpress web site.  Rypple surprisingly enough is built using the Google Web Toolkit.  I really wish their was a &quot;FirefoxTab&quot; that would open certain sites in Firefox instead.&lt;/li&gt;
&lt;li&gt;There's a number of Jetpacks and Extensions that only exist for Firefox or they are severely lacking.

&lt;ul&gt;
&lt;li&gt;The Jetpacks for Mozilla's Bugzilla instance are awesome.&lt;/li&gt;
&lt;li&gt;The Delicious and AdBlock extensions on Chrome aren't nearly as good as the ones for Firefox.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Firebug is much better than the Chrome developer tools.  For example, you can adjust css values instantly.&lt;/li&gt;
&lt;li&gt;Extensions die... and don't come back without restart and appear to never have been installed unless you remember them crashing.&lt;/li&gt;
&lt;li&gt;AwesomeBar (the location bar in Firefox) queries your history much better than the OmniBar (the location bar in Chrome)

&lt;ul&gt;
&lt;li&gt;At first I thought this was because of a sparse history, but after several weeks I still have a hard time finding sites I've been to.&lt;/li&gt;
&lt;li&gt;Chrome will show you a handful of results, and then let you know there are more results, but that takes you to a new screen which is a jarring UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Chrome can be slow.  The extensions can take a while, and even switching between tabs can be slow.  At this point startup time can be a moot point.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Overall this was a healthy exercise, since I really like to be up on new browsers, and Chrome really seems like it's can be a good browser for many people.  I'll probably try it again after the next major Chrome update.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Making our tests run thrice as fast</title>
   <link href="http://davedash.com/2010/03/16/making-our-tests-run-thrice-as-fast/"/>
   <updated>2010-03-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/03/16/making-our-tests-run-thrice-as-fast</id>
   <content type="html">&lt;p&gt;I've written a faster version of &lt;a href=&quot;http://github.com/jbalogh/test-utils/blob/c4c31905a95e59dcc8919c1030b23848ad7fbca6/test_utils/__init__.py#L57&quot;&gt;TransactionTestCase&lt;/a&gt; and packaged it with &lt;a href=&quot;http://github.com/jbalogh/test-utils&quot;&gt;test_utils&lt;/a&gt;.  It's mysql specific since it relies on &lt;code&gt;SET FOREIGN_KEY_CHECKS=0&lt;/code&gt; to flush the database.&lt;/p&gt;

&lt;p&gt;The long story...&lt;/p&gt;

&lt;!-- more--&gt;


&lt;h3&gt;Why speed matters&lt;/h3&gt;

&lt;p&gt;We're closing in on 300 tests for &lt;a href=&quot;http://github.com/jbalogh/zamboni/&quot;&gt;Zamboni&lt;/a&gt;.  As of yesterday, to run our entire test suite it would have taken approximately 5 minutes.  If you run tests before code-reviews, during a code-review, and before you push to master - you've spent about 15 minutes doing tests for a single feature or bug-fix.  We have about 5 developers, so this cycle happens many times in a work day.  In that time many sandwiches can be made and consumed.&lt;/p&gt;

&lt;p&gt;Even shortcuts, like running a subset of tests will only go so far, and ultimately we do want to validate that all our tests pass for any code-change.&lt;/p&gt;

&lt;h3&gt;Testing Sphinx search with &lt;code&gt;TransactionTestCase&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Django recently sped up testing by running tests in a transaction.  However, this means that data never gets committed to the database and therefore external tools, like the Sphinx indexer, will never see any of that data.  So we resort to &lt;code&gt;TransactionTestCase&lt;/code&gt; which &lt;em&gt;will&lt;/em&gt; commit the data.&lt;/p&gt;

&lt;p&gt;Unfortunately &lt;code&gt;TransactionTestCase&lt;/code&gt; is painfully slow.  The accepted practice is to only use &lt;code&gt;TestCase&lt;/code&gt; if you want your tests to be fast.  So, I decided to complain to &lt;a href=&quot;http://blog.ianbicking.org/&quot;&gt;one of our new hires&lt;/a&gt; and he and I decided to tinker in mysql to figure out what was slow.  We discovered the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;delete from [table] is slow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;truncate [table] is slow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;... unless you &lt;code&gt;SET FOREIGN_KEY_CHECKS=0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;So we decided we should do our own tear down.  After some tinkering with &lt;code&gt;cProfiler&lt;/code&gt; I discovered that &lt;code&gt;TransactionTestCase&lt;/code&gt; does a (slow) database &lt;code&gt;flush&lt;/code&gt; on setup for a test case.  This wouldn't do.&lt;/p&gt;

&lt;h3&gt;Making our own &lt;code&gt;TransactionTestCase&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;I decided to make our own &lt;code&gt;TransactionTestCase&lt;/code&gt; and it would just run &lt;code&gt;SET FOREIGN_KEY_CHECKS=0&lt;/code&gt; and &lt;code&gt;TRUNCATE&lt;/code&gt; on each table at tear down time.  It would also not do a &lt;code&gt;flush&lt;/code&gt; on set up.&lt;/p&gt;

&lt;p&gt;We write our tests with the idea that they clean up after themselves.  Rather than having them cleanup after the last test.  This is a requirement for us since &lt;code&gt;django-nose&lt;/code&gt; doesn't reorder tests (nor should it) and a standard &lt;code&gt;django.test.TestCase&lt;/code&gt; assumes a clean database.&lt;/p&gt;

&lt;p&gt;Looking at a single test &lt;code&gt;test_sphinx_indexer&lt;/code&gt;, using &lt;code&gt;django.test.TransactionTestCase&lt;/code&gt; took ~30 seconds.  Using our new &lt;code&gt;TransactionTestCase&lt;/code&gt; it takes ~4 seconds!&lt;/p&gt;

&lt;h3&gt;Fast tests are good&lt;/h3&gt;

&lt;p&gt;We can now run our 275 tests in ~100 seconds versus the ~300 seconds it used to take.  Furthermore, skipping our sphinx tests (which are the only tests that use &lt;code&gt;TransactionTestCase&lt;/code&gt;) only saves us ~10seconds.  That's not a lot of overhead for better coverage.&lt;/p&gt;

&lt;p&gt;This took me the better part of a day, but solving this now, means we're going to more often than not run our sphinx tests all the time rather than skip them.  Our QA team will assure you that search is probably the most regression prone part of our site, so running these tests are vital to quality.&lt;/p&gt;

&lt;p&gt;If you need to use &lt;code&gt;TransactionTestCase&lt;/code&gt; in mysql, &lt;a href=&quot;http://github.com/jbalogh/test-utils&quot;&gt;give ours a try&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>&lambda;^2: safely doing class based views in Django</title>
   <link href="http://davedash.com/2010/03/09/2-safely-doing-class-based-views-in-django/"/>
   <updated>2010-03-09T00:00:00-08:00</updated>
   <id>http://davedash.com/2010/03/09/2-safely-doing-class-based-views-in-django</id>
   <content type="html">&lt;p&gt;When I started rewriting the API for &lt;a href=&quot;https://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt;, my views were mostly the same: get some data and render it as either JSON or XML.  I also wanted all my API methods to take an &lt;code&gt;api_version&lt;/code&gt; parameter, so I decided class based views would be best.  This way my classes could just inherit from a base class.&lt;/p&gt;

&lt;p&gt;To do this I had to implement a &lt;a href=&quot;http://github.com/davedash/zamboni/blob/b5a147820840e66b542691e7239f15eccdebeec9/apps/api/views.py#L39&quot;&gt;&lt;code&gt;__call__&lt;/code&gt; method&lt;/a&gt;.  This works fine, except I wanted to store things into the class -- after all the whole point of my use of classes was to keep the code a bit more compact, and cleaner.  So, why pass the api_version around everywhere?  Unfortunately thread-safety comes to play, and you need a separate instance of your class for each request.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;&amp;lambda;&lt;/h3&gt;

&lt;p&gt;Django's &lt;code&gt;urlpatterns&lt;/code&gt; expects a callable object.  So you can't give it an instance of &lt;code&gt;AddonDetailView()&lt;/code&gt;.  But you could give it a callable that creates an instance of &lt;code&gt;AddonDetailView()&lt;/code&gt; and passes it &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt;.  Luckily python has &lt;code&gt;lambda&lt;/code&gt; functions.  You can &lt;a href=&quot;http://github.com/davedash/zamboni/blob/b5a147820840e66b542691e7239f15eccdebeec9/apps/api/urls.py#L10&quot;&gt;note how we solved that in our &lt;code&gt;urlpatterns&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;&amp;lambda; &amp;lambda;&lt;/h3&gt;

&lt;p&gt;But wrapping all your urls with &lt;code&gt;lambda&lt;/code&gt; is tedious and remembering to pass &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; is error prone.&lt;/p&gt;

&lt;p&gt;So let's make a &lt;code&gt;lambda&lt;/code&gt; function that returns... a &lt;code&gt;lambda&lt;/code&gt; function that &lt;a href=&quot;http://github.com/davedash/zamboni/blob/609ec5467dd6db6a6647f375e95abced5203a1b2/apps/api/urls.py#L9&quot;&gt;turns an instance of our class into a callable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can now return to coding and not think about thread safety.&lt;/p&gt;

&lt;p&gt;&amp;lambda;&amp;lambda;&amp;lambda;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>django-fixture-magic: Testing issues with real data.</title>
   <link href="http://davedash.com/2010/03/05/django-fixture-magic-testing-issues-with-real-data/"/>
   <updated>2010-03-05T00:00:00-08:00</updated>
   <id>http://davedash.com/2010/03/05/django-fixture-magic-testing-issues-with-real-data</id>
   <content type="html">&lt;p&gt;I just released &lt;a href=&quot;http://github.com/davedash/django-fixture-magic&quot;&gt;Fixture Magic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When dealing with legacy data, you'll run into all kinds of edge cases.  Perhaps, an object might not display correctly unless it has the right parameters, or if it has null parameters it might not display at all.  So when testing &lt;a href=&quot;http://djangoproject.com/&quot;&gt;Django&lt;/a&gt;, it's nice to actually use non-dummy data.&lt;/p&gt;

&lt;p&gt;Luckily Django has a way of pulling real data out of your database using &lt;code&gt;django.core.serializers&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from addons.models import Addon
a = Addon.objects.get(id=3615)
from django.core.serializers import serialize
jsonize = lambda a: serialize(&quot;json&quot;, a, indent=4)
jsonize([a])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This solution runs well in a Django shell and can be lots of fun for the whole family... until things get complicated.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Serializing alone isn't enough.&lt;/h3&gt;

&lt;p&gt;Serializing a fixture with foreign keys means you'll have an un-loadable fixture unless you serialize the dependent fixtures.  Even for one or two foreign keys, this can be a pain.  For &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt;, we have a spidery-web of dependencies: &lt;code&gt;File&lt;/code&gt;s need a &lt;code&gt;Version&lt;/code&gt; which needs an &lt;code&gt;Addon&lt;/code&gt; which need &lt;code&gt;Translation&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Thus begat the &lt;code&gt;dump_object&lt;/code&gt; management command.  Give it an app, model name and a &lt;code&gt;pk&lt;/code&gt; and it will give you not only a serialized JSON of that object, but all the objects that it requires.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./manage.py dump_object files.file 64874 64876 &amp;gt; my_new_fixture.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This looks for the &lt;code&gt;File&lt;/code&gt; model in the &lt;code&gt;files&lt;/code&gt; app and pulls out of the database &lt;code&gt;File&lt;/code&gt;s instances with &lt;code&gt;pk&lt;/code&gt;s of &lt;code&gt;64874&lt;/code&gt; and &lt;code&gt;64876&lt;/code&gt;.  It then recursively searches for any required objects.&lt;/p&gt;

&lt;h3&gt;Too much serial&lt;/h3&gt;

&lt;p&gt;If you create a lot of fixtures, you'll eventually have overlapping serialized objects.  In &lt;code&gt;addons.mozilla.org&lt;/code&gt; we have &lt;code&gt;Addon&lt;/code&gt;s, &lt;code&gt;Version&lt;/code&gt;s (which depend on &lt;code&gt;Addon&lt;/code&gt;s) and &lt;code&gt;AddonCategory&lt;/code&gt;s (which depend on &lt;code&gt;Addon&lt;/code&gt;s and &lt;code&gt;Category&lt;/code&gt;s).  If we wanted to get serialize a specific &lt;code&gt;Addon&lt;/code&gt;, it's dependent &lt;code&gt;Version&lt;/code&gt;s and &lt;code&gt;AddonCategory&lt;/code&gt;s it makes sense to start with &lt;code&gt;dump_object&lt;/code&gt;ing the related &lt;code&gt;Version&lt;/code&gt; and then &lt;code&gt;dump_objecting&lt;/code&gt; the &lt;code&gt;AddonCategory&lt;/code&gt;.  Both &lt;code&gt;dump_object&lt;/code&gt; commands will fetch the &lt;code&gt;Addon&lt;/code&gt; in question, resulting in duplicated data.&lt;/p&gt;

&lt;p&gt;To combat this we can use &lt;code&gt;merge_fixtures&lt;/code&gt; to dedupe our fixtures:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./manage.py dump_object versions.version 64874 &amp;gt; 1.json
./manage.py dump_object categories.addoncategory &amp;gt; 2.json
./manage.py merge_json 1.json 2.json &amp;gt; happy_fixture.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This should make creating test data slightly less painful.  So &lt;a href=&quot;http://github.com/davedash/django-fixture-magic&quot;&gt;give it a try&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Retrieving elements in a specific order in Django and mySQL</title>
   <link href="http://davedash.com/2010/02/11/retrieving-elements-in-a-specific-order-in-django-and-mysql/"/>
   <updated>2010-02-11T00:00:00-08:00</updated>
   <id>http://davedash.com/2010/02/11/retrieving-elements-in-a-specific-order-in-django-and-mysql</id>
   <content type="html">&lt;p&gt;If you have a list of ordered ids and you want to turn them into an ordered result set you can use &lt;code&gt;FIELD()&lt;/code&gt; in mysql:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT * FROM addons
ORDER BY FIELD(id, 3, 5, 9, 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is a handy trick if you use an external search engine which gives you an ordered list of ids and you want to pull out entire row sets.&lt;/p&gt;

&lt;p&gt;We do this in &lt;a href=&quot;http://github.com/jbalogh/zamboni&quot;&gt;addons.mozilla.org&lt;/a&gt; using the Django ORM like so:&lt;/p&gt;

&lt;script src=&quot;http://gist.github.com/301162.js&quot;&gt;&lt;/script&gt;


&lt;p&gt;&lt;a href=&quot;http://github.com/jbalogh/zamboni/commit/a0166108e8a62f386b4310cab0ceb3502575d520#L1R219&quot;&gt;The code in action&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Palm Pré: A retraction, I really like it now</title>
   <link href="http://davedash.com/2010/01/11/palm-pre-a-retraction-i-really-like-it-now/"/>
   <updated>2010-01-11T00:00:00-08:00</updated>
   <id>http://davedash.com/2010/01/11/palm-pre-a-retraction-i-really-like-it-now</id>
   <content type="html">&lt;p&gt;So before I went on trip to Minnesota last month, I decided maybe I would give the Palm Pré another shot.  After all, my parents have no internet access, so having the Pré... if I could overcome &lt;a href=&quot;/2009/11/19/palm-pre-always-hot/&quot;&gt;my issues&lt;/a&gt;, might be a welcome distraction.&lt;/p&gt;

&lt;p&gt;Before I packed it, I updated to WebOS 1.3.x (a few days later I updated to 1.3.5) and I was blown away.  The horsepower was increased by utilizing the GPU.  The following problems were fixed:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;* The device was no longer hot all the time
* Shutdown and startup were long, but not nearly as long as before.
* Render times were quicker
* All the elements usually rendered quickly in an app
* Network was fairly steady
* Phone calls also seemed fairly drop-free.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All these improvements helped me get over&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;* The tiny keyboard... not so bad in practice.
* No soft keyboard - I missed it, but I could deal without it.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Overall the device was great, it was fast enough to use, and most of the errors were annoying, but things I could deal with.  Cut and paste could be improved, and I wish the USB connector was the same as the one for HTC devices (I can't keep micro or mini USB types straight).&lt;/p&gt;

&lt;p&gt;So I love the device, and Murphy's Law dictates if work gives you a phone you don't like you get to keep it... until you start liking it again.  So I sent the phone back into rotation for other people at Mozilla to try.  Have at it.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Google Chrome Extensions Puzzle</title>
   <link href="http://davedash.com/2010/01/06/google-chrome-extensions-puzzle/"/>
   <updated>2010-01-06T00:00:00-08:00</updated>
   <id>http://davedash.com/2010/01/06/google-chrome-extensions-puzzle</id>
   <content type="html">&lt;div style=&quot;float:left; margin-right:1em&quot;&gt;&lt;a href=&quot;http://www.flickr.com/photos/44124375866@N01/4252390433&quot; title=&quot;View 'puzzle' on Flickr.com&quot;&gt;&lt;div style=&quot;text-align:center;&quot;&gt;&lt;img src=&quot;http://farm3.static.flickr.com/2684/4252390433_b49093b583_m.jpg&quot; alt=&quot;puzzle&quot; border=&quot;0&quot; width=&quot;161&quot; height=&quot;240&quot; /&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;I went to Add-on-Con some weeks back to represent my employer, the Mozilla Corporation.&lt;/p&gt;

&lt;p&gt;One of the goodies you get as a registrant was a jigsaw puzzle from the Google Chrome Extensions team.&lt;/p&gt;

&lt;p&gt;Perfect, my wife and I love solving jigsaw puzzles.  We finally finished a few days ago.  Anybody who has started at all will realize the puzzle is of a QR-code.  The QR-code is an extension that will eventually lead you to a prize.  It was a bit of a mini-puzzle not nearly as difficult as finding the QR code.&lt;/p&gt;

&lt;p&gt;Although finding a QR code scanner was a bit difficult, I had to borrow a HTC Magic from &lt;a href=&quot;http://fligtar.com/&quot;&gt;Justin Scott&lt;/a&gt; and installed a decent barcode scanner.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Django: Model Inheritance or Related Tables wrt AMO</title>
   <link href="http://davedash.com/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo/"/>
   <updated>2009-12-15T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo</id>
   <content type="html">&lt;p&gt;When I attended DjangoCon this year, I lamented that our flagship web property was difficult to test, and not fun to develop.  I figured DjangoCon was a way to placate me, and Django might mean something for some of the smaller projects at Mozilla.  However, Wil Clouser, our lead web developer, &lt;a href=&quot;http://micropipes.com/blog/2009/11/17/amo-development-changes-in-2010/&quot;&gt;announced development changes&lt;/a&gt; for &lt;a href=&quot;http://addons.mozilla.org&quot;&gt;addons.mozilla.org&lt;/a&gt; (AMO) that says we'll be moving to Django.&lt;/p&gt;

&lt;p&gt;Wil was open to Django and knew that's what we in the dev team wanted.  Jeff spawned our foray into a new AMO with &lt;a href=&quot;http://github.com/jbalogh/zamboni&quot;&gt;Zamboni&lt;/a&gt;.  I've been working on some grunt-work tasks inside and outside of Django.&lt;/p&gt;

&lt;p&gt;One of those tasks is building a transparent layer in Django to keep users logged in from our PHP-based site.  That kind of problem almost immediately forces you to ask one of the most fundamental questions you ask when using any framework:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;How much do I change my app, in order to accommodate the framework?&lt;/p&gt;&lt;/blockquote&gt;

&lt;!--more--&gt;


&lt;p&gt;More specifically:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Should I use the &lt;code&gt;django.contrib.auth&lt;/code&gt; User module, and to what extent?&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The more we looked into what features of Django we might want to use, &lt;code&gt;django.contrib.auth&lt;/code&gt; was heavily tied into other things we wanted, so it made sense for us to use it.  The next question is whether we try the &lt;a href=&quot;http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/&quot;&gt;inheritance approach&lt;/a&gt; or do we treat our legacy users table as a sort of User Profile and utilize the User module using the &lt;a href=&quot;http://www.b-list.org/weblog/2007/feb/20/about-model-subclassing/&quot;&gt;related table approach&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Using model-inheritance seems real nice, because we can pretend that our legacy user is the same thing as a &lt;code&gt;djaango.contrib.auth&lt;/code&gt; User - but this isn't true:&lt;/p&gt;

&lt;p&gt;Looking at our &lt;code&gt;users&lt;/code&gt; table more closely:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; explain users;
+-------------------------+---------------------+------+-----+---------------------+----------------+
| Field                   | Type                | Null | Key | Default             | Extra          |
+-------------------------+---------------------+------+-----+---------------------+----------------+
| id                      | int(11) unsigned    | NO   | PRI | NULL                | auto_increment |
| email                   | varchar(255)        | YES  | UNI | NULL                |                |
| password                | varchar(255)        | NO   |     |                     |                |
| firstname               | varchar(255)        | NO   |     |                     |                |
| lastname                | varchar(255)        | NO   |     |                     |                |
| nickname                | varchar(255)        | YES  | MUL | NULL                |                |
| bio                     | int(11) unsigned    | YES  | MUL | NULL                |                |
| emailhidden             | tinyint(1) unsigned | NO   |     | 0                   |                |
| sandboxshown            | tinyint(1) unsigned | NO   |     | 0                   |                |
| homepage                | varchar(255)        | YES  |     | NULL                |                |
| display_collections     | tinyint(1) unsigned | NO   |     | 0                   |                |
| display_collections_fav | tinyint(1) unsigned | NO   |     | 0                   |                |
| confirmationcode        | varchar(255)        | NO   |     |                     |                |
| resetcode               | varchar(255)        | NO   |     |                     |                |
| resetcode_expires       | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| notifycompat            | tinyint(1) unsigned | NO   | MUL | 1                   |                |
| notifyevents            | tinyint(1) unsigned | NO   | MUL | 1                   |                |
| deleted                 | tinyint(1)          | YES  |     | 0                   |                |
| created                 | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
| modified                | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| notes                   | text                | YES  |     | NULL                |                |
| location                | varchar(255)        | NO   |     |                     |                |
| occupation              | varchar(255)        | NO   |     |                     |                |
| picture_type            | varchar(25)         | NO   |     |                     |                |
| averagerating           | varchar(255)        | YES  |     | NULL                |                |
+-------------------------+---------------------+------+-----+---------------------+----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can very easily argue that this is a profile table, which happens to have credential information thrown in.&lt;/p&gt;

&lt;p&gt;I can see overtime, I'll just struggle to keep our legacy User to act like a Django User, whereas a UserProfile is fairly standard.&lt;/p&gt;

&lt;p&gt;Had I been writing this app from scratch, I would have chosen the UserProfile route.  This is extra data which takes up a lot of space, and changes far more often than user credentials.  Changing 4M+ rows sucks, by making users our UserProfile table, any changes to that table, don't tie up the table used for sign-ins.&lt;/p&gt;

&lt;p&gt;I'm curious what other people who port their apps to Django have done.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Palm Pre: Always hot</title>
   <link href="http://davedash.com/2009/11/19/palm-pre-always-hot/"/>
   <updated>2009-11-19T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/11/19/palm-pre-always-hot</id>
   <content type="html">&lt;p&gt;So I borrowed a Palm Pré that we had at Mozilla to see what it was like.  I was at first very excited, I remember before the Pre was released there was a lot of talk about how awesome-fantastic it was going to be.  The stories of awesomeness sort of died, and I had thought nothing of it.&lt;/p&gt;

&lt;p&gt;Immediately upon using the Pre I figured out why.  In short, it's a crappy phone.  It makes a very good attempt to do a lot, but it does them with such piss-poor performance, that nothing good is noticed.&lt;/p&gt;

&lt;p&gt;I am disappointed.  It's not even in the same class as an iPhone - maybe a future generation of Palm devices will be, but not this one.  I was hoping WebOS would be a good alternative to the iPhone.  It looks like Google will be doing that, though their phones haven't impressed me much either.  I am hoping that maybe this phone is just a dud.&lt;/p&gt;

&lt;p&gt;Here's what I didn't like:
* The Palm was always hot.
* The first run experience is painfully slow.
* The first run was an indicator of things to come, startup and shutdown are ridiculously slow.
* Every application is slow to render.
* Not all elements of an app render.
* The keys are too small.  Some people aren't migrating from a Treo and aren't used to mini keys.
* No soft keyboard.
* The palm website doesn't let you use plus-style addressing
* Media Mode was not self explanatory - and forced the phone to not work.
* Network would constantly drop out.  Couldn't use a lot of the data features.
* Phone calls didn't work so great.
* Did I mention it was ass slow, even the dialing program was slow.
* The battery dies quickly
* I could only cut/paste when composing, but I couldn't cut a string of text from an email.
* Felt too much like an old palm&lt;/p&gt;

&lt;p&gt;Despite the sadness there were a few good things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When it did fetch email, and other notices, it displayed them nicely&lt;/li&gt;
&lt;li&gt;The unification of Facebook and Gmail was pretty cool - it also made me want to trim some of those friends from highschool off my facebook - I ain't ever gonna call em.&lt;/li&gt;
&lt;li&gt;The Icons were pretty.&lt;/li&gt;
&lt;li&gt;The card interface was interesting.&lt;/li&gt;
&lt;li&gt;The travel charger could be modified to work in non US chargers fairly easily.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;All in all, I'm glad that I had a chance to try out this device.  It showed me, that user interfaces above all need to be very fast and responsive.  Furthermore, everything you try to do should be done exceptionally well.  I'm hopeful that software updates can alleviate some of the problem, but I think the root of the problem is slow hardware.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>AMO Search: Powered by Sphinx</title>
   <link href="http://davedash.com/2009/09/30/amo-search-powered-by-sphinx/"/>
   <updated>2009-09-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/09/30/amo-search-powered-by-sphinx</id>
   <content type="html">&lt;p&gt;Last night, I gave a talk at the &lt;a href=&quot;https://wiki.mozilla.org/AddonMeetups:2009:Chicago&quot;&gt;Addons Meetup&lt;/a&gt; at Threadless HQ in Chicago on the new search engine powering &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt;.  I'll recap the technical portion of the talk and give a bit more details.&lt;/p&gt;

&lt;p&gt;First, I'd like to thank Harper and Threadless.  It was a great location in the greatest city in the universe.  Before and after the meetup, Harper was just an all-around great guy to hang with and the threadless headquarters was a nice hangout place for meeting people interested in addons.&lt;/p&gt;

&lt;p&gt;Shortly after my talk, our Engineering Ops team deployed the new AMO 5.1 complete with a new Sphinx powered search engine.&lt;/p&gt;

&lt;p&gt;So let's talk about search.  Note: parts of this are a rehash of my talk, so feel free to skip around.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;A bit about addons&lt;/h3&gt;

&lt;p&gt;Addons is a huge growing space.  Arguably it's Mozilla's best kept secret.  Sure readers of this blog probably know what Addons are, but ask people who aren't as web-savvy.  Most people don't know what a browser is - and it's hard to explain it to people without getting technical.&lt;/p&gt;

&lt;p&gt;We can just skip that step.  Because Addons are small things that people can easily &quot;get&quot;.&lt;/p&gt;

&lt;p&gt;&quot;It's an easy way to customize the internet when your surfing.&quot;&lt;/p&gt;

&lt;p&gt;While perhaps not technically correct, its one way of explaining it to people.  Maybe a better way is just showing people what they can do with addons.&lt;/p&gt;

&lt;p&gt;On my flight out to Chicago, I talked to a person on the plane who didn't know what a browser was, but after showing her &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; she was really intrigued.&lt;/p&gt;

&lt;p&gt;If everyday non-technical people can realize the potential of addons, it's only a matter of time before they start knocking down the doors to AMO.&lt;/p&gt;

&lt;p&gt;So we better be prepared to handle them, and get them what they want.&lt;/p&gt;

&lt;h3&gt;The technical details of addons.mozilla.org&lt;/h3&gt;

&lt;p&gt;Everytime you open Firefox, it pings &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; to see if there's any updates to any of the addons that happen to be installed.  Over a third of the people using Firefox have at least one addon, and Firefox is roughly 22% of the browser market.  That means roughly 7% of people opening their browsers are pinging our servers for updates.&lt;/p&gt;

&lt;p&gt;Needless to say it's a lot of traffic, and to support it we need a fair amount of hardware.  AMO is clearly the largest site in the Mozilla universe in both respects.&lt;/p&gt;

&lt;p&gt;Some stats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 mySQL master&lt;/li&gt;
&lt;li&gt;4 mySQL slaves&lt;/li&gt;
&lt;li&gt;2 memached servers&lt;/li&gt;
&lt;li&gt;2 Sphinx indexer/search daemons&lt;/li&gt;
&lt;li&gt;24 Web Frontend&lt;/li&gt;
&lt;li&gt;Multiple Zeus ZXTM clusters all&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Most of this is standard, we'll talk about Sphinx later, but Zeus is amazing.  I didn't know what Zeus was until earlier this year when I interviewed with Mozilla's VP of Engineering Operations.  All our requests get cached so much of our hits actually hit our Zeus cluster and not our web servers.&lt;/p&gt;

&lt;p&gt;To see just how amazing they are read our &lt;a href=&quot;http://blog.mozilla.com/mrz/&quot;&gt;mrz's ops blog&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Why search matters&lt;/h3&gt;

&lt;p&gt;If you have any kind of custom content and unique meta data a custom search solution is a must.  Browsing through a site isn't going to cut it.  Browsing is dead.  Search is how you find things on a web site.  On &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; you may see an addon that's featured somewhere, or you might want to see what's out there, but the right search query will find you the right addon in two clicks.&lt;/p&gt;

&lt;h3&gt;Improve Search&lt;/h3&gt;

&lt;p&gt;So my first job on AMO was to &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=498999&quot;&gt;improve addons search&lt;/a&gt;.  It was a vague request and born out of frustration with what we had.  It wasn't a problem that certain things were indexed, or unicode didn't work, or results weren't sorted.  We may have had all those problems, but as a product search needed to be replaced.&lt;/p&gt;

&lt;p&gt;To me it meant that we needed some framework that would allow developers to quickly debug and fix any future search calamities at a moments notice.&lt;/p&gt;

&lt;p&gt;So here were the goals I made for myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do something that sucks less than what we’ve got&lt;/li&gt;
&lt;li&gt;Do something that makes it easier to suck less in the future&lt;/li&gt;
&lt;li&gt;Do something that’s easy to use for our operations team, web developers and most importantly, end-users&lt;/li&gt;
&lt;li&gt;Reduce strain on our databases, developers and operations teams&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Complex Data&lt;/h3&gt;

&lt;p&gt;Our data set is small (we have 5,000 addons), but there's a lot of secondary meta data about the addons that we track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Addons work in 1 or more locales (e.g. en-US, fr, de, etc)&lt;/li&gt;
&lt;li&gt;Addons are optionally platform specific (Linux, OS X, etc)&lt;/li&gt;
&lt;li&gt;Addons work with one or more products (Firefox, Thunderbird, Seamonkey, Sunbird or Fennec)&lt;/li&gt;
&lt;li&gt;Addons come in multiple flavors (extensions, themes, dictionaries and more)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;We want to index all this data.  Unfortunately to get at much of this data it involves either numerous queries, or numerous joins which put a strain on mysql.  How much strain?&lt;/p&gt;

&lt;p&gt;At peak we get about 10 search queries per second.  If we do something smarter this won't have to cause a lot of strain.&lt;/p&gt;

&lt;h3&gt;Using Sphinx&lt;/h3&gt;

&lt;p&gt;Sphinx is an open source search indexer and daemon.  It's used by Craigslist, the Pirate Bay and &lt;a href=&quot;http://support.mozilla.com&quot;&gt;Mozilla Support&lt;/a&gt;.  It was very easy to use and despite a complicated set of data and business logic, Sphinx was up to the task.&lt;/p&gt;

&lt;h3&gt;The challenges&lt;/h3&gt;

&lt;p&gt;We needed to search for addons in several languages.  So indexing just addons wouldn't work, we need to make sure we have every translation of every addon indexed.  For those counting, we have 5,000 addons, but 18,000 translations of addons.&lt;/p&gt;

&lt;p&gt;All the joining and filtering that needed to be done for our old search still needs to be done, but we can do this all in one shot by using a mysql view.  This view is a flat list of each translated addon as well as all meta data associated with it.  This then gets fed into the sphinx indexer.&lt;/p&gt;

&lt;p&gt;Along the way we ran into some issues which used to be dealt with outside of mysql, such as comparing versions.  It was gross and quite a hack, so we turned the variety of &lt;a href=&quot;http://spindrop.us/2009/08/07/v-is-for-version-hell/&quot;&gt;acceptable version strings into integers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also learned that stemming wasn't a good idea as we assumed it would be.  Stemming was great for searching through lots of text, but a great deal of addon searches were really just searches for product names, so we opted for substring searches.  We'll see how that fares.  There is probably room for improvement.&lt;/p&gt;

&lt;p&gt;Much of this, however involved knowing our data, and knowing how it will be used by our users.  Once we got that down, we could hammer it all out using Sphinx.&lt;/p&gt;

&lt;h3&gt;Wins&lt;/h3&gt;

&lt;p&gt;So Sphinx gains us a bit architecturally.  We have a complicated query, but it only gets run once every 5 minutes versus the 180,000 times it was run &quot;on demand.&quot;&lt;/p&gt;

&lt;p&gt;Indexing happens rather quickly, just over a minute.&lt;/p&gt;

&lt;p&gt;The API was a breeze to work with, and was easy to drop into our own codebase.&lt;/p&gt;

&lt;p&gt;Because of our relatively small data set, and quick indexing, we're able to scale this simply by cloning and load balancing.  Meaning, we just need to scale for traffic, but addon growth (which is slower than traffic growth) we can safely not worry about for a while.&lt;/p&gt;

&lt;p&gt;Our ops team can monitor the sphinx clusters and just deploy additional nodes as needed.&lt;/p&gt;

&lt;h3&gt;Building a platform&lt;/h3&gt;

&lt;p&gt;What we've done is built a foundation for search.  Not all the problems are gone, but a lot of the problems that our QA team finds are able to be resolved quickly.  We have a nice pile of unit tests as well that help us keep our results in check when we start tweaking dials.&lt;/p&gt;

&lt;p&gt;We even have the groundwork for some nifty advanced search syntax, that hopefully we can inject into future releases of AMO.&lt;/p&gt;

&lt;p&gt;Enjoy.  And if you find anything, &lt;a href=&quot;http://bit.ly/search-bugs&quot;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Getting started with pipe viewer</title>
   <link href="http://davedash.com/2009/09/16/getting-started-with-pipe-viewer/"/>
   <updated>2009-09-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/09/16/getting-started-with-pipe-viewer</id>
   <content type="html">&lt;p&gt;Despite working on slimming the &lt;code&gt;addons.mozilla.org&lt;/code&gt; database through dieting and exercise - I still have to occasionally do long running database tasks.  So I finally tried out &lt;a href=&quot;http://www.ivarch.com/programs/pv.shtml&quot;&gt;pipe viewer&lt;/a&gt;.  As someone who's impatient this has been awesome.  Here's some quick examples:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[root@ml-db10 sun]# pv -cN source &amp;lt; addons_remora.2009.09.15.sql.gz | gunzip|pv -cN gunzip &amp;gt; addons_remora.2009.09.15.sql
   gunzip: 10.1GB 0:06:48 [25.5MB/s] [   &amp;lt;=&amp;gt;                                  ]
   source: 3.47GB 0:06:48 [8.72MB/s] [======================&amp;gt;] 100%
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we are calling pipe viewer with an argument that says to title this progress meter as &lt;code&gt;source&lt;/code&gt;, and feeding it the gzip'd file.  Pipe viewer will output two things the progress, and the actual file.  We pipe that file into &lt;code&gt;gunzip&lt;/code&gt; to unzip it, and back into another instance of pipe viewer (again with a title, of &lt;code&gt;gunzip&lt;/code&gt;) and the standard output gets redirected to our destination file.&lt;/p&gt;

&lt;p&gt;Now a simpler example is checking the progress of loading a large sql file into mysql:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[root@ml-db10 sun]# pv -cN sql &amp;lt; addons_remora.2009.09.15.sql | mysql -uroot addons_remora -p$PWD
      sql: 2.55GB 0:18:19 [5.68MB/s] [=====&amp;gt;                  ] 25% ETA 0:54:30
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We could have probably combined all this, however:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[root@ml-db10 sun]# pv -cN source &amp;lt; addons_remora.2009.09.15.sql.gz | gunzip|pv -cN gunzip | mysql -u root addons_remora -p$PWD
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Armed with this knowledge you can determine whether to grab a soda, a sandwich or a 2-hour lunch.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>DjangoCon wrapup</title>
   <link href="http://davedash.com/2009/09/15/djangocon-wrapup/"/>
   <updated>2009-09-15T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/09/15/djangocon-wrapup</id>
   <content type="html">&lt;p&gt;I went to &lt;a href=&quot;http://djangocon.org/&quot;&gt;DjangoCon&lt;/a&gt; this past week for work.  Django is one of my favorite frameworks.  I dropped PHP and the symfony framework to learn python and Django and I haven't looked back.  I think for Mozilla's webdev team it would be the framework of choice.  We have 100s of sites in many frameworks, but not a lot of resuability.  Django apps are built to built to be reusable.  If you build correctly you don't have to refactor, it's already done.&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Here's a collection of notes I collected through the conference.&lt;/p&gt;

&lt;h3&gt;Day one&lt;/h3&gt;

&lt;h4&gt;Keynote - Avi Bryant&lt;/h4&gt;

&lt;blockquote&gt;&lt;p&gt;Frameworks lock us into RDBMS = bad&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;This keynote mentioned the limits of modern frameworks and modern web development.  Essentially frameworks are great for getting started, but as a site grows, the framework gets replaced little by little.  Sometimes it can get in the way - such as with limitation of database choices.&lt;/p&gt;

&lt;h4&gt;UR doing it wrong - James Bennet&lt;/h4&gt;

&lt;p&gt;James outlined a few key problems that many Django developers run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;learning python as you go&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; doesn't work unless you know some programming upfront&lt;/li&gt;
&lt;li&gt; do the python tutorial&lt;/li&gt;
&lt;li&gt; read python in a nutshell or dive into python&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Things you should know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;subclasses&lt;/li&gt;
&lt;li&gt;super()&lt;/li&gt;
&lt;li&gt;slides went too fast... hopefully they'll be posted&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;All in all RTFM for python and Django :)&lt;/p&gt;

&lt;p&gt;Learn about other py packages... like twisted.  If Twisted Matrix was implemented in Ruby it would be advertised as the second coming of Christ.&lt;/p&gt;

&lt;p&gt;Bennet's Django App review smoketests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installable via pip, easy_install or setup.py

&lt;ul&gt;
&lt;li&gt;read distutils-guide&lt;/li&gt;
&lt;li&gt;stay away from setuptools&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;have a README&lt;/li&gt;
&lt;li&gt;INSTALL file list deps&lt;/li&gt;
&lt;li&gt;Write DOCUMENTATION

&lt;ul&gt;
&lt;li&gt;use sphinx.pocoo.org&lt;/li&gt;
&lt;li&gt;store it in your package &lt;em&gt;and&lt;/em&gt; upload package docs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LICENSE (most Django apps use BSD)&lt;/li&gt;
&lt;li&gt;Write unit tests&lt;/li&gt;
&lt;li&gt;django-lint - to look over code (like pep8.py)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;pro-django is a decent book, but not written by Bennet.&lt;/p&gt;

&lt;h4&gt;Testing - Eric Holscher&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Django 1.1 encourages you to test by auto-creating tests.py.&lt;/li&gt;
&lt;li&gt;Support for:

&lt;ul&gt;
&lt;li&gt;Unittests&lt;/li&gt;
&lt;li&gt;Doctest&lt;/li&gt;
&lt;li&gt;Tests done in a db transacation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Test Driven Documentation (TDD + DDD)&lt;/li&gt;
&lt;li&gt;Doctest

&lt;ul&gt;
&lt;li&gt;easy&lt;/li&gt;
&lt;li&gt;can't use PDB&lt;/li&gt;
&lt;li&gt;Hides certain failures&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Unittests via Django TestCase

&lt;ul&gt;
&lt;li&gt;XUnit&lt;/li&gt;
&lt;li&gt;setup/Teardown&lt;/li&gt;
&lt;li&gt;adds db fixtures&lt;/li&gt;
&lt;li&gt;assertions&lt;/li&gt;
&lt;li&gt;mail testing/inbox testing&lt;/li&gt;
&lt;li&gt;url testing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TestCase

&lt;ul&gt;
&lt;li&gt;Browserless Request/Response testing&lt;/li&gt;
&lt;li&gt;Similar to sfBrowser in symfony&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Google Summer of Code (for Django 1.2)

&lt;ul&gt;
&lt;li&gt;Coverage reports!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I need to learn PDB&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Deploying Django -&lt;/h4&gt;

&lt;p&gt;Run mod_wsgi in daemon mode.&lt;/p&gt;

&lt;h3&gt;Day 2&lt;/h3&gt;

&lt;h4&gt;&lt;a href=&quot;http://blog.ianbicking.org/2009/09/10/a-new-self-definition-for-foss/&quot;&gt;Keynote - Ian Bicking&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;GNU Manifest:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt; I consider that the golden rule requires that if I like a program I must share it with other people who like it. Software sellers want to divide the users and conquer them, making each user agree not to share with others. I refuse to break solidarity with other users in this way. I cannot in good conscience sign a nondisclosure agreement or a software license agreement. ...&lt;/p&gt;

&lt;p&gt;So that I can continue to use computers without dishonor, I have decided to put together a sufficient body of free software so that I will be able to get along without any software that is not free.&lt;/p&gt;&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;GNU manifesto was the idea of sharing software amongst friends&lt;/li&gt;
&lt;li&gt;GNU has purpose - BSD, etc is just a rule - free to share&lt;/li&gt;
&lt;li&gt;Free is not just the absense of copyright&lt;/li&gt;
&lt;li&gt;Free is not a reaction to existing rules, but a golden rule&lt;/li&gt;
&lt;li&gt;Not just a fight against MS&lt;/li&gt;
&lt;li&gt;Need to find morality (the why) within the practical (the law, or what you can do)&lt;/li&gt;
&lt;li&gt;Open sourcing closed source code isn't building open source&lt;/li&gt;
&lt;li&gt;This might apply to Mozilla... as webkit has taken off more than Gecko.&lt;/li&gt;
&lt;li&gt;Open source is person to person not company to company - despite sponsorship.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Using Django in Non-standard ways - Eric Florenzano&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Django loosely coupled&lt;/li&gt;
&lt;li&gt;Replace templating with Jinja 2&lt;/li&gt;
&lt;li&gt;Copy Django methods into djangoext to easily customize Django behavior&lt;/li&gt;
&lt;li&gt;Not using django.contrib.auth

&lt;ul&gt;
&lt;li&gt;reasons: writing a fb app - no auth needed&lt;/li&gt;
&lt;li&gt;no shoehorning needed - saves time - less overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;skip the orm?

&lt;ul&gt;
&lt;li&gt;legacy dbs&lt;/li&gt;
&lt;li&gt;non standard or db (or non-relational database)&lt;/li&gt;
&lt;li&gt;no database&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;wsgi middleware has some cool shit

&lt;ul&gt;
&lt;li&gt;repose.bitblt: autoscales images&lt;/li&gt;
&lt;li&gt;repose.squeeze: will concat js/css on the fly based on statistical analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;non standard Django based apps

&lt;ul&gt;
&lt;li&gt;YARDBird - IRCBot framework&lt;/li&gt;
&lt;li&gt;djng micro framework&lt;/li&gt;
&lt;li&gt;Jngo- singlefile cms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;using admin in a nonstandard way is hard/impossible coupled with ORM and auth&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Real-time web and other Buzzwords - Chris Wanstrath&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;more than just getting your rss feeds faster&lt;/li&gt;
&lt;li&gt;push vs. pull&lt;/li&gt;
&lt;li&gt;1 persisting connection vs polling&lt;/li&gt;
&lt;li&gt;comet/flash-xml/or html5 web socket&lt;/li&gt;
&lt;li&gt;orbitted - open source python comet server&lt;/li&gt;
&lt;li&gt;zeddicus - does the business logic&lt;/li&gt;
&lt;li&gt;orbitted has its own js libs - its a simple port/socket thing for your server code to deal with - not request/response.&lt;/li&gt;
&lt;li&gt;all connections are persisting browser/orbitted orbitted/zeddicus&lt;/li&gt;
&lt;li&gt;You can even use orbitted to connect straight to IRC and write a client in JS&lt;/li&gt;
&lt;li&gt;Jetty also is good for comet&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Also:
* see webhooks
* see pubsubhubub&lt;/p&gt;

&lt;h4&gt;&lt;a href=&quot;http://www.slideshare.net/nowells/djangocon-09-presentation-pluggable-applications&quot;&gt;Pluggable, Reusable Django Apps: A Use Case and Proposed Solution&lt;/a&gt; - Shawn Rider and Nowell Strite&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;PBS moved from perl to django - build a lot of reusable apps&lt;/li&gt;
&lt;li&gt;convincing your superiors

&lt;ul&gt;
&lt;li&gt;need a good story -&lt;/li&gt;
&lt;li&gt;existing base of python helped&lt;/li&gt;
&lt;li&gt;With Django easy to do things right without doing things slow&lt;/li&gt;
&lt;li&gt;be really good...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;built a lot of apps to be very reusable, and pluggable based on requirements PBS had&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Day 3&lt;/h3&gt;

&lt;h4&gt;&lt;a href=&quot;http://www.slideshare.net/twleung/djangocon-2009-keynote&quot;&gt;Keynote&lt;/a&gt; - Ted Leung - Sun&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Django jobs are a growing market&lt;/li&gt;
&lt;li&gt;Preferred by startups&lt;/li&gt;
&lt;li&gt;Bespin/wave - cool&lt;/li&gt;
&lt;li&gt;APIs are big... still&lt;/li&gt;
&lt;li&gt;Physically impossible to create purely server-side interactions that are usable enough - rely on rest/comet/ajax/etc to bridge gap&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;&lt;a href=&quot;http://immike.net/files/scaling_django_dc09.pdf&quot;&gt;Scaling Django&lt;/a&gt; Mike Malone&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;MM from Pownce (now sixapart)&lt;/li&gt;
&lt;li&gt;Slides started out as &quot;Building Scalable Web Applications&quot;&lt;/li&gt;
&lt;li&gt;Django didn't get in the way too much when it came to scaling&lt;/li&gt;
&lt;li&gt;Django had tons of caching support&lt;/li&gt;
&lt;li&gt;Cached objects by hand (memcached) and object ID lists&lt;/li&gt;
&lt;li&gt;Use memache for sessions too&lt;/li&gt;
&lt;li&gt;use signals to signal cache invalidation&lt;/li&gt;
&lt;li&gt;race conditions...&lt;/li&gt;
&lt;li&gt;Queue shit... gearman, rabbit mq, etc.&lt;/li&gt;
&lt;li&gt;Memecached incr/decr operators are awesome&lt;/li&gt;
&lt;li&gt;See gh/mmalone/django-caching&lt;/li&gt;
&lt;li&gt;See gh:.../django-multidb&lt;/li&gt;
&lt;li&gt;to combat slavelag use a memcache key to alternate between master or slave&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;&lt;a href=&quot;http://heisel.org/blog/2009/09/11/gearman/&quot;&gt;Gearman - working later&lt;/a&gt; - Chris Heisel&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Gearman - a work later alt to rabbit mq&lt;/li&gt;
&lt;li&gt;Makes the most sense for something like cesium, with a bazillion worker &lt;strike&gt;bees&lt;/strike&gt; foxes feeding off a single queue&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Also at the con, I talked to someone about rebuilding large apps... and they took a PHP app and used URL rewriting to and a lot of PHP/Python glue code to build a seamless transitory app.  The rule is, all new functionality was done up in python while the old app was in maintenance mode.&lt;/p&gt;

&lt;p&gt;More talks &lt;a href=&quot;http://djangocon.pbworks.com/Slides&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Snow Leopard for Macports and Mysql users</title>
   <link href="http://davedash.com/2009/09/02/snow-leopard-for-macports-and-mysql-users/"/>
   <updated>2009-09-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/09/02/snow-leopard-for-macports-and-mysql-users</id>
   <content type="html">&lt;p&gt;I use mysql and macports on OSX and both were broken when I upgraded to Snow Leopard.&lt;/p&gt;

&lt;p&gt;Mysql was a quick fix:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ln -s /usr/local/mysql-5.1.35-osx10.5-x86 /usr/local/mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(you're installed version might be different).  It turns out a symlink was removed during the Snow Leopard upgrade.&lt;/p&gt;

&lt;p&gt;As for MacPorts, I had to install Xcode from the Snow Leopard CD, install the Snow Leopard version of MacPorts and then follow &lt;a href=&quot;http://trac.macports.org/wiki/Migration&quot;&gt;this migration guide&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fun with HTML5, Blockchalk, Bookmarklets and Google Maps</title>
   <link href="http://davedash.com/2009/09/01/fun-with-html5-blockchalk-bookmarklets-and-google-maps/"/>
   <updated>2009-09-01T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/09/01/fun-with-html5-blockchalk-bookmarklets-and-google-maps</id>
   <content type="html">&lt;p&gt;I've been filing away neat things that I've learned.  Like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firefox supports GeoLocation (which varies in accuracy, but is really accurate for me)&lt;/li&gt;
&lt;li&gt;Stephen Hood released the &lt;a href=&quot;http://blockchalk.com/developers&quot;&gt;Blockchalk API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://twitter.com/joshu/status/3679085168&quot;&gt;Google Maps lets you use GeoRSS feeds as a term&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;This solves the problem I had with BlockChalk, which is I wanted a way that I could see what's going on near where I am - and I don't have an iPhone.&lt;/p&gt;

&lt;p&gt;So I wrote two bookmarklets:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;javascript:navigator.geolocation.getCurrentPosition(function(p){window.location='http://maps.google.com/maps?q=http://blockchalk.com/api/v0.6/chalks/'+p.coords.latitude+','+p.coords.longitude;})&quot;&gt;Blockchalk Me&lt;/a&gt; which will list Blockchalk listings near you&lt;/p&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;javascript:c=gApplication.getMap().getCenter();window.location=%22http://maps.google.com/maps?q=http://blockchalk.com/api/v0.6/chalks/%22+c.lat()+%22,%22+c.lng()&quot;&gt;Blockchalk this Google Map&lt;/a&gt; which only works if Google Maps is open.  It will load Blockchalks that are near the center of the open Google Map.&lt;/p&gt;

&lt;p&gt;Unfortunately Blockchalk doesn't have a lot of data yet, and will return no results if there's nothing within a mile radius.  Hopefully a radius parameter will be included for the API call.&lt;/p&gt;

&lt;p&gt;So there's no guarantees on the first bookmarklet, but the second bookmarklet should yield nice results for &lt;a href=&quot;http://maps.google.com/maps?q=37.74339,-122.428924&quot;&gt;this location&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>git svn rebase... forever?</title>
   <link href="http://davedash.com/2009/08/12/git-svn-rebase-forever/"/>
   <updated>2009-08-12T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/08/12/git-svn-rebase-forever</id>
   <content type="html">&lt;p&gt;While working on &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt; I ran into an issue of &lt;code&gt;git svn rebase&lt;/code&gt; continually asking me to merge a file, over and over.&lt;/p&gt;

&lt;p&gt;I had a branch open for a bug.  In that branch I wrote a library.  While that bug was under review, I had to use that library in a new branch for another bug - and had to develop on it a bit.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git co -b bug1 master
$ vi libs/mylib.php # make the lib
$ git add .
$ git commit -m &quot;my new lib&quot;
$ git checkout -b bug2 master
$ git checkout bug1 libs/mylib.php # copies this file from one branch to the next
$ git commit -m &quot;lib copied over&quot;
$ vi libs/mylib.php # hack on the lib 
$ git commit -m &quot;awesomized lib&quot;
$ git svn dcommit # push it up
$ git checkout bug1
$ git svn rebase #... oh shit
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So the rebase was happening.  This is git trying to merge your changes in bug1 and bug2 and play them together in realtime nicely, asking you each step of the way to merge things manually.  I thought something weird was happening since &quot;libs/mylib.php&quot; kept needing manual merging.  Then I noticed that git is applying a series of patches, and that eventually this will resolve and your site will be rebased.&lt;/p&gt;

&lt;p&gt;Don't lose hope, &lt;code&gt;git svn rebase&lt;/code&gt; will finish.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>V is for Version Hell</title>
   <link href="http://davedash.com/2009/08/07/v-is-for-version-hell/"/>
   <updated>2009-08-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/08/07/v-is-for-version-hell</id>
   <content type="html">&lt;p&gt;Versioning is quite difficult to deal with.  Versions are nearly-numbers, but
you can't quite sort them using standard numerical algorithms.&lt;/p&gt;

&lt;p&gt;While the following is true:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;1.1 &amp;lt; 1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The following is also true:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;1.2 &amp;lt; 1.18 &amp;lt; 1.20
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &quot;.&quot; is not a decimal point but a separator.&lt;/p&gt;

&lt;p&gt;Mozilla uses a modestly complicated &lt;a href=&quot;https://developer.mozilla.org/en/Toolkit_version_format&quot;&gt;versioning system&lt;/a&gt; that involves stars,
plusses, and sometimes &quot;x&quot;.&lt;/p&gt;

&lt;p&gt;I found a very convoluted way to translate these versions into large integers.
The versions for applications in the AMO database have four parts at most, they
are potentially alpha or beta and potentially a pre-release.  In some cases we
have multiple versions represented with &lt;code&gt;.*&lt;/code&gt;, &lt;code&gt;.x&lt;/code&gt; or &lt;code&gt;+&lt;/code&gt; at the end.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en/Toolkit_version_format&quot;&gt;Toolkit docs&lt;/a&gt; let us translate &quot;+&quot; to mean &quot;pre-release of the next
version&quot;.  E.g. 1.0+ is 1.1pre0.  Since my primary purpose of all this is for
sorting, &lt;code&gt;.*&lt;/code&gt; and &lt;code&gt;.+&lt;/code&gt; may as well just be a very large &quot;version part.&quot;  Since
all the version parts I deal with are a maximum of 2-digits, I turned &lt;code&gt;.*&lt;/code&gt; and
&lt;code&gt;.+&lt;/code&gt; into &lt;code&gt;.99&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;3.5+ =&amp;gt; '03'+'05'+'99' =&amp;gt; 030599
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We also need to deal with versions that may be alpha, beta or not.  If
everything else is equal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;3.5a &amp;lt; 3.5a5 &amp;lt; 3.5b &amp;lt; 3.5b2 &amp;lt; 3.5 &amp;lt; 3.5+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We assign a single integer to represent a version's &quot;non-alphaness&quot;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;a =&amp;gt; 0
b =&amp;gt; 1
non alpha/beta =&amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We assume that &lt;code&gt;3.5a = 3.5a1&lt;/code&gt;.  Therefore:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;'3.5a =&amp;gt; 3.5.0a1 =&amp;gt; '03'+'05'+'00'+'0'+'01' =&amp;gt; 030500001
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Similarly if it's a pre-release we assign a 0 or 1 to represent
&quot;non-pre-releaseness&quot;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;'3.5a pre2 =&amp;gt; 3.5.0a1pre2
=&amp;gt; '03'+'05'+'00'+'0'+'01'+'0'+'02
=&amp;gt; 030500001002
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So what does this get us?  Integers which we can use for comparison, sorting,
etc.  It's a one time calculation for each version and we can do some nice SQL
statements in AMO like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; SELECT version,version_int FROM appversions WHERE application_id = 1 ORDER BY version_int LIMIT 15;
+---------+--------------+
| version | version_int  |
+---------+--------------+
| 0.3     |  30000200100 |
| 0.6     |  60000200100 |
| 0.7     |  70000200100 |
| 0.7+    |  80000200000 |
| 0.8     |  80000200100 |
| 0.8+    |  90000200000 |
| 0.9     |  90000200100 |
| 0.9.0+  |  90100200000 |
| 0.9.1+  |  90200200000 |
| 0.9.2+  |  90300200000 |
| 0.9.3   |  90300200100 |
| 0.9.3+  |  90400200000 |
| 0.9.x   |  99900200100 |
| 0.9+    | 100000200000 |
| 0.10    | 100000200100 |
+---------+--------------+
15 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can now index these integers using Sphinx and do some very easy searches for
addons based on version number.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Delicious keeps you in the know</title>
   <link href="http://davedash.com/2009/08/04/delicious-keeps-you-in-the-know/"/>
   <updated>2009-08-04T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/08/04/delicious-keeps-you-in-the-know</id>
   <content type="html">&lt;p&gt;My last task at Delicious was to build along with the amazing &lt;a href=&quot;http://zooie.wordpress.com/&quot;&gt;Vik Singh&lt;/a&gt; was to build a new feed of bookmarks that was heavily influenced by Twitter.  It was one of the most interesting and enjoyable pieces of code that I worked on at Delicious.&lt;/p&gt;

&lt;p&gt;Over two months since my final check-in, the code is &lt;a href=&quot;http://delicious.com/&quot;&gt;now in production&lt;/a&gt;.  It is mostly as intended, but is lacking an RSS or JSON feed (which I had already built).  This is somewhat disappointing since I was hoping that Delicious would remain as open as it had been in the past.&lt;/p&gt;

&lt;p&gt;The algorithm is fairly simple we take a look at what trending topics exist at any moment in time (via Google Trends and Twitter) and we combine it with a list of popular terms.  We take the whole lot of these items and   query search twitter and store an in-memory data table of tweets.  We also take a snapshot of new URLs to the Delicious corpus (basically anything on &lt;a href=&quot;http://delicious.com/recent/&quot;&gt;Delicious recent&lt;/a&gt; with 1 save).  We cluster the Delicious URLs and then find tweets that are similar to each of these clusters.&lt;/p&gt;

&lt;p&gt;The code for this is similar to &lt;a href=&quot;http://zooie.wordpress.com/2009/01/15/twitter-boss-real-time-search/&quot;&gt;Vik's TweetNews&lt;/a&gt; - but I think the Delicious data is a nicer fit.&lt;/p&gt;

&lt;p&gt;We optimized this quite a bit and built a very fast inverted-index and tweaked the code to run in about a minute.  Like TweetNews the heart of this was built using Python.  Python while being a dynamic language is quite amazing for manipulating and iteratiting over sets of data.&lt;/p&gt;

&lt;p&gt;While building this tool, it became my way to feel pulse of what's going on.  I could ditch a lot of my RSS feeds and rely solely on Delicious to be on the up and up.  Unfortunately I can't subscribe to a feed for this.  Either delicious has made a mistake and didn't launch their feeds at the same time as their web (entirely possible, since Delicious hasn't been updated for most of 2009) or they are deliberately taking a step backwards.&lt;/p&gt;

&lt;p&gt;This step backwards is weird from the usability issue.  Delicious has always been a tool that allowed for multiple types of consumers and a tool that appealed to developers thanks to its myriad of RSS and JSON feeds.    I'm glad I didn't have to be on the losing side of that decision.  Delicious relies heavily on Google Trends and Twitter Search.  While there is no requirement for them to share the data they are mashing up, it would be the right thing to do.&lt;/p&gt;

&lt;p&gt;Let me know what you think of the new feeds.  I wish I could share a github link or something snazzy so you could play around with it, but this post should be a good starting point for other real-time data mashups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Read &lt;a href=&quot;http://blog.delicious.com/blog/2009/08/delicious-homepage-gets-%E2%80%9Cfresh%E2%80%9D.html&quot;&gt;Vik's account of this on the Delicious Blog&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Have unique descriptive page titles</title>
   <link href="http://davedash.com/2009/07/31/have-unique-descriptive-page-titles/"/>
   <updated>2009-07-31T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/07/31/have-unique-descriptive-page-titles</id>
   <content type="html">&lt;div style=&quot;float:right&quot;&gt;&lt;a href=&quot;http://www.flickr.com/photos/44124375866@N01/3764074726&quot; title=&quot;View 'Sphinx - Free open-source SQL full-text search engine - (Build 20090715083437)' on Flickr.com&quot;&gt;&lt;div style=&quot;text-align:center;&quot;&gt;&lt;img src=&quot;http://farm4.static.flickr.com/3588/3764074726_0c02ffd18c.jpg&quot; alt=&quot;Sphinx - Free open-source SQL full-text search engine - (Build 20090715083437)&quot; border=&quot;0&quot; width=&quot;483&quot; height=&quot;338&quot; /&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;One of my internet pet-peeves is people using the same page title for every page on their web site.  Take a look at &lt;a href=&quot;http://www.google.com/search?hl=en&amp;amp;q=+site:www.sphinxsearch.com+sphinx+api+php&quot;&gt;this search for Sphinx&lt;/a&gt;.  As you can see virtually all the links for Sphinx are titled &quot;Sphinx - Free open-source SQL full-text search engine&quot; which blows for usability when it comes to searching, or even managing the various pages you might have open in your web browser.&lt;/p&gt;

&lt;p&gt;To get an idea of the page I want I need to look at the abstract which may or may not give me a clue.  Even the forum posts which usually have subjects, have their &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; set to the site-wide default.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;The first step in solving this, is identifying you have a problem in the first place.  So I wrote &lt;a href=&quot;http://github.com/davedash/Title-Variance/tree&quot;&gt;a tool&lt;/a&gt; in python to determine how unique the page titles you have are:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;% python measure.py sphinxsearch.com
1050 titles found for sphinxsearch.com
483 unique titles found for sphinxsearch.com
46% of the pages on sphinxsearch.com have unique titles
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is fairly telling.  It means over half the pages on sphinxsearch.com have a generic title.&lt;/p&gt;

&lt;p&gt;This site faired a bit better:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;% python measure.py spindrop.us
988 titles found for spindrop.us
822 unique titles found for spindrop.us
83% of the pages on spindrop.us have unique titles
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So please, think about people trying to use the information on your site.  Design your templates in such a way that you can come up with unique titles.&lt;/p&gt;

&lt;p&gt;Feel free to expand on this tool, it could easily output the offending pages or titles.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Comprehensive list of international dialing codes</title>
   <link href="http://davedash.com/2009/07/01/list-of-comprehensive-international-dialing-codes/"/>
   <updated>2009-07-01T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/07/01/list-of-comprehensive-international-dialing-codes</id>
   <content type="html">&lt;p&gt;I get bored with mundane tasks.  So I create little adventures for myself.  I had to create a list of countries and country codes to use on the &lt;a href=&quot;http://mozilla.com/mobile&quot;&gt;Firefox mobile home page&lt;/a&gt;.  The first few lists were incomplete, so I made my own by parsing a list provided by the International Telecommunication Union.  I stripped it down to simple forms of the country names and removed codes that are very rare (satellite phones).&lt;/p&gt;

&lt;p&gt;Well this could be a boring task for most people, so I placed it on [github][http://github.com/davedash/International-Dialing-Codes/].  Feel free to use this in your own projects, I even include a perl one-liner in the README to convert this into a drop down HTML list.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Question: Building a Better Search Engine</title>
   <link href="http://davedash.com/2009/06/18/question-building-a-better-search-engine/"/>
   <updated>2009-06-18T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/06/18/question-building-a-better-search-engine</id>
   <content type="html">&lt;p&gt;So I finally have one of those jobs where I can tell people almost every little detail about what I'm doing and I'm encouraged to talk to people on the intar-webs and solicit opinions.&lt;/p&gt;

&lt;p&gt;Uh - this is more or less how I've operated at previous jobs, just now I can be overt about it.&lt;/p&gt;

&lt;p&gt;So my &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=498999&quot;&gt;new task&lt;/a&gt; is to work on improving the &lt;a href=&quot;http://addons.mozilla.org&quot;&gt;addons.mozilla.org&lt;/a&gt; search engine.  I've built various &quot;search engines&quot; over time in PHP, powered by Lucene and most recently in python using an inverted index.&lt;/p&gt;

&lt;p&gt;One tool that I've been looking at briefly is &lt;a href=&quot;http://sphinxsearch.com/&quot;&gt;Sphinx&lt;/a&gt;.  While my record count is low (5-10K), Sphinx basically bakes in a lot of the things I would want in a search engine.  Indexing, merging, etc.&lt;/p&gt;

&lt;p&gt;Since I'm fairly new to the add-ons team I'm still understanding the basics of what we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast automated indexing of addons for Firefox, Thunderbird and any other Mozilla product&lt;/li&gt;
&lt;li&gt;Quick result sets&lt;/li&gt;
&lt;li&gt;Easy deployability&lt;/li&gt;
&lt;li&gt;Extendible&lt;/li&gt;
&lt;li&gt;Customized ranking&lt;/li&gt;
&lt;li&gt;Filtering (e.g. by Firefox version, etc).&lt;/li&gt;
&lt;li&gt;Basics: Stemming and stop-words&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Whether it's Sphinx, Lucene or some home grown solution, I have all that to support.  But this should be fairly straight forward.  What are people's thoughts?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>From Delicious to Mozilla</title>
   <link href="http://davedash.com/2009/05/26/from-delicious-to-mozilla/"/>
   <updated>2009-05-26T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/05/26/from-delicious-to-mozilla</id>
   <content type="html">&lt;p&gt;Today I said my good-byes to Delicious.com and Yahoo! and tonight I went to the &lt;a href=&quot;http://blog.mozilla.com/addons/2009/05/26/add-ons-meetup-tonight/&quot;&gt;Addons Meetup&lt;/a&gt; @ Mozilla to get a sneak peak at what I'll be working on in less than two weeks.&lt;/p&gt;

&lt;p&gt;I was thrilled.  I had no idea how many people to expect, but the Mozilla living room was packed - and most people were there the whole time.  Real developers with really cool addons giving feedback to &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt; directly.  No matter how many blog comments, forums answered, customer care emails I responded to at Delicious - nothing beats the real insight and instant feedback you get from meeting a group of users face to face.&lt;/p&gt;

&lt;p&gt;My brain, because of Delicious is always in data mining and analysis mode so through each presentation and each question asked, my brain was churning through things that I could build to bring some level of utility to the community.&lt;/p&gt;

&lt;p&gt;I'm also happy to be joining an organization where everything is open sourced and available for comment.  So I'm hoping to post a lot more on some of the cool tricks I do at Mozilla.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Reading urlopen and probably any file-ish things in python fast</title>
   <link href="http://davedash.com/2009/05/21/reading-urlopen-and-probably-any-file-ish-things-in-python-fast/"/>
   <updated>2009-05-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/05/21/reading-urlopen-and-probably-any-file-ish-things-in-python-fast</id>
   <content type="html">&lt;p&gt;So I've been churning away in my last few days in Delicious-land trying to optimize some python code.&lt;/p&gt;

&lt;p&gt;I was doing essentially this:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
file = urlopen(&quot;http://myhost.com/my.json&quot;)

for line in file:
  pass

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;and it was taking almost a minute... for a ~3000 line json feed.  Being the n00b that I am, I started getting my python learn on... and thought... an open url connection seems like a bad idea... let's do this:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
file = urlopen(&quot;http://myhost.com/my.json&quot;)
lines = file.read().split(&quot;\n&quot;)

for line in lines:
  pass

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Zoom!  Took less than a second... actually putting real code in their (e.g. &lt;code&gt;simplejson.loads()&lt;/code&gt;) bumped it to 2 seconds.  Hooray.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Python Generators</title>
   <link href="http://davedash.com/2009/04/08/python-generators/"/>
   <updated>2009-04-08T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/04/08/python-generators</id>
   <content type="html">&lt;p&gt;Someone had mentioned &quot;generators&quot; in python to me, so I decided to figure out what it was... and I figured it out.  I think a simple example would help explain it:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
&gt;&gt;&gt; def fib():
...  i1 = 0
...  i2 = 1
...  while True:
...    yield i2
...    i3 = i2 + i1
...    i1 = i2
...    i2 = i3
... 
&gt;&gt;&gt; a = fib()
&gt;&gt;&gt; a
&lt;generator object at 0x319468&gt;
&gt;&gt;&gt; a.next()
1
&gt;&gt;&gt; a.next()
1
&gt;&gt;&gt; a.next()
2
&gt;&gt;&gt; a.next()
3
&gt;&gt;&gt; a.next()
5
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Basically you can iterate over this &quot;generator&quot; which is a special function that &quot;yield&quot;s more than one value if you keep pinging it.&lt;/p&gt;

&lt;p&gt;Adding this to the toolkit in my brain.  I'm thinking one potential use is taking a resultset from a datastore (e.g. mysql) and converting it into an object.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixing the DiggBar with Delicious</title>
   <link href="http://davedash.com/2009/04/02/fixing-the-diggbar-with-delicious/"/>
   <updated>2009-04-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/04/02/fixing-the-diggbar-with-delicious</id>
   <content type="html">&lt;p&gt;I've been tooling around with the &lt;a href=&quot;http://digg.com/http://spindrop.us/&quot;&gt;DiggBar&lt;/a&gt; and I've been quite pleased... but I was a bit annoyed that it was hard to bookmark things in Delicious via the digg bar.  I'd have to either close bar and then bookmark in delicious, or right click on the url in the DiggBar and then change the title.&lt;/p&gt;

&lt;p&gt;I'm fundamentally a lazy programmer, so I wrote &lt;a href=&quot;http://userscripts.org/scripts/show/45716&quot;&gt;some greasemonkey to save me some time&lt;/a&gt;.  Hooray... diggBar+Delicious.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Designing a tagging system: what is tagging</title>
   <link href="http://davedash.com/2009/03/30/designing-a-tagging-system-what-is-tagging/"/>
   <updated>2009-03-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/03/30/designing-a-tagging-system-what-is-tagging</id>
   <content type="html">&lt;p&gt;&lt;img src=&quot;http://farm3.static.flickr.com/2080/1592127385_eca211d6af_m.jpg&quot; class=&quot;alignright&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There's a lot of collective knowledge about tagging and how it works, but everybody seems to have their own way of doing it and understanding it.&lt;/p&gt;

&lt;p&gt;As the delicious engineer who was responsible for making sure the data integrity was maintained during transitions, I ran a series of audit tests on our bookmarks.  This kept me cozy with our tags on a product level but with some insight into the engineering behind it as well.&lt;/p&gt;

&lt;p&gt;I think the biggest struggle with tagging is that its surrounded with &quot;Web2.0&quot; hype.  Technologically tagging is not new.  The way its used is very clever, but its fundamentally simple.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Tagging is search&lt;/h3&gt;

&lt;p&gt;Tagging is no different than keywords assigned to a document.  I'm using document to mean any such object that might be tagged or indexed by a search indexer.   It's clever because rather than using a machine to index a document, or even a publisher to assign keywords, end-users directly assign keywords.  This creates the &quot;folksonomy.&quot;&lt;/p&gt;

&lt;p&gt;Let's say I have a tagging system, and I tag a photograph with the term &quot;milkshake&quot;.  I can conceivably go to a page called &quot;Things tagged as milkshake&quot; and one of the items will be my photograph.  I've just performed a search.&lt;/p&gt;

&lt;p&gt;If this sounds simple, that's because it is.  Tags are a user-instantiated index.  It is different because you can do some cleverly cool things with it.  With delicious, I determine my personal index.  That means, I can very quickly find &lt;a href=&quot;http://delicious.com/davedash/funny+videos&quot;&gt;comical videos&lt;/a&gt; or neat stuff about &lt;a href=&quot;http://delicious.com/davedash/django&quot;&gt;django&lt;/a&gt; without too much trouble.&lt;/p&gt;

&lt;p&gt;The fact that it's &quot;just search&quot; means when you build a tagging system, you can do the same tricks you can do with search.  A mysql database with a tags table can be your search index - or you could use Lucene or Sphinx.&lt;/p&gt;

&lt;p&gt;The point is tagging and retrieving tags is search, so if you can build a search engine, you can build a tagging system.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Optimizing via YSlow</title>
   <link href="http://davedash.com/2009/03/22/optimizing-via-yslow/"/>
   <updated>2009-03-22T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/03/22/optimizing-via-yslow</id>
   <content type="html">&lt;p&gt;I'm not a performance person per se.  I am a heuristics type of guy.  Meaning you tell me a bunch of things to look out for, I get an understanding for those things, and if I agree, I'll fix them.  I work off lists :)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/yslow/&quot;&gt;YSlow&lt;/a&gt; is therefore a boon to me.  If you aren't familiar with it, it is a plugin for Firefox (with a dependency on Firebug) that will grade the performance of a given site based on a set of rules determined by &lt;a href=&quot;http://developer.yahoo.com/&quot;&gt;YDN&lt;/a&gt;.  This is my list, and YDN went into great detail to explain each of these items is important.&lt;/p&gt;

&lt;p&gt;The following is my journey through optimization.  I went from a D- to a C in a single day.  While that might not seem like much, I could have nabbed an A if I used a CDN, like &lt;a href=&quot;http://aws.amazon.com/cloudfront/&quot;&gt;CloudFront&lt;/a&gt;, or even a &lt;a href=&quot;http://spindrop.us/2009/03/08/appengine-is-not-a-free-cdn/&quot;&gt;fake CDN&lt;/a&gt; and dropped Google AdSense and Analytics.  Those tools are helpful for me, and worth keeping around, so I opted to take the grade penalty.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Begin: D - 61&lt;/h3&gt;

&lt;p&gt;I began with a D.  It's actually not that bad.  In most dimensions I was getting an A.  Namely because I use a web framework (Django) that is optimized for being cached and served well.&lt;/p&gt;

&lt;h3&gt;YUI Combo-loading: Sneaking up to D - 62&lt;/h3&gt;

&lt;p&gt;My first goal was to reduce the HTTP requests.&lt;/p&gt;

&lt;p&gt;I use the &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; CSS and javascript framework as the foundation for my CSS and javascript.  One major benefit is that they offer to host this for you on a true &lt;acronym title=&quot;Content Delivery Network&quot;&gt;CDN&lt;/acronym&gt;.  The javascript framework is very thorough and can do quite a lot.  Because it's so robust, it is split into several files.  Originally I had been doing this:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;html&quot;&gt;

&lt;script type=&quot;text/javascript&quot; src=&quot;http://yui.yahooapis.com/2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;http://yui.yahooapis.com/2.6.0/build/connection/connection-min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;http://yui.yahooapis.com/2.6.0/build/datasource/datasource-min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;http://yui.yahooapis.com/2.6.0/build/autocomplete/autocomplete-min.js&quot;&gt;&lt;/script&gt;

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;That was 4 separate calls from the web browser to this server.  While they are all fast requests, it'd be better to make a single request that catches all of this.&lt;/p&gt;

&lt;p&gt;Luckily, YUI has &quot;combo-handling&quot;.  I was able to turn those four requests into the following:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;html&quot;&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&amp;2.7.0/build/connection/connection-min.js&amp;2.7.0/build/datasource/datasource-min.js&amp;2.7.0/build/autocomplete/autocomplete-min.js&quot;&gt;&lt;/script&gt;
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;A very quick fix, that brought my overall grade up by a single point.&lt;/p&gt;

&lt;h3&gt;Concatenation and Minification: Instant C- - 72&lt;/h3&gt;

&lt;p&gt;I still had a large number of HTTP requests.  Luckily &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; has the &lt;a href=&quot;http://developer.yahoo.com/yui/compressor/&quot;&gt;YUI Compressor&lt;/a&gt;.  It compresses javascript and CSS and even gives you tips on writing better code (or more compressible code).&lt;/p&gt;

&lt;p&gt;This used to be scary and not something I'd want to deal with, but if I can run it from the commandline, then I can add it to &lt;a href=&quot;http://spindrop.us/2009/03/02/a-stitch-in-fabric-saves-time/&quot;&gt;my deployment script&lt;/a&gt;.  Now on each deployment my CSS and javascript are concatenated and compressed and uploaded to my server.  When my app is in production mode, it seeks out minified code, versus more verbose human-readable code.&lt;/p&gt;

&lt;p&gt;The javascript compression was great.  The output was at least compressed in half.  This skyrocketed the score to 72.  Less HTTP requests and Javascript minification were the individual rules in YSlow that were boosted.&lt;/p&gt;

&lt;h3&gt;Google: They want you to fail&lt;/h3&gt;

&lt;p&gt;This is of course tongue-in-cheek.  Google has some wonderful tools like AdSense and Analytics that do hinder the score and ever so slightly the speed of your site.&lt;/p&gt;

&lt;p&gt;As far as javascript is concerned, I am linking to:
* YUI
* My own JS
* Google Analytics
* Google Adsense&lt;/p&gt;

&lt;p&gt;Google Adsense is by far the worst offender.  You think you're calling just one solitary call to Google to fetch an ad, but it's actually 4 calls.  I'm sure this could be engineered better.  The good news is it's usually not too slow.&lt;/p&gt;

&lt;p&gt;There's an option to self-serve &lt;code&gt;ga.js&lt;/code&gt; the Analytics code.  While this might result in a higher score, there are a few issues.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The process is more error prone, there's a chance that the file you download from Google might get corrupted and your unit tests might miss it.&lt;/li&gt;
&lt;li&gt;You are serving from your own servers - which could either be expensive or slower.&lt;/li&gt;
&lt;li&gt;Many sites include a call for &lt;code&gt;ga.js&lt;/code&gt; and therefore there's a high likelihood that it's already browser-cached.&lt;/li&gt;
&lt;li&gt;The code could get out of date.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;For this reason, it's better to lose about 5 points for not serving it.  This score is large because it effects a lot of YSlow's rulesets.  If you'd still like to self-serve here's &lt;a href=&quot;http://www.askapache.com/javascript/google-analytics-speed-tips.html&quot;&gt;some discussion on that&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This will likely give you an &quot;F&quot; on the Expires Header ruleset as well.&lt;/p&gt;

&lt;h3&gt;Content Delivery Network (CDN)&lt;/h3&gt;

&lt;p&gt;A lot of people freak that CDN is on YSlow and feel like not all sites need/require a CDN.  They are right that the don't need a CDN, but in my opinion they don't necessarily need to get an &quot;A&quot; either.  All this is an approximation of doing the best you can do to make your frontend code as fast and efficient as possible.&lt;/p&gt;

&lt;p&gt;With that said, CDN's are becoming more and more in reach.  Amazon Cloud Files which &lt;a href=&quot;http://spindrop.us/2009/03/08/appengine-is-not-a-free-cdn/&quot;&gt;I mentioned&lt;/a&gt; is a pay as you go system, which is quite affordable (I estimated &amp;lt; $1/month for my needs).&lt;/p&gt;

&lt;p&gt;I configured YSlow to list some of the standard Google and Yahoo API hosts as CDNs.  This didn't affect my score, and I didn't want to integrate S3/Cloudfiles at this stage, so I took the &quot;F&quot;.&lt;/p&gt;

&lt;h3&gt;Gzip files (still at 72)&lt;/h3&gt;

&lt;p&gt;Adding Gzip to the Nginx configuration was easy, however my grade still stayed at 72, but it did up my score for that specific rule.  Gzip compression is supported in most modern browsers and is usually a config change away.&lt;/p&gt;

&lt;p&gt;Again, note that &lt;code&gt;ga.js&lt;/code&gt; is not served compressed.&lt;/p&gt;

&lt;h3&gt;Upgrading from &lt;code&gt;urchin.js&lt;/code&gt; (up to 73)&lt;/h3&gt;

&lt;p&gt;In the process of tweaking my Gzip configuration, I noticed that I was still using the legacy &lt;code&gt;urchin.js&lt;/code&gt; instead of the more modern &lt;code&gt;ga.js&lt;/code&gt;.  Simply changing this boosted me to 73.&lt;/p&gt;

&lt;h3&gt;Move Javascript to the bottom (still at 73)&lt;/h3&gt;

&lt;p&gt;Moving the javascript to the bottom is another trick.  This at least loads most of the HTML before the Javascript loads (and in many cases, waits).&lt;/p&gt;

&lt;p&gt;Some refactoring of Django templates makes this a breeze.&lt;/p&gt;

&lt;h3&gt;Conclusions&lt;/h3&gt;

&lt;p&gt;I was able to raise the score from a low D to a mid C, at some point I could make the score a high C, but it was inevitably not worth it.&lt;/p&gt;

&lt;p&gt;From my perspective, Google could do a few things to help webmasters who use their widgets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a decent expires header to &lt;code&gt;ga.js&lt;/code&gt; - this file is safe to cache for some degree of time.  Being at the latest and greatest &lt;code&gt;ga.js&lt;/code&gt; is nice, but not necessary.&lt;/li&gt;
&lt;li&gt;Serve files Gzipped&lt;/li&gt;
&lt;li&gt;Use a single host for serving public APIs.  Yahoo can improve on this as well.  They can potentially do combo-hosting a la Yahoo Developer Network&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Ultimately YSlow is a good guideline for speeding up your page render times.  It's not perfect, nor exact.  There's no realistic way to determine a users experience with page load times.  Slow network connectivity and bad browsers can make all your attempts in vain.  However, YSlow is an easy to follow set of heuristics which every site owner should attempt to implement as well as possible.&lt;/p&gt;

&lt;p&gt;YSlow's &lt;a href=&quot;http://www.slideshare.net/stoyan/yslow-20-presentation&quot;&gt;future incarnations&lt;/a&gt; will yield some more flexibility.  Even in its default state it should yield better scores for people who are doing the right thing.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>AppEngine is not a free CDN</title>
   <link href="http://davedash.com/2009/03/08/appengine-is-not-a-free-cdn/"/>
   <updated>2009-03-08T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/03/08/appengine-is-not-a-free-cdn</id>
   <content type="html">&lt;p&gt;I've been doing some personal research with YSlow and looking into CDNs and reading comments on various blogs about CDN solutions.&lt;/p&gt;

&lt;p&gt;I think people make a lot of assumptions as to what is a CDN.  Even people who correctly &lt;a href=&quot;http://24ways.org/2008/using-google-app-engine-as-your-own-cdn&quot;&gt;define what a CDN is&lt;/a&gt;, will later make this mistake.&lt;/p&gt;

&lt;p&gt;To quickly summarize in layman's terms:  A CDN is a system which sends data to a user using nearby servers, rather than a central server.  So your web site might have dynamic content hosted in one location, but a user requesting static assets (images, stylesheets, videos, etc) will have it served from a server that is physically close to them.  So I might have my logo ultimately served from San Jose, whereas someone in New Jersey might have it served from a server in New York.&lt;/p&gt;

&lt;p&gt;This is not the service that Google is offering with AppEngine.  It is not the service you get for hosting with Yahoo's unlimited small business hosting, and it's not even the service you get with Amazon's S3.&lt;/p&gt;

&lt;p&gt;These are all very specific services that don't have any guarantee that the data will be served from a location that is close to the end user.  It's just specialized hosting.&lt;/p&gt;

&lt;p&gt;With that said, all these services are worthwhile if you want to stop serving static content and rely on someone else to do it.  Chances are they can do a better job than you at hosting these static assets, and allow you to focus on serving dynamic content.  The one thing to remember is be sure you are using the right tool for the right job.  AppEngine is great for writing apps on the Google Infrastructure, but it doesn't necessarily offer the same SLA for serving content as S3, or Yahoo! Small Business might.&lt;/p&gt;

&lt;p&gt;Also note, that if you are using S3, and really need a CDN, Amazon offers &lt;a href=&quot;http://aws.amazon.com/cloudfront/&quot;&gt;CloudFront&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Automating Django Redirects</title>
   <link href="http://davedash.com/2009/03/07/automating-django-redirects/"/>
   <updated>2009-03-07T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/03/07/automating-django-redirects</id>
   <content type="html">&lt;p&gt;Django has a very &lt;a href=&quot;http://docs.djangoproject.com/en/dev/ref/contrib/redirects/&quot;&gt;simple redirects system&lt;/a&gt;.  Simple as in it's easy to understand.  If someone encounters a 404, Django Redirects catches this and does a final lookup to see if there's an entry to a new URL in the redirects table.&lt;/p&gt;

&lt;p&gt;The real win, is when your &lt;code&gt;slug&lt;/code&gt; fields change (and thus the &lt;code&gt;get_absolute_url()&lt;/code&gt; of your objects), you can simply automate the creation of a redirect:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
        if self.pk:
            old_version = MyObjectClass.objects.get(pk=self.pk)
            if old_version.stripped_title != self.stripped_title:
                Redirect(site_id=1, old_path=old_version.get_absolute_url(), new_path=self.get_absolute_url()).save()


&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Note you must have enabled the &lt;a href=&quot;http://docs.djangoproject.com/en/dev/ref/contrib/redirects/&quot;&gt;redirects app&lt;/a&gt; before trying this.  You also need a &lt;code&gt;get_absolute_url()&lt;/code&gt; function defined for  your &lt;code&gt;MyObjectClass&lt;/code&gt;.  This is the true value of Django Middleware.  Let those truly repetitive site-wide tasks get done in one spot.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A stitch in Fabric saves time</title>
   <link href="http://davedash.com/2009/03/02/a-stitch-in-fabric-saves-time/"/>
   <updated>2009-03-02T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/03/02/a-stitch-in-fabric-saves-time</id>
   <content type="html">&lt;p&gt;Each day as I grow as a developer I pick up better habits.  One is automating anything that I'll have to do more than once.&lt;/p&gt;

&lt;p&gt;Therefore, deploying important projects has evolved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit files live on the site.&lt;/li&gt;
&lt;li&gt;FTP files from my local machine onto the live site and do tweaking as needed.&lt;/li&gt;
&lt;li&gt;Develop using a cross-platform capable framework and deploy live via rsync.&lt;/li&gt;
&lt;li&gt;One step deployment.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;I looked at a few methods for performing one-step deployment.  Shell scripts seemed to basic, and things like Puppet seemed to large a scale.  So I looked into Capistrano and &lt;a href=&quot;http://www.nongnu.org/fab/&quot;&gt;Fabric&lt;/a&gt;, and settled on &lt;a href=&quot;http://www.nongnu.org/fab/&quot;&gt;Fabric&lt;/a&gt;, because it was barebones and python.&lt;/p&gt;

&lt;p&gt;I'm at a stage with my project where I need to test it on an external server, so rather than uploading it and winging it I actually thought about a few things.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;The task&lt;/h3&gt;

&lt;p&gt;I was thinking the ideal situation is a single command line that I can type that will upload my files to the server.  Furthermore it should upload first to a new directory and then symlink and restart the server to do the cutover.  I can easily write another script to rollback.&lt;/p&gt;

&lt;p&gt;The way I setup my Nginx and Apache servers was they would look for the symlinks: staging, test, production to serve up the respective environments (I'm using a single host for this example).&lt;/p&gt;

&lt;h3&gt;Directory Structure&lt;/h3&gt;

&lt;p&gt;I use a very particular directory structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$WWW_APPS/mysite.com/&lt;/code&gt; - is my root app deployment directory.

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;releases/&lt;/code&gt; - each code push is stored in a unique directory

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100/&lt;/code&gt; - the directory name is the &lt;code&gt;svn&lt;/code&gt; revision, but is a series of precise &lt;code&gt;svn&lt;/code&gt; exports, not a checkout.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging&lt;/code&gt; - this symbolic link links to a specific revision directory that has been designated for staging purposes.  This might be the last directory in &lt;code&gt;releases/&lt;/code&gt;, e.g. &lt;code&gt;$WWW_APPS/mysite.com/releases/100/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/&lt;/code&gt; - any config files go here (e.g. nginx or apache)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysite.com/&lt;/code&gt; - the actual Django project&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/&lt;/code&gt; - any utility scripts, mostly database model changes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site-packages/&lt;/code&gt; - any related libraries that I need, this isn't the best approach I need to investigate &lt;code&gt;virtualenv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static/&lt;/code&gt; - all my static assets go here&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staging.rollback&lt;/code&gt; - the former &lt;code&gt;staging&lt;/code&gt; symlink is demoted to &lt;code&gt;staging.rollback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$WWW/mysite.com/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;staging&lt;/code&gt; - This is where the static assets get served from according to nginx.  It's a symbolic link to &lt;code&gt;$WWW_APPS/staging/static&lt;/code&gt;.  This link does not change during deployment.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uploads_staging/&lt;/code&gt; - This directory contains uploads (user data).  It requires manual adjustment when there are data changes (deletes, adds, updates).  For the most part it can stay unchanged from one deployment to the next.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Subversion&lt;/h3&gt;

&lt;p&gt;I'm currently using subversion to maintain my code.  If you are starting a project anew, I suggest using &lt;code&gt;git&lt;/code&gt; as it is designed with branching in mind.  The script below should be adaptable for &lt;code&gt;git&lt;/code&gt; instead of &lt;code&gt;svn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That being said, when I deploy code, I use the revision number as the directory name for that deployment (e.g. &lt;code&gt;$WWW_APPS/mysite.com/releases/100/&lt;/code&gt; for &lt;code&gt;r100&lt;/code&gt;) .&lt;/p&gt;

&lt;p&gt;This forces me to commit my changes and not deploy code that is not checked in.&lt;/p&gt;

&lt;p&gt;Other viable alternatives would be time-stamped directories.  Anything that is unique between deployments will be sufficient.&lt;/p&gt;

&lt;h3&gt;The &lt;code&gt;fabfile.py&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Here's the &lt;code&gt;fabfile.py&lt;/code&gt; I constructed.  It's by no means final&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
def staging():
    &quot;Pushes current code to staging, hups Apache&quot;
    # get the build number    
    local('svn up mysite.com')
    
    config.svn_version   = svn_get_version()
    
    if not config.svn_version:
        abort()
    
    config.static_path   = '/var/www/static.mysite.com'
    config.svn_path      = 'http://svn.mysite.com/trunk'
    config.svn_export    = 'svn export -q -r %(svn_version)s'
    
    run('mkdir %(path)s', fail='abort')
    
    # svn export mysite.com to path 
    run('%(svn_export)s %(svn_path)s/mysite.com %(path)s/mysite.com', fail='abort')
    
    # svn export site-packages to site-packages
    run('%(svn_export)s %(svn_path)s/site-packages %(path)s/site-packages', fail='abort')
    
    # svn export mysite.com to path 
    run('%(svn_export)s %(svn_path)s/scripts %(path)s/scripts', fail='warn')
    
    # svn export configs
    run('%(svn_export)s %(svn_path)s/config %(path)s/config', fail='abort')
    
    # export /var/www/static.mysite.com/releases/%(svn_version) 
    run('%(svn_export)s %(svn_path)s/static %(path)s/static', fail='abort')
    
    # symlink to images from /var/www/static.mysite.com/staging/images/menuitems/* new release dir
    run(&quot;rm -r %(path)s/static/images/menuitems&quot;, fail=abort)
    run(&quot;ln -s %(static_path)s/menuitems_staging %(path)s/static/images/menuitems&quot;, fail=abort)

    # rotate &quot;staging&quot; symlinks
    run('rm %(releases_path)s/staging.rollback', fail='warn')
    run('mv %(releases_path)s/staging  %(releases_path)s/staging.rollback', fail='warn')

    # staging sym to new destination
    run('ln -s %(path)s %(releases_path)s/staging', fail='abort')
    
    # server is hup'd
    invoke(hup)

def rm_cur_rev():
    config.svn_version   = svn_get_version()
    run('rm -rf %(path)s', fail='abort')

def hup():
    sudo('/etc/init.d/apache2 restart')
    sudo('/etc/init.d/nginx restart')
    
    
def svn_get_version():
    from subprocess import Popen, PIPE
    output = Popen([&quot;svn&quot;, &quot;info&quot;, &quot;mysite.com&quot;], stdout=PIPE).communicate()[0]
    return output.partition('Revision: ')[2].partition('\n')[0]



config.fab_hosts = ['mysite.com']
config.fab_user = 'builder'
config.releases_path = '/var/www_apps/mysite.com'
config.path          = '%(releases_path)s/releases/$(svn_version)'


&lt;/textarea&gt;&lt;/div&gt;


&lt;h3&gt;Final Thoughts&lt;/h3&gt;

&lt;p&gt;There's room for improvement, but this is a start.  &lt;code&gt;virtualenv&lt;/code&gt; looks like another avenue I might want to explore (especially when it comes time to upgrade Django, python, MysqlDB or PIL).  This also doesn't take care of priming the server with necessary packages - again, something &lt;code&gt;virtualenv&lt;/code&gt; could make easier for me.&lt;/p&gt;

&lt;p&gt;However, this file was not too difficult to create.  I sat down, thought about the ideal solution, implemented the steps and iterated on that until the script worked.  I have pushed 19 revisions of code so far, mostly to test this process, but also to make necessary adjustments.  I've probably saved countless hours of logging in and doing things manually.&lt;/p&gt;

&lt;p&gt;How do you deploy your apps?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Resizing Image on upload in Django</title>
   <link href="http://davedash.com/2009/02/21/resizing-image-on-upload-in-django/"/>
   <updated>2009-02-21T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/02/21/resizing-image-on-upload-in-django</id>
   <content type="html">&lt;p&gt;I had trouble wrapping my head around Django ORM's handling of Images.&lt;/p&gt;

&lt;p&gt;The first hurdle was realizing that
&lt;a href=&quot;/2009/02/18/database-versus-files-for-images/&quot;&gt;there is no easy way to store images in the database&lt;/a&gt;.  I couldn't be too upset with that, it was a flawed concept at best.  I wrote a dump script to fix this, and converted my app to read from disk.  One of those &quot;good&quot; problems.&lt;/p&gt;

&lt;p&gt;When it came to uploading new content... I seemed to hit every roadblock.  So I
thought this guide might be of use for people (other than future-me).  What I
want to do is upload an image, resize it per the requirements of my app, and
save the resized image.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;The Template&lt;/h3&gt;

&lt;p&gt;The template was fairly easy, yet I forgot to specify the &lt;code&gt;enctype&lt;/code&gt; attribute.
Here's what you need:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Upload a new photo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;action=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;POST&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;cmxform&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;enctype=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
        
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;id_image&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Photo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;submit&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;Submit&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;h3&gt;Resizing an image&lt;/h3&gt;

&lt;p&gt;Resizing an image was straightforward.  The Python Imaging Library is very easy
to work with, and very similar to the PHP GD Library.  There was one caveat...&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_uploaded_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# resize image&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imageImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imageImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scale_dimensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;longest_side&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;240&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;resizedImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imageImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resizedImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;JPEG&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;I'm using OS X and didn't have libjpeg installed.  The recommended way was to
install it via fink.&lt;/p&gt;

&lt;h3&gt;Generating a filename&lt;/h3&gt;

&lt;p&gt;I like using MD5 to generate filenames for images because MD5 almost guarantees
uniqueness (&quot;unique enough&quot;).&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;   &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getvalue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;.jpg&amp;#39;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;h3&gt;Saving the file via the model&lt;/h3&gt;

&lt;p&gt;The model is supposed to put the files in the right spot and populate the meta data.&lt;/p&gt;

&lt;p&gt;You can supposedly do this via:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;   &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;my_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyDjangoObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;my_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;That will also take care of &lt;code&gt;my_object.save()&lt;/code&gt;.  The trick is that content
needs to be a &lt;code&gt;django.core.files.File&lt;/code&gt; object or things will never work.
Further more, you can't use a StringIO for your &lt;code&gt;File&lt;/code&gt; object.  A true file
object is best.  Hence rewriting the &lt;code&gt;JPEG&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;/tmp&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resizedImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;JPEG&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;/tmp&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;django&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;Note: I also had to reopen the imagefile.&lt;/p&gt;

&lt;h3&gt;All together now&lt;/h3&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;nd&quot;&gt;@login_required&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;my_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_slug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;form&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NewImageForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FILES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_valid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;handle_uploaded_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FILES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpResponseRedirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;elsewhere&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;form&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NewImageForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_to_response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;somewhere&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;locals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                              &lt;span class=&quot;n&quot;&gt;context_instance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RequestContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_uploaded_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# resize image&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imageImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imageImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scale_dimensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;longest_side&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;240&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;resizedImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imageImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resizedImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;JPEG&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getvalue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;.jpg&amp;#39;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# #save to disk&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;/tmp&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resizedImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;JPEG&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;/tmp&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;django&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imagefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;my_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyDjangoObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;my_object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;I'm sure there's room to optimize this flow.  But this gets the job done.  I do
wish I could have used a &lt;code&gt;StringIO&lt;/code&gt; instead of an actual file to save the
image.  Writing to disk twice seems silly.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Database versus files for Images</title>
   <link href="http://davedash.com/2009/02/18/database-versus-files-for-images/"/>
   <updated>2009-02-18T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/02/18/database-versus-files-for-images</id>
   <content type="html">&lt;p&gt;This is a dead topic for sure, but one of the bad web development habits I had picked up was that storing everything in a database made things easier.&lt;/p&gt;

&lt;p&gt;I actually put some effort into thinking this through.  For me it was a case of management.  I didn't want to have to worry about two data stores for application-generated data.  In otherwords, the data in the database was all generated via my web application (either by myself or others who worked on the product, or by end users).  Having to also remember that there were select files as well seemed like a disaster.&lt;/p&gt;

&lt;p&gt;Most frameworks, correctly assume you'll upload files to a filesystem.  I never fully understood this until I thought about how I'd speed things up when the time comes to speed things up.  Django forces you to think that way.&lt;/p&gt;

&lt;p&gt;Almost from the start Django encourages you to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload any binary content to the filesystem with pointers in a database&lt;/li&gt;
&lt;li&gt;Have a separate server, or even machine serve static content.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;Furthermore in a cached environment or even an environment that utilizes a CDN, putting static content in one spot, including user-generated content, was a big win.&lt;/p&gt;

&lt;p&gt;I've been porting an app from symfony to django for some time, and I had been serving images via the database.  Immediately when I switched to the filesystem I saw a huge benefit.  Not just a drop in database connections, but overall &quot;zippiness&quot; in the site.&lt;/p&gt;

&lt;p&gt;We'll see how well this performs in the real world, but I am quite sure that I learned my lesson.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Versioning Django Models</title>
   <link href="http://davedash.com/2009/02/13/versioning-django-models/"/>
   <updated>2009-02-13T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/02/13/versioning-django-models</id>
   <content type="html">&lt;p&gt;In symfony, versioning a model was not terribly difficult.  I had my own specialized brute-force way of doing this.&lt;/p&gt;

&lt;p&gt;In my experience it's been a lot easier to write python code than PHP code, so naturally I figured this would be an easy task.  It was not.  I suspect that it was not easy because I have a naive understanding of Django and my symfony knowledge was fairly well grounded.&lt;/p&gt;

&lt;p&gt;I tried looking for a generic implementation of django model versioning, but failed.  So I came up with a specific solution.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;How I think of versions&lt;/h3&gt;

&lt;p&gt;For my app I chose to use a sparse versioning system.  I'd have one interface for interacting with a model.  For example, I have a &lt;code&gt;Restaurant&lt;/code&gt; model  and a &lt;code&gt;RestaurantVersion&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Restaurant&lt;/code&gt; would directly store immutable elements like &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;rating&lt;/code&gt; or &lt;code&gt;approved&lt;/code&gt; and it would encapsulate versioned elements like &lt;code&gt;description&lt;/code&gt; by proxying to &lt;code&gt;RestaurantVersion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means I only need to interface with one class and let the versioning happen behind the scenes.&lt;/p&gt;

&lt;h3&gt;The Django implementation of &lt;code&gt;RestaurantVersion&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RestaurantVersion&lt;/code&gt; held the data that I thought might suffer from corruption, and therefore require reversion.  Therefore there's nothing surprising in this table:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
class RestaurantVersion(models.Model):
    restaurant       = models.ForeignKey('Restaurant', null=True, blank=True)
    user             = models.ForeignKey(Profile, null=True, blank=True)
    description      = models.TextField(blank=True)
    html_description = models.TextField(blank=True)
    url              = models.CharField(max_length=765, blank=True)
    created_at       = models.DateTimeField(auto_now_add=True)
    
    def save(self, force_insert=False, force_update=False):
        self.html_description = markdown(self.description)
        super(RestaurantVersion, self).save(force_insert, force_update)
        
    class Meta:
        db_table = u'restaurant_version'

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The only thing of note is that I'm enforcing a &lt;code&gt;1:M&lt;/code&gt; relation between &lt;code&gt;Restaurant&lt;/code&gt; and &lt;code&gt;RestaurantVersion&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;The Django implementation of &lt;code&gt;Restaurant&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Restaurant&lt;/code&gt; is where the magic happens.  It needs to do a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do what a normal object does.&lt;/li&gt;
&lt;li&gt;Proxy attributes to the corresponding attribute of a &lt;code&gt;RestaurantVersion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Generate a new &lt;code&gt;RestaurantVersion&lt;/code&gt; on demand.&lt;/li&gt;
&lt;li&gt;(optional) Manage which version is &quot;active&quot;.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;I say &quot;4&quot; is optional because I don't do this myself.  I built versioning into a few models because it was easier to do upfront than worry about it down the road.&lt;/p&gt;

&lt;p&gt;Here's the code I use:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;

class Restaurant(models.Model):
    name           = models.CharField(max_length=765, blank=True)
    stripped_title = models.CharField(max_length=384, blank=True)
    approved       = models.IntegerField(null=True, blank=True)
    version        = models.ForeignKey(RestaurantVersion, related_name=&quot;the_restaurant&quot;)
    updated_at     = models.DateTimeField(auto_now=True)
    created_at     = models.DateTimeField(auto_now_add=True)
    new_version    = None
    
    def save(self, force_insert=False, force_update=False):
        if not self.stripped_title:
            self.stripped_title = slugify(self.name)
        
        super(Restaurant, self).save(force_insert, force_update)
        if self.new_version:
            self.new_version.restaurant = self
            self.new_version.save()
            self.version = self.new_version
            super(Restaurant, self).save(force_insert, force_update)        
        
    def __setattr__(self, name, value):
        if name == 'description':
            self.get_new_version().description = value
        
        elif name == 'url':
            self.get_new_version().url = value
        
        else:
            object.__setattr__(self, name, value)

    def __getattr__(self, name):
        try:
            if name == 'description':
                return self.version.description

            elif name == 'url':
                return self.version.url
        except RestaurantVersion.DoesNotExist:
            return ''
            
        models.Model.__getattribute__(self, name)
    
    def get_new_version(self):
        if self.new_version == None:
            try:
                rv    = self.version
                rv.id = None
            except RestaurantVersion.DoesNotExist:
                rv = RestaurantVersion()
            
            self.new_version = rv

        return self.new_version
            
    class Meta:
        db_table     = u'restaurant'
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;It's not rocket science, but it wasn't necessarily easy either.  Let's look at requirements 2 and 4 in a bit more detail.&lt;/p&gt;

&lt;h4&gt;Proxy attributes to the corresponding &lt;code&gt;RestaurantVersion&lt;/code&gt; attributes&lt;/h4&gt;

&lt;p&gt;Proxying attributes is a way of masking the whole versioning infrastructure from the developer.  We do this with &lt;code&gt;__getattr__&lt;/code&gt; and &lt;code&gt;__setattr__&lt;/code&gt; methods.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def __getattr__(self, name):
        try:
            if name == 'description':
                return self.version.description

            elif name == 'url':
                return self.version.url
        except RestaurantVersion.DoesNotExist:
            return ''
            
        models.Model.__getattribute__(self, name)
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;__getattr__&lt;/code&gt; determins if you are looking for &lt;code&gt;description&lt;/code&gt; or &lt;code&gt;url&lt;/code&gt; attributes of a &lt;code&gt;Restaurant&lt;/code&gt;.  If you are then it uses the current &lt;code&gt;RestaurantVersion&lt;/code&gt;'s attribute.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def __setattr__(self, name, value):
        if name == 'description':
            self.get_new_version().description = value
        
        elif name == 'url':
            self.get_new_version().url = value
        
        else:
            object.__setattr__(self, name, value)
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;__setattr__&lt;/code&gt; is very similar, except since we're changing a versionable attribute we're going to make a request to a method, &lt;code&gt;get_new_version()&lt;/code&gt;, which we will detail later.  It does what it says, though, it gets the &quot;new&quot; &lt;code&gt;RestaurantVersion&lt;/code&gt; of the &lt;code&gt;Restaurant&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;Generate a new &lt;code&gt;RestaurantVersion&lt;/code&gt; on demand&lt;/h4&gt;

&lt;p&gt;As you can see from the above code, I am &quot;auto-versioning&quot;.  This is done mostly via &lt;code&gt;get_new_version()&lt;/code&gt;.  We also have to do a few tricks to make sure the bidirectional relationship gets maintained on save.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def get_new_version(self):
        if self.new_version == None:
            try:
                rv    = self.version
                rv.id = None
            except RestaurantVersion.DoesNotExist:
                rv = RestaurantVersion()
            
            self.new_version = rv

        return self.new_version
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;get_new_version()&lt;/code&gt; either returns the current &quot;new version&quot;, a brand new &quot;new version&quot; or a &quot;copy&quot; of the current version.&lt;/p&gt;

&lt;p&gt;The &quot;copy&quot; is done simply by setting the &lt;code&gt;id&lt;/code&gt; attribute of the new version to &lt;code&gt;None&lt;/code&gt;.  Django takes care of assigning it a new &lt;code&gt;id&lt;/code&gt; on save, thus preserving the old version.&lt;/p&gt;

&lt;p&gt;Note that when we create a brand new &lt;code&gt;RestaurantVersion&lt;/code&gt;, we don't immediately set it's &lt;code&gt;restaurant&lt;/code&gt; attribute.  That's because we haven't saved the current &lt;code&gt;restaurant&lt;/code&gt; yet.  It's a &quot;chicken and the egg&quot; problem that gets solved in our &lt;code&gt;save()&lt;/code&gt; method:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def save(self, force_insert=False, force_update=False):
        if not self.stripped_title:
            self.stripped_title = slugify(self.name)
        
        super(Restaurant, self).save(force_insert, force_update)
        if self.new_version:
            self.new_version.restaurant = self
            self.new_version.save()
            self.version = self.new_version
            super(Restaurant, self).save(force_insert, force_update)
        
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We first save the &lt;code&gt;Restaurant&lt;/code&gt;, then we save a new version if there is one.  If we save a new version we want to update the &lt;code&gt;Restaurant&lt;/code&gt; a second time.  This ensures that there's a &lt;code&gt;1:1&lt;/code&gt; relationship between &lt;code&gt;Restaurant&lt;/code&gt; and the current &lt;code&gt;RestaurantVersion&lt;/code&gt; and it also establishes a &lt;code&gt;1:M&lt;/code&gt; relationship between &lt;code&gt;Restaurant&lt;/code&gt; and &lt;em&gt;all&lt;/em&gt; &lt;code&gt;RestaurantVersion&lt;/code&gt;s.&lt;/p&gt;

&lt;h3&gt;Conclusion and Drawbacks&lt;/h3&gt;

&lt;p&gt;While, I think that this setup works in my particular situation, I feel like it has some major flaws.&lt;/p&gt;

&lt;p&gt;The code is quite messy and very specific to this model.  My goal was to take care of this quickly, not necessarily in a reusable manner.  I also was not familiar enough with the Django model to create some sort of extension.&lt;/p&gt;

&lt;p&gt;This way of versioning does not allow for versioned attributes to be set upon instantiation of the model:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;r=Restaurant(description=&quot;Great place&quot;) 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;won't work.  You'll have to do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;r=Restaurant()
r.description=&quot;Great place&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I figured this was acceptable.&lt;/p&gt;

&lt;p&gt;Lastly I'm not entirely happy with having to explicitly save the &lt;code&gt;Restaurant&lt;/code&gt; model twice, but I think my bidirectional relation requires this.&lt;/p&gt;

&lt;p&gt;All in all this works in my particular situation.  I'm hoping that this can be simplified.  I'm curious to hear about other versioning schemes.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dear Banks, Stop Encouraging Bad Security</title>
   <link href="http://davedash.com/2008/12/16/dear-banks-stop-encouraging-bad-security/"/>
   <updated>2008-12-16T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/12/16/dear-banks-stop-encouraging-bad-security</id>
   <content type="html">&lt;p&gt;I use an online personal finance site that connects to all my financial accounts and aggregates my transaction history.  I love it, it's very useful, and it keeps me financially organized.&lt;/p&gt;

&lt;p&gt;The part that annoys me is that most of these personal finance sites require you to supply your username and password for all your bank accounts.  For some banks it also requires your social security number, the last five people you've slept with, your home town, your favorite color, etc, etc.  Basically all the pesky sign in questions your bank might ask you when you log in.&lt;/p&gt;

&lt;p&gt;This is a cruel necessity for companies like &lt;a href=&quot;http://geezeo.com/&quot;&gt;Geezeo&lt;/a&gt;, &lt;a href=&quot;http://mint.com/&quot;&gt;Mint&lt;/a&gt;, Ameriprise and Quicken Online in order to provide this aggregation service and a scary proposition for people like us who use these services.  You're giving full unfettered access to companies you may not have ever heard of to all your finances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security questions, and personalized security questions are the wrong way to fix bank security.&lt;/strong&gt;&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;People want online personal finance sites.  They want all their data in a single place without having to jump through a bazillion hoops for each and every 401K, savings account, checking account, online stock trading system and mortgage account.  They will gladly sacrifice security for a chance to better their financial management capabilities.&lt;/p&gt;

&lt;p&gt;Banks need to create APIs so third-party software can access transaction data.  The authentication for this should be secure, limited and revokable.  Meaning, I may authorize &lt;a href=&quot;http://mint.com/&quot;&gt;Mint&lt;/a&gt; to see my Bank of America account, but at any time I can log on to BoA and deny Mint's ability to see my transaction data.  OAuth may be one mechanism to achieve this.&lt;/p&gt;

&lt;p&gt;This will achieve a few things:
* People won't give out their passwords online to anybody but their bank.
* Getting data into these aggregating sites will be reliable and secure.
* At any time you can see who has access to your transaction data and revoke it.&lt;/p&gt;

&lt;p&gt;Please banks, do your part to keep the internet secure.  &lt;a href=&quot;http://mint.com/&quot;&gt;Mint&lt;/a&gt;, &lt;a href=&quot;http://geezeo.com/&quot;&gt;Geezeo&lt;/a&gt; and anybody else, please do your part of turning up the pressure on financial institutions and when the time comes... please start using these APIs.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>PHPs strengths: array_count_values</title>
   <link href="http://davedash.com/2008/12/07/phps-strengths-array_count_values/"/>
   <updated>2008-12-07T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/12/07/phps-strengths-array_count_values</id>
   <content type="html">&lt;p&gt;I always like to think of what different interpreted programming languages bring to the table.&lt;/p&gt;

&lt;p&gt;Perl is great with string manipulation.  I tend to use perl if I need to rewrite/reformat text.&lt;/p&gt;

&lt;p&gt;Python makes it easy to write clean, organized code and has a lot of syntactical sugar like list comprehensions.&lt;/p&gt;

&lt;p&gt;For PHP one of my coworkers suggested that maybe it's ease is what it brings to the table.  I've accepted that wryly at first, but after some thought it's actually a real strength.&lt;/p&gt;

&lt;p&gt;PHP has positioned itself to be easily adoptable.  When I learned PHP/FI 2.0 it was because I had exhausted server-side includes and needed something better.  PHP/FI was a great solution because it was easy to install (my ISP had it) and documented easy things that I could do.  After all it was just some additional changes to my templates.&lt;/p&gt;

&lt;p&gt;I never for once thought that I'd be paid at a company to code in it... but then the internet just got big.&lt;/p&gt;

&lt;p&gt;The other way php positioned itself is the library of functions it has out of the box.  Today I found myself looking for a python equivalent to &lt;a href=&quot;http://www.php.net/array_count_values&quot;&gt;&lt;code&gt;array_count_values&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Its a function I wouldn't use every day, but there are rare instances where having a set of data aggregated by count would be very useful.  That's exactly what this function does.&lt;/p&gt;

&lt;p&gt;Even python's lists, sets and dictionaries, which can do with simple operations what PHP needs an army of &lt;code&gt;array_&lt;/code&gt; functions to do, didn't reveal anything useful.  So I will use the following:&lt;/p&gt;

&lt;div&gt;
&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
def list_count_values(l):
    d = {}
    
    for item in l:
        if item in d:
            d[item] += 1
        else:
            d[item] = 1
    
    return d
&lt;/textarea&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Python String Formatting</title>
   <link href="http://davedash.com/2008/12/06/python-string-formatting/"/>
   <updated>2008-12-06T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/12/06/python-string-formatting</id>
   <content type="html">&lt;p&gt;Python 2.6 (and Py3K) introduce a new way to format strings.  Perviously you did this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&quot;%s, Here is my string, %s!&quot; % ('Salutations', 'Dave')
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now you do this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&quot;{0}, Here is my string, {1}!&quot;.format('Salutations','Dave')
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At first glance this looks like syntactical-sugar.  But now you can do some clever things (which you could have done before, but I think are more intuitive now).  You can easily repeat strings:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def tag(name, value):
    return &quot;&amp;lt;{0}&amp;gt;{1}&amp;lt;/{0}&amp;gt;&quot;.format(name, value)
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>Mimic propel's update_at and created_at in Django models</title>
   <link href="http://davedash.com/2008/11/29/mimic-propels-update_at-and-created_at-in-django-models/"/>
   <updated>2008-11-29T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/29/mimic-propels-update_at-and-created_at-in-django-models</id>
   <content type="html">&lt;p&gt;One &quot;trick&quot; that propel offers you is tables with fields &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fields are self explanatory and within Propel you just define them and they just do their business.&lt;/p&gt;

&lt;p&gt;To simulate this in Django just do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;auto_now&lt;/code&gt; automatically sets a field to the current time, and &lt;code&gt;auto_now_add&lt;/code&gt; only does this if the object is being added (not updated).&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Django models: saving markdown</title>
   <link href="http://davedash.com/2008/11/29/django-models-saving-markdown/"/>
   <updated>2008-11-29T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/29/django-models-saving-markdown</id>
   <content type="html">&lt;p&gt;I love markdown.  I write my blogs in markdown.  For most user text areas in my web apps I support markdown and save both the markdown and the formatted text into my data store.&lt;/p&gt;

&lt;p&gt;To do this I had to install &lt;a href=&quot;http://www.freewisdom.org/projects/python-markdown/Installation&quot;&gt;markdown for python&lt;/a&gt;.  For my Django projects I prefer downloading (or externally linking) to source code versus &lt;code&gt;easy_install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then you override your save model like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from markdown import markdown

class MyNote(models.Model):
    def save(self, force_insert=False, force_update=False):
        self.html_note = markdown(self.note)
        super(MyNote, self).save(force_insert, force_update)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Easy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Custom error messages for Django forms</title>
   <link href="http://davedash.com/2008/11/28/custom-error-messages-for-django-forms/"/>
   <updated>2008-11-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/28/custom-error-messages-for-django-forms</id>
   <content type="html">&lt;p&gt;For some reason, it was difficult for me to find the documentation for this.  If your Django form field is required you'll by default get an error stating that 'This field is required.'  You can easily replace that when defining your form like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class ReviewForm(forms.Form):
    rating = forms.ChoiceField(choices=STARS, error_messages={'required': 'Please choose a star rating'})
    note   = forms.CharField(widget=forms.Textarea(),)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;error_messages&lt;/code&gt; is just a simple dictionary of validation messages that override the default.&lt;/p&gt;

&lt;p&gt;While this is properly documented it was not quickly searchable.  But the &lt;a href=&quot;http://docs.djangoproject.com/en/dev/ref/forms/fields/&quot;&gt;django form fields reference page&lt;/a&gt; documents this well.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfGuardUser -> django.contrib.auth</title>
   <link href="http://davedash.com/2008/11/20/sfguarduser-djangocontribauth/"/>
   <updated>2008-11-20T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/20/sfguarduser-djangocontribauth</id>
   <content type="html">&lt;p&gt;If you find yourself moving from symfony to Django, here's how you &lt;code&gt;sf_guard_user&lt;/code&gt;'s user table to &lt;code&gt;django.contrib.auth&lt;/code&gt; user table:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;INSERT INTO 
    auth_user (id, username, password, is_active, last_login, date_joined)
SELECT 
    id, 
    username, 
    CONCAT(algorithm,'$', salt, '$', password), 
    1, 
    last_login, 
    created_at 
FROM sf_guard_user;

DROP TABLE sf_guard_user;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Luckily, django uses a similar salting and encryption strategy as symfony, so it's easy to go back and forth.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Picking up loose ends: Position Pieces</title>
   <link href="http://davedash.com/2008/11/17/picking-up-loose-ends-position-pieces/"/>
   <updated>2008-11-17T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/17/picking-up-loose-ends-position-pieces</id>
   <content type="html">&lt;p&gt;I'm picking up a pet project of mine that I was working on a few months back.  It's written in Django and is openID enabled... but I didn't used &lt;a href=&quot;http://code.google.com/p/django-openid/&quot;&gt;django openID&lt;/a&gt; and I have no clue why... I wrote my own code.&lt;/p&gt;

&lt;p&gt;My best guess is I wrote my own code, just so I could understand it better, and that perhaps &lt;a href=&quot;http://code.google.com/p/django-openid/&quot;&gt;django openID&lt;/a&gt; wasn't mature enough or documented well enough.&lt;/p&gt;

&lt;p&gt;Whatever the reason, in the future when I take the non obvious route... that I actually document the &quot;why?&quot;  This will save me the tedious task of determining if I need to swap out my code and replace it with something that's more maintained.&lt;/p&gt;

&lt;p&gt;In any case, it's certainly refreshing to tool around with Django some more... it's a lot more pleasant than the PHP that I'm often subjected to...&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Django Admin and Cookies</title>
   <link href="http://davedash.com/2008/11/15/django-admin-and-cookies/"/>
   <updated>2008-11-15T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/15/django-admin-and-cookies</id>
   <content type="html">&lt;p&gt;I was dusting off an old Django project and everything was working except the admin site:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The culprit was &lt;code&gt;SESSION_COOKIE_DOMAIN = &quot;onyxfoundation.org&quot;&lt;/code&gt; being set.&lt;/p&gt;

&lt;p&gt;The project wasn't loading into Development mode (it's triggered by hostname) so it set the cookie domain to what I require in production.  Once remedied, everything worked as expected.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Google Apps: In search of a worthy email system</title>
   <link href="http://davedash.com/2008/10/12/google-apps-in-search-of-a-worthy-email-system/"/>
   <updated>2008-10-12T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/10/12/google-apps-in-search-of-a-worthy-email-system</id>
   <content type="html">&lt;p&gt;Email, in a lot of ways, is one of the most critical applications for me.&lt;/p&gt;

&lt;p&gt;There was a point in my life where I made sure all my email was kept together indefinitely, but as time grew, so did this task.&lt;/p&gt;

&lt;p&gt;For the longest time, I had my own hosted @davedash.com, @ftmax.com addresses.  These were hosted diligently at pair Networks.  pair introduced the qmail system which gave birth to my trend of using &quot;minus addressing&quot; (if your email is user@domain.com you can use user-&lt;em&gt;anything&lt;/em&gt;@domain.com).  It was my way of seeing which sites spammed me (so far I only outed one company using this trick) and just to organize my inbox.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Gmail...&lt;/h3&gt;

&lt;p&gt;In 2004, I decided to experiment with Gmail.  I had a lot of email to manage and Gmail's ajax-enabled features made its webmail service far superior to any competitors and even made desktop apps cower in shame.&lt;/p&gt;

&lt;p&gt;The ideology of search don't sort, and archive don't delete, made Gmail a perfect storage system for all my emails.  I never had to lose track of email again.  Just about any conversation, or discussion I've had over email - I can pull up very quickly using Gmail.&lt;/p&gt;

&lt;p&gt;I'd say after 4 years of solid use, the experiment is over, I was sold on Gmail.&lt;/p&gt;

&lt;h3&gt;Switching hosts.&lt;/h3&gt;

&lt;p&gt;I did, however, move my domain management over to Dreamhost.  This is where it still resides, and that was a problem.  Minus addressing was not a part of Dreamhost.  I had to painstakingly create procmail files that simulated this.  Inevitably pushing anything and everything to my gmail account if it matched the syntax I chose.&lt;/p&gt;

&lt;p&gt;I knew Gmail supported plus addressing (user&lt;strong&gt;+&lt;/strong&gt;&lt;em&gt;anything&lt;/em&gt;@gmail.com), but I never took the plunge.  My habit, was and is still minus style.&lt;/p&gt;

&lt;h3&gt;Google Apps&lt;/h3&gt;

&lt;p&gt;The Dreamhost account I used has been tossed from one server to the next, unfortunately there's a lot of hand-holding I need to do in order to make sure procmail and all my other tweaks I've made to my accounts work.  I finally threw in the towel today and settled on Google Apps.&lt;/p&gt;

&lt;p&gt;Both my wife and I use Gmail (with aliased emails @katiebonn.com and @davedash.com respectively), so I enabled an app and did the painstaking &lt;a href=&quot;http://glomerate.wordpress.com/2008/08/20/migrating-from-gmail-to-google-apps/&quot;&gt;migration process&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While it's not that complicated, you don't ever realize how entangled your Google account can be.  We both use GTalk and Google Calendar and we both have large Gmail accounts.  Mine has 70K+ addresses and is slowly being migrated to my Google Apps email address.&lt;/p&gt;

&lt;p&gt;In the end, I think this struggle will be worth it, since I'll have my addresses managed in a clean efficient way.&lt;/p&gt;

&lt;p&gt;This will also pave the path for me to move more of my web sites off of Dreamhost and onto Slicehost.&lt;/p&gt;

&lt;p&gt;Wish me luck.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Creating a new project in Mercurial versus SVN</title>
   <link href="http://davedash.com/2008/07/22/creating-a-new-project-in-mercurial-versus-svn/"/>
   <updated>2008-07-22T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/07/22/creating-a-new-project-in-mercurial-versus-svn</id>
   <content type="html">&lt;p&gt;One of the most annoying things about creating new SVN projects is the new project dance:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ svnadmin create $SVNREP_DIR/newproject
$ svn mkdir -m &quot;layout creation&quot; file:///$SVNREP_DIR/newproject/trunk file:///$SVNREP_DIR/newproject/tags file:///$SVNREP_DIR/newproject/branches
$ mkdir newproject
$ cd newproject
$ svn import -m &quot;initial import&quot; . file:///$SVNREP_DIR/newproject/trunk
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Oh... yeah, no this isn't that bad.&lt;/p&gt;

&lt;p&gt;It's this part that's bad:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd ..
$ mv newproject newproject.tmp
$ svn co file:///$SVNREP_DIR/newproject/trunk newproject
$ # do sanity check here...
$ rm -rf newproject.tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's a lot of work, and it's actually hard to remember unless you do it a lot.&lt;/p&gt;

&lt;p&gt;Here's the mercurial equivalent:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ mkdir -p newproject/trunk
$ cd newproject
$ mkdir branches tags
$ hg init
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's it.  It's not apples to apples of course.  Mercurial keeps its repository locally, and does very simple push/pull commands for synchronization.  But the dreaded dance of removing a project after it's been imported and then checking out the project is really a peeve of mine.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Windows Mobile 6.1 ROM for Mogul</title>
   <link href="http://davedash.com/2008/07/06/windows-mobile-61-rom-for-mogul/"/>
   <updated>2008-07-06T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/07/06/windows-mobile-61-rom-for-mogul</id>
   <content type="html">&lt;p&gt;A &lt;a href=&quot;http://www.phonenews.com/phones/index.php/HTC_PPC-6800_/_XV6800_/_Mogul_/_P4000_/_Titan&quot;&gt;leaked ROM for the Sprint Mogul&lt;/a&gt; featuring Windows Mobile 6.1 was circulating the internet and many of my issues with Windows Mobile were pushing me to upgrading.  For me it fixes a few things with the GPS and improves the usability of texting.&lt;/p&gt;

&lt;h3&gt;GPS&lt;/h3&gt;

&lt;p&gt;The biggest issue I was having with the Sprint Mogul was the GPS would crash continuously... forcing me to restart my device.  Restarting any phone tends to take forever, so I was eager to have this problem minimized.  The issue was that if an app still had the GPS open, and the machine went to sleep, upon waking the GPS driver would crash.&lt;/p&gt;

&lt;p&gt;Something similar still happens in 6.1, but its way more resilient.  Instead of restarting every day, I find myself restarting once or twice every several weeks - and that's after heavy GPS usage.&lt;/p&gt;

&lt;h3&gt;SMS Notifications&lt;/h3&gt;

&lt;p&gt;The Mogul used to vibrate and alert that your SMS message has sent.  There was a registry hack to disable this for versions prior to 6.1, but that's no longer necessary.&lt;/p&gt;

&lt;p&gt;The downside to the hack was you wouldn't have any sort of feedback that a message was sent.  However, Pocket Outlook has adopted a chat style interface for displaying text messages.  So now texts look like conversations - which make it very easy to have tiny conversations with people.&lt;/p&gt;

&lt;h3&gt;Do it...&lt;/h3&gt;

&lt;p&gt;Flashing the ROM is easy since Sprint (apparently) gives you a Rom Update Utility (Windows executable unfortunately) that does all the dirty work.  I backed up my data using Missing Sync and then started the update process in a virtual machine.&lt;/p&gt;

&lt;p&gt;Restoring my default settings and apps was a bit of a pain, but overall it's a decent upgrade and fixes my biggest gripes.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The Sprint SERO plan</title>
   <link href="http://davedash.com/2008/07/02/the-sprint-sero-plan/"/>
   <updated>2008-07-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/07/02/the-sprint-sero-plan</id>
   <content type="html">&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;So originally I was thinking of getting the new iPhone, because they had a &lt;a href=&quot;http://spindrop.us/2008/05/12/smart-pricing-for-iphone-server/&quot;&gt;competitive rate plan&lt;/a&gt;, but they later dropped that with the new 3Gness.  Simple math tells you it's &lt;a href=&quot;http://gizmodo.com/5015540/iphone-3gs-true-price-compared&quot;&gt;not that great of a deal over two years&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking at the comments &lt;a href=&quot;http://gizmodo.com/5015540/iphone-3gs-true-price-compared&quot;&gt;in the Gizmodo article&lt;/a&gt;, I learned about &lt;a href=&quot;http://sprint.com/sero&quot;&gt;Sprint SERO&lt;/a&gt; and proceeded to find out &lt;a href=&quot;http://delicious.com/davedash/sero&quot;&gt;as much as I could about it&lt;/a&gt;.  &lt;a href=&quot;http://sprint.com/sero&quot;&gt;SERO&lt;/a&gt; is the Sprint Exclusive Referral Offer which is a data-enabled plan with 500 minutes and unlimited texts for $30/mo.  That's less than what I pay with a family plan now (with no data or texts).  There are slight variations if more minutes are needed.  500 was enough for me, so I was on board.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Sprint SERO&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;http://sprint.com/sero&quot;&gt;SERO plan&lt;/a&gt; is simple to get, you just need to enter an email address of a Sprint employee to order phones and plans from the web site or by calling Sprint SERO by phone.  It's fairly easy to find these by doing some simple navigating on Sprint.com or ask around you're bound to know someone who knows someone at Sprint.&lt;/p&gt;

&lt;p&gt;Dealing with Sprint customer service can be a mixed bag.  Unlike T-mobile not everybody seems helpful.  Many people there seem to be reading off a script and not deviating a bit, so it's hard to get a real conversation.  But some people really know their stuff and can help you in a pinch and really want to help you.&lt;/p&gt;

&lt;p&gt;When you activate your phone make sure you write down every detail they tell you.  You'll get an NSL code that lets you re-activate your device over and over if you have to flash it, without having to call them up.&lt;/p&gt;

&lt;p&gt;Also, if you're porting your phone number do it ASAP.&lt;/p&gt;

&lt;h3&gt;Porting nightmare&lt;/h3&gt;

&lt;p&gt;I had a bit of a porting nightmare with my phones.  I had two SERO lines on one account and they ported both my numbers to the same phone line.  After several email exchanges and several phone calls everything was restored.  Basically of WLNP goes awry they can fix it.  I was without my 612 number for a day or so.&lt;/p&gt;

&lt;p&gt;Everything is restored now, but in the future I would have either ported both lines at once (I waited a day or two between porting) or created two separate accounts and then combined them at a later time.&lt;/p&gt;

&lt;h3&gt;Other discounts&lt;/h3&gt;

&lt;p&gt;You can dial #733 and enter in a current Sprint customer and get a $25 gift card (and the referrer also gets one).  If you need a Sprint number, I can give you mine.&lt;/p&gt;

&lt;p&gt;Generally Sprint will not give you much more beyond this since they view SERO as a huge discount (and it is in comparison to other providers).&lt;/p&gt;

&lt;h3&gt;Get it!&lt;/h3&gt;

&lt;p&gt;Seriously, as much as I hate not having a GSM phone, Sprint's SERO makes a lot of sense money-wise.  The data has been excellent and the customer service has been good, even if they do make mistakes.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Open season for headhunting Yahoos</title>
   <link href="http://davedash.com/2008/06/07/open-season-for-headhunting-yahoos/"/>
   <updated>2008-06-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/06/07/open-season-for-headhunting-yahoos</id>
   <content type="html">&lt;p&gt;None of my resumes floating around on the internet have my current job listed because I get enough calls and emails (mostly from Minneapolis, but some from the SF Bay Area) from recruiters attempting to help me find a job.&lt;/p&gt;

&lt;p&gt;Here's an email my coworker received that I thought I'd translate.  I removed the name of my coworker and the firm who wrote the email.  I really don't want backlash from them, since they harass me enough as it is.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Hi [Name removed],&lt;/p&gt;

&lt;p&gt;Since you've communicated with [Company name removed] in the past, I wanted to touch base and get caught up with you on your potential career advancement.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Since your name appeared on a job board in the last 5 years, or we bought an email list of job seekers we wanted to see if we could be the custodians of your resume and make a few grand by getting you to quit your job and start somewhere else.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;We’ve been placing many of your Yahoo colleagues lately, and are currently working with many Yahoo employees on their passive job searches, both at the individual contributor level and Management/Director level.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;We've placed two or more people from Yahoo at other jobs, can we count on you're resume pulling in some big money too?&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Based on these dynamics and the information we have gathered, it looks like the timing is looking better and better to get out of Yahoo!  Even if Yahoo is trying to bribe you into staying with promotions, counteroffers, or severance packages, don’t be fooled.  The smartest people will leave now.  One example:  we had a candidate making 160k at Yahoo that would have gotten a 1 year  severance package if he stayed till after the Microsoft acquisition, but he still left and is happily placed at a much better company.&lt;/p&gt;

&lt;p&gt;Consider this:   Before the last round of layoffs, we were usually able to get Yahoo candidates significant pay raises and multiple offers.  After the latest round, Yahoo candidates’ value has gone down because there are more of them available in the market and Yahoo has continued to lose respect in the industry.  We foresee this trend continuing through the Microsoft Acquisition, when there will probably be another round of layoffs and voluntary departures of your colleagues.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;It's simultaneously getting better and better to leave Yahoo! while at the same time, it's becoming not-so &quot;better and better&quot; to leave Yahoo!  The market economy is confusing!&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;This all means that the sooner you move from Yahoo, the better off you’ll be.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Let's face it.  We want you to leave Yahoo! now.  The sooner you leave, the sooner we can make bank by placing you elsewhere.  If you stay elsewhere for at least a year, we make commission and then we can find you a new job.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Please contact me as soon as possible so we can show you some better alternatives with much hotter companies/jobs.   When you email me, let me know what’s new since the last time we talked, and what type of opportunities you’d like us to get you.  The job market is very hot right now!&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;I need to fill out the details on your record in my CRM software, that way the next time I contact you I can pretend to know intimate details about you.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Also, feel free to view my profile on LinkedIn, where you can read testimonials of people I’ve worked with (some from Yahoo), and verify that I am indeed well connected in the industry.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Finally a social network for recruiters.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;I look forward to working with you&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;$$$ HOT HOT HOT $$$&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;[Name Removed]&lt;br/&gt;
Manager, Sr. Technical Recruiter&lt;br/&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;I'm more than just a phone jockey, I'm a deal maker.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;[Company Removed]&lt;br/&gt;
405 El Camino Real #356&lt;br/&gt;
Menlo Park, CA&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;We live on El Camino Real, you know, between the adult bookstore and the Indian buffet.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Smart pricing for iphone</title>
   <link href="http://davedash.com/2008/05/12/smart-pricing-for-iphone-server/"/>
   <updated>2008-05-12T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/05/12/smart-pricing-for-iphone-server</id>
   <content type="html">&lt;p&gt;Going to NYC really made me want a new phone.  Primarily just to have Google Maps telling me where stuff is.  So I've been looking at prices (theoretically for my wife and myself) more closely.  Despite both devices being different, I'm treating them essentially the same:&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;

&lt;th&gt;iPhone&lt;/th&gt;

&lt;th&gt;Blackberry (AT&amp;T)&lt;/th&gt;
&lt;th&gt;Blackberry (T-Mobile)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;

&lt;tr&gt;
&lt;th&gt;Cost of two&lt;/th&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;td&gt;Free＊&lt;/td&gt;
&lt;td&gt;$600&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;th&gt;Ammortized over 24 months&lt;/th&gt;
&lt;td&gt;$34&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;th&gt;Monthly rate&lt;/th&gt;
&lt;td&gt;$109&lt;/td&gt;
&lt;td&gt;$60&lt;/td&gt;
&lt;td&gt;$70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Data plan&lt;/th&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;$60&lt;/td&gt;
&lt;td&gt;$40 ($30＊＊)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Total Monthly cost over 2 years&lt;/th&gt;
&lt;td&gt;$143&lt;/td&gt;
&lt;td&gt;$120&lt;/td&gt;
&lt;td&gt;$135 ($125＊＊)&lt;/td&gt;
&lt;/tr&gt;


&lt;/tbody&gt;
&lt;/table&gt;


&lt;p&gt; ＊ If you go through Amazon.com&lt;/p&gt;

&lt;p&gt;＊＊ My wife is likely to use this mostly for email and less-so for web surfing, and Blackberry has a plan for just that.&lt;/p&gt;

&lt;p&gt;The way I see it, I'm likely to have a smart phone last me two years &amp;mdash; at least.  It's a stretch since most of my phones go about a year (til I get bored of them), but I'm hoping for better results with a smart phone.  So initial costs can be spread out over time.&lt;/p&gt;

&lt;p&gt;So I get a few things out of this chart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon/AT&amp;amp;T's free Blackberry is a white elephant&lt;/li&gt;
&lt;li&gt;The iPhone doesn't cost much more than a Blackberry.&lt;/li&gt;
&lt;li&gt;I will jump at a refurb iPhone since that will be cheaper in the end or a free Blackberry for T-mobile.  Unfortunately I am currently with T-mobile so I don't get a lot of the awesome discounts that you'd find on Amazon.&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Screen</title>
   <link href="http://davedash.com/2008/05/06/screen/"/>
   <updated>2008-05-06T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/05/06/screen</id>
   <content type="html">&lt;p&gt;Screen is &lt;strong&gt;AWESOME&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;I've been doing some data auditing at Delicious and we have a lot of data, and I work off of a laptop connected to other machines.&lt;/p&gt;

&lt;p&gt;So I caved in and started diving into &lt;code&gt;screen&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If your not familiar, screen is a multi-tasking terminal that you can connect and reconnect to.  Everything you run in screen keeps running as if you were there.  Think of it as VNC for the command line.&lt;/p&gt;

&lt;p&gt;I keep my screen session on a linux box at work and now I can do this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ssh -t myawesomecomputer screen -x
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and be connected to my 10 different terminal sessions all at once.  I'm connected to any database I might need, and I have several long running tests in others, or even just an open bash prompt at the ready on the right server.  Very awesome!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Surgical Precision versus Brute Force</title>
   <link href="http://davedash.com/2008/04/11/surgical-precision-versus-brute-force/"/>
   <updated>2008-04-11T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/04/11/surgical-precision-versus-brute-force</id>
   <content type="html">&lt;p&gt;Wireless networking can be troublesome... usually I never have problems, but every now and then I'll go to a coffeeshop or a café where I can't just open up my laptop and immediately start surfing.&lt;/p&gt;

&lt;p&gt;The problem solver in me wants to figure out why, what setting needs enabling, etc.&lt;/p&gt;

&lt;p&gt;The person who wants to cut the crap restarts the machine.&lt;/p&gt;

&lt;p&gt;Restarting almost always is the best course of option.  If it still doesn't work then diagnose it.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>unixtimestamp conversion</title>
   <link href="http://davedash.com/2008/03/06/unixtimestamp-conversion/"/>
   <updated>2008-03-06T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/03/06/unixtimestamp-conversion</id>
   <content type="html">&lt;p&gt;My coworker taught me this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;dash$ date -r 1204748227
Wed Mar  5 12:17:07 PST 2008
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>Yahoo Pipes and reducing information overload</title>
   <link href="http://davedash.com/2008/02/28/yahoo-pipes-and-reducing-information-overload/"/>
   <updated>2008-02-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/28/yahoo-pipes-and-reducing-information-overload</id>
   <content type="html">&lt;p&gt;I've been suffering from some information overload.  I subscribed to Engadget and Gizmodo because I wanted to keep up with some home based network devices like homeplug/powerline and wireless routers.  But Engadget and Gizmodo are overwhelming.&lt;/p&gt;

&lt;p&gt;Finally I decided... to write a &lt;a href=&quot;http://pipes.yahoo.com/&quot;&gt;pipe&lt;/a&gt;.  If you're not familiar with &lt;a href=&quot;http://pipes.yahoo.com/&quot;&gt;Yahoo's Pipes&lt;/a&gt; it is the best thing to come out of Yahoo! (&lt;a href=&quot;http://flickr.com/&quot;&gt;Flickr&lt;/a&gt; and &lt;a href=&quot;http://delicious.com/&quot;&gt;del.icio.us&lt;/a&gt; are acquisitions originally).  Pipes is what Yahoo! should be doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's niche oriented: It appeals highly to geeks who understand pipes, yet it's very learnable, so you don't need to know how to program to use it.&lt;/li&gt;
&lt;li&gt;It's got a great UI.  The user interface is great, you drag and drop inputs and outputs - again very learnable.&lt;/li&gt;
&lt;li&gt;It's very useful.  As I'll show you, below.&lt;/li&gt;
&lt;li&gt;It harnesses the technology that a large company that Yahoo! can provide that might otherwise be difficult: Pipes has to store a lot of data from RSS feeds, to screen scrapes, to anything that you feed it.  Using Yahoo! technology it's trivial to obtain and process all that.&lt;/li&gt;
&lt;li&gt;It's not a clone or a &quot;me too&quot; of other products: This is completely original, and very appealing.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;So my &lt;a href=&quot;http://pipes.yahoo.com/davedash&quot;&gt;pipes&lt;/a&gt; take a regexp filter (e.g. &quot;delicious&quot; or if your clever &quot;delicious|Yahoo|Google&quot;) and give me items that match in a selection of RSS feeds.  So it aggregates things like Engadget and Gizmodo and gives you just what you want to see:&lt;/p&gt;

&lt;p&gt;Take a look: &lt;a href=&quot;http://pipes.yahoo.com/davedash/gadgets&quot;&gt;Gadget Filter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also made a copy called &lt;a href=&quot;http://pipes.yahoo.com/davedash/tech&quot;&gt;Technology Filter&lt;/a&gt; which aggregates the SF Chronicle and TechCrunch.&lt;/p&gt;

&lt;p&gt;Try them out, remember you can customize the filters... not everyone likes what I like ;)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Smarter excerpts: the art of the semi-automatic CMS</title>
   <link href="http://davedash.com/2008/02/28/smarter-excerpts-the-art-of-the-semi-automatic-cms/"/>
   <updated>2008-02-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/28/smarter-excerpts-the-art-of-the-semi-automatic-cms</id>
   <content type="html">&lt;p&gt;I was browsing the &lt;a href=&quot;http://ted.com/&quot;&gt;TED&lt;/a&gt; site, since it's all up in my blogospheres and ran across this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://spindrop.us/wp-content/uploads/2008/02/picture-1.png&quot; alt=&quot;Picture 1.png&quot; border=&quot;0&quot; width=&quot;689&quot; height=&quot;340&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you look at the excerpts, they are piss poor descriptions:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Frans Lanting is one of the greatest nature p...
&lt;/code&gt;&lt;/pre&gt;

&lt;!--more--&gt;


&lt;p&gt;Nature p-what?  Nature painter?  Nature preserver?  If you click around enough... the answer is nature photographer.  Many of the excerpts failed at giving me any indication of who these people are or even enticing me to find out.  I am a web 2.0 person, if I have to click on a link and load a new page, then it better be good.  When I'm doing an information binge, I am not going to have that level of patience.&lt;/p&gt;

&lt;p&gt;One day at del.icio.us I decided to test the limits of every profile field... and I learned that every now and then you &lt;em&gt;do&lt;/em&gt; need to truncate user generated content.&lt;/p&gt;

&lt;p&gt;This page however, is trusted users who have edited structured content (to borrow &lt;a href=&quot;http://www.djangobook.com/en/1.0/chapter17/&quot;&gt;a term from the Django Book&lt;/a&gt;).  That means, that someone had to enter in a description for this person.  Someone who is trusted at TED.  Chances are the page was rendered to trim the excerpt at 46 characters.&lt;/p&gt;

&lt;p&gt;There's two easy solutions to this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;DHTML&lt;/p&gt;

&lt;p&gt; This remaining excerpt could have been hidden away, and a button that said &quot;show all&quot; or &quot;expand&quot; (or whatever is the usable phrase/icon) could show the full except.  This prevents me from having to jump around.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Semi-Automatic excerpts&lt;/p&gt;

&lt;p&gt; We are talking about &quot;trusted users&quot; meaning, we can trust them to do things that humans do better than machines (there's my del.icio.us humans-do-some-things-better mentality).  When they enter the description for a person, their administration interface should be able to suggest an excerpt and they, the &quot;trusted user&quot; should be able to make adjustments or tweaks.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;The former solution is far easier, but if you're scanning a site, especially one as extensive as &lt;a href=&quot;http://www.ted.com/index.php/speakers&quot;&gt;TED's speakers listing&lt;/a&gt;, the last thing you want to do is click &quot;expand&quot; buttons everywhere to read everything.  The latter solution is smarter.  Of course, the problem could also be solved by upping the 46 character limit, but not everything should be fully automated.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FriendFeed: Pure Genius</title>
   <link href="http://davedash.com/2008/02/28/friendfeed-pure-genius/"/>
   <updated>2008-02-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/28/friendfeed-pure-genius</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://friendfeed.com/&quot;&gt;FriendFeed&lt;/a&gt; is pure genius.&lt;/p&gt;

&lt;p&gt;I had this idea that I was going to write an aggregation service that took your sources from multiple locations and aggregated them into once nice RSS'able feed so your friends could follow your footprints on the web.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://friendfeed.com/&quot;&gt;FriendFeed&lt;/a&gt; seems to do just that.  I've listed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon.com wishlist&lt;/li&gt;
&lt;li&gt;spindrop&lt;/li&gt;
&lt;li&gt;davedash.com&lt;/li&gt;
&lt;li&gt;livejournal&lt;/li&gt;
&lt;li&gt;del.icio.us&lt;/li&gt;
&lt;li&gt;digg&lt;/li&gt;
&lt;li&gt;flickr&lt;/li&gt;
&lt;li&gt;katie's flickr (since I hardly post)&lt;/li&gt;
&lt;li&gt;gTalk&lt;/li&gt;
&lt;li&gt;last.fm&lt;/li&gt;
&lt;li&gt;netflix&lt;/li&gt;
&lt;li&gt;Pandora&lt;/li&gt;
&lt;li&gt;Twitter&lt;/li&gt;
&lt;li&gt;reddit&lt;/li&gt;
&lt;li&gt;pownce&lt;/li&gt;
&lt;li&gt;youtube&lt;/li&gt;
&lt;li&gt;upcoming&lt;/li&gt;
&lt;li&gt;linked in&lt;/li&gt;
&lt;li&gt;yumbo&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;That's a lot!  I'm prolly on a few more sites than that, but having one central source where I can point people is great.  What'll be better is if they can do a more Facebook newsfeed style system, where you're not pummeled with everything at once, unles you want it, and it learns what info you are interested (e.g. I am interested in Dave's flickr photos, but not so much his Pandora listening habbits).  Of course that's always stage two, I imagine.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://friendfeed.com/&quot;&gt;Try it out&lt;/a&gt; if you're everywhere on the web.  Here's &lt;a href=&quot;http://friendfeed.com/davedash&quot;&gt;my feed&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Building a triangle toggler in YUI</title>
   <link href="http://davedash.com/2008/02/25/building-a-triangle-toggler-in-yui/"/>
   <updated>2008-02-25T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/25/building-a-triangle-toggler-in-yui</id>
   <content type="html">&lt;p&gt;If someone knows the more common name for triangle-toggle menu's similar to &lt;a href=&quot;http://developer.yahoo.com/yui/examples/treeview/menu_style.html&quot;&gt;this&lt;/a&gt;.  Let me know.&lt;/p&gt;

&lt;p&gt;There is a widget where an element toggles the display of a secondary set of elements.  The toggle shows an arrow pointing down or right depending on the visibility of the elements.  I wanted to build that in YUI.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;The problem is two-fold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build a triangle that toggles from right to down.&lt;/li&gt;
&lt;li&gt;Show and hide content.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;However, with some clever CSS we can do this all in a single class change on an element.&lt;/p&gt;

&lt;h3&gt;Build that triangle&lt;/h3&gt;

&lt;p&gt;There's a number of ways to show or hide this triangle.  Because it is flexible, I'm going to opt for using a background image.  Also to save on HTTP requests, I'll use a sprite-d image.  This may get merged with other icons on the system.&lt;/p&gt;

&lt;p&gt;The particular site I'm working on uses a black background with white text, therefore a white triangle toggle seems appropriate.&lt;/p&gt;

&lt;div style=&quot;background:black;text-align:center; padding:1em&quot;&gt;
&lt;img src=&quot;http://spindrop.us/wp-content/uploads/2008/02/sprite.png&quot; alt=&quot;sprite.png&quot; border=&quot;0&quot; width=&quot;11&quot; height=&quot;29&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;This is what I use.  I leave a little space, because the background will only clip horizontally based on the size of the element.&lt;/p&gt;

&lt;h3&gt;Add the HTML/CSS&lt;/h3&gt;

&lt;p&gt;The following HTML:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;html&quot;&gt;
&lt;div id=&quot;area&quot; class=&quot;off&quot;&gt;
  &lt;h3&gt;&lt;span class=&quot;toggle&quot;&gt;Toggle Me&lt;/span&gt;&lt;/h3&gt;
  &lt;ul class=&quot;hide_me&quot;&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;and the following CSS:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;css&quot;&gt;

.toggle {
    background: url(../images/icons/sprite.png) no-repeat 0px -14px;
    padding: 0 0 0 18px;
}

.off .hide_me {
    display: none;
}

.on .toggle {
    background-position: 0 9px;
}

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Provide the two states we require for the triangle.  If you are new to sprites, rather than changing the background image entirely we just shift the background image up or down appropriately to show a new background.&lt;/p&gt;

&lt;h3&gt;The Javascript&lt;/h3&gt;

&lt;p&gt;The javascript is rather simple, but we put in some magical tricks here and there.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;js&quot;&gt;
var MA = {}
MA.toggler = function()
{
    var e = YAHOO.util.Event; 
    var d = YAHOO.util.Dom;
    
    return {
        init: function() {
            e.onDOMReady(this.setup,this, true)
        },
        
        setup: function() {
            e.on(d.get('doc4'),'click',this.handleClick,this,true);
        },
        
        handleClick: function(ev) {
            var target = e.getTarget(ev);
            if (d.hasClass(target, 'toggle')) {
                var toggle = target.parentNode.parentNode;
                this.toggle(toggle);
            }
        },
        toggle: function(element) {
            var on  = &quot;on&quot;;
            var off = &quot;off&quot;;
            if (d.hasClass(element, on)){
                d.removeClass(element, on);
                d.addClass(element, off);
            } else {
                d.removeClass(element, off);
                d.addClass(element, on);
            }
        }
    }
}();

MA.toggler.init();
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;If you're unfamiliar with this style of Javascript, here's what's going on.  Everything is done in the &lt;code&gt;MA&lt;/code&gt; namespace as to not conflict with other javascript.&lt;/p&gt;

&lt;p&gt;The toggler is fairly generic and expects a similar HTML structure for &lt;em&gt;any&lt;/em&gt; toggle-able element.  That means this code only needs to be written once, and anytime we use the &lt;code&gt;toggle&lt;/code&gt; class, toggling in this fashion will occur.&lt;/p&gt;

&lt;p&gt;We only run the &lt;code&gt;init&lt;/code&gt; function.  &lt;code&gt;init&lt;/code&gt; says when the &lt;code&gt;DOM&lt;/code&gt; is available then run the &lt;code&gt;setup&lt;/code&gt; function.  &lt;code&gt;setup&lt;/code&gt; adds an event handler to &lt;code&gt;doc4&lt;/code&gt;.  &lt;code&gt;doc4&lt;/code&gt; just happens to be the id we use on our &lt;code&gt;body&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;Note that we're listening for clicks everywhere.  This means we have to define only one event handler, regardless of how many toggle-able items there are.  The event handler checks to make sure we clicked on a relevant element and then applies the &lt;code&gt;toggle&lt;/code&gt; function to the grandparent element (switching the class from &lt;code&gt;off&lt;/code&gt; to &lt;code&gt;on&lt;/code&gt; as appropriate).&lt;/p&gt;

&lt;p&gt;Note that this style of event handling means you need to carefully apply your class names.  Also note, that this code could be optimized a little bit more, I haven't put this code into production, and therefore haven't optimized it for the YUI compressor.&lt;/p&gt;

&lt;p&gt;Try this out if you want, I'm sure it can be trimmed down overtime.  If you have trouble with it, let me know.&lt;/p&gt;

&lt;p&gt;-d&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Why I hate the Apple Store and so can you!</title>
   <link href="http://davedash.com/2008/02/23/why-i-hate-the-apple-store-and-so-can-you/"/>
   <updated>2008-02-23T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/23/why-i-hate-the-apple-store-and-so-can-you</id>
   <content type="html">&lt;p&gt;So if you remember, &lt;a href=&quot;/2008/02/13/iphone-and-macbook-air/&quot;&gt;I went to the apple store over a week ago&lt;/a&gt; to learn that Apple gave me a goofy battery and it needed replacing.  Here's what happened:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I waited over the 72 hours maximum I was told I'd wait for a phone call.&lt;/li&gt;
&lt;li&gt;I went to the store today and was told that I should have made yet another appointment &lt;em&gt;just to pick up a battery&lt;/em&gt;.  They could have told me that on the phone.&lt;/li&gt;
&lt;li&gt;Instead of getting a new battery right away, I was told it would take &lt;strong&gt;an hour&lt;/strong&gt;.  Good god, Apple... this is ridiculous.&lt;/li&gt;
&lt;li&gt;I spent a nasty rainy day at a mall trying to blow away gift cards.  California malls are outdoor ones mostly.&lt;/li&gt;
&lt;li&gt;I came in after an hour and was told to wait even longer.&lt;/li&gt;
&lt;li&gt;Finally someone came out with an invoice for $110?  Wait, I thought you were replacing a defective battery.  Here's a hint.  If a customer has been waiting for a week, and then comes in and is told to wait an hour, read your notes that say, this is a replacement for a battery recall.&lt;/li&gt;
&lt;li&gt;The guy went back and got new and improved paperwork that says the battery is free.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;Seriously.. I wasted a lot of time today.  In the future I will resign to calling Apple Care, because at least I can work while on speakerphone.  It does suck that all these batteries are proprietary so there's generally one place to get them taken care of.&lt;/p&gt;

&lt;p&gt;I'm going to see how long I can hold off on buying any new apple stuff.  I'd rather not shell out $50 to apple for a keyboard, but it's been the most comfortable keyboard I've used (I have one at work).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update: &lt;/strong&gt; There's a lot of fanboys out there, so I guess I need to break it down.  For a process that should take 5 minutes (me bringing in a battery and getting a replacement) it took over a week of waiting and additionally 2 hours on two separate days to actually assess that I need a new battery and to finally get it in my hand.  This is incompetence on Apple's part.  This post serves as an alert for people who actually value their time.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>py vs php: stemming</title>
   <link href="http://davedash.com/2008/02/16/py-vs-php-stemming/"/>
   <updated>2008-02-16T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/16/py-vs-php-stemming</id>
   <content type="html">&lt;p&gt;I've been porting some PHP to python during SuperHappyDevHouse and was amazed at how little code I needed to write since python makes list manipulation a breeze.&lt;/p&gt;

&lt;p&gt;Today I was working on stemming (ala &lt;a href=&quot;http://tartarus.org/martin/PorterStemmer/&quot;&gt;Porter Stemming algorithm&lt;/a&gt;).  &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; uses stemming in the search engine to make queries:&lt;/p&gt;

&lt;p&gt;Stemming turns &lt;code&gt;hello everybody how are you guy's&lt;/code&gt; into a collection &lt;code&gt;'everybodi', 'gui', 'hello'&lt;/code&gt;.  To produce this in php I do the following:&lt;/p&gt;

&lt;!--more--&gt;




&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public static function stemPhrase($phrase)
    {
        // remove apostrophe's and periods
        $phrase = strtolower(str_replace(array('\'', '.'), null, $phrase));
        
        // split into words
        $words = str_word_count($phrase, 1);

        // ignore stop words
        $words = array_diff($words, STOP_WORDS_ARRAY);

        // stem words
        $stemmed_words = array();

        foreach ($words as $word)
        {
            $stemmed_words[] = PorterStemmer::stem($word, true);
        }

        return $stemmed_words;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;With some magic python:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
def stem_phrase(phrase):
    words = phrase.lower().replace('.','').replace(&quot;'&quot;,'').split()

    # ignore stop words
    words = list(set(words)-set(STOP_WORDS))

    p = PorterStemmer()
    
    return [p.stem(word,0,len(word)-1) for word in words]

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The magic here is list mappings.  Learning about them, they don't seem that great, but as soon as you start coding you stop using a lot of for loops.&lt;/p&gt;

&lt;p&gt;I'm sure my PHP can be cleaned up and reduced as well, but its fun exploiting the magic of languages.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>unix timestamp in python</title>
   <link href="http://davedash.com/2008/02/14/unix-timestamp-in-python/"/>
   <updated>2008-02-14T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/14/unix-timestamp-in-python</id>
   <content type="html">&lt;p&gt;I spent far too much time learning that:&lt;/p&gt;

&lt;div&gt;&lt;textarea class=&quot;python&quot; name=&quot;code&quot;&gt;
from time import time
print time()
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;gives me the nice numeric representation of the unix timestamp.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>iPhone and MacBook Air</title>
   <link href="http://davedash.com/2008/02/13/iphone-and-macbook-air/"/>
   <updated>2008-02-13T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/13/iphone-and-macbook-air</id>
   <content type="html">&lt;p&gt;I had to wait quite some time at the Apple Store only to find out my MacBook Pro &lt;a href=&quot;http://spindrop.us/2006/05/03/apple_batteries_and_applecare_paid_support/&quot;&gt;had a battery&lt;/a&gt; that had been recalled.&lt;/p&gt;

&lt;p&gt;So while Apple was wasting my time, I looked at the Macbook Air and iPhone.  Yes, this is my blog where I'll give my un-expert opinion on gadgets that people have already written about at length.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;iPhone&lt;/h3&gt;

&lt;p&gt;First the bad news.  I think I have fat fingers.  I finally &quot;got&quot; how it's supposed to work, but keyboard interface and the gesturing was not to my liking.&lt;/p&gt;

&lt;p&gt;If someone gave me an iPhone, I definitely wouldn't mind, and I'd probably grow to like it.  But at the moment...  It really doesn't do much for me.  I like that I could access the web, and play with Google Maps, but the interface just doesn't click for me.&lt;/p&gt;

&lt;p&gt;I do want a smart phone, I think there's a few nice alternatives out there.  I think I can wait on the iPhone.  But then again, maybe I don't really need a new toy.&lt;/p&gt;

&lt;h3&gt;Macbook Air&lt;/h3&gt;

&lt;p&gt;So here's the good news.  Despite it's lack of things, it really seems appealing.  My biggest problem with my laptop is when I take it somewhere.  Granted I bike with my Y! laptop short distances almost daily, but every now and then, I am toting my personal laptop, and the weight starts to drag me down.&lt;/p&gt;

&lt;p&gt;It seemed reasonably fast, although with little effort I can bring any machine to a grinding halt.  I think if one of our laptops go out, we might replace it with a MacBook Air.  Although... both my MacBook Pro, and &lt;a href=&quot;http://katiebonn.com/&quot;&gt;Katie's&lt;/a&gt; Powerbook are pretty sturdy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Redo The Web</title>
   <link href="http://davedash.com/2008/02/12/redo-the-web/"/>
   <updated>2008-02-12T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/12/redo-the-web</id>
   <content type="html">&lt;p&gt;Sometimes it's nice to read a blog the &quot;normal&quot; way.  You go to the site and just read the entries in reverse chronological order ... versus the RSS-crazy aggregated with everything else.&lt;/p&gt;

&lt;p&gt;François Zaninotto's &lt;a href=&quot;http://redotheweb.com/&quot;&gt;Redo The Web&lt;/a&gt; is a great blog for that if you do any work with &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; or web development in general.  François wrote most of the documentation for symfony including &lt;em&gt;The Definitive Guide to Symfony&lt;/em&gt; and his blog manages to keep that same easy-to-follow tips and tutorials.&lt;/p&gt;

&lt;p&gt;Definitely check it out.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Keep a second web server around for luck...</title>
   <link href="http://davedash.com/2008/02/12/keep-a-second-web-server-around-for-luck/"/>
   <updated>2008-02-12T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/12/keep-a-second-web-server-around-for-luck</id>
   <content type="html">&lt;p&gt;I had one of those mid-day &quot;what's going on with my server&quot; heart-atacks.  I have a service that emails me when &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; is down.  On my old server if it went down, I could just restart the server and it'd be back up.  That was big old apache, running out of memory or something.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://reviewsby.us/&quot;&gt;Reviewsby.us&lt;/a&gt; is a medium sized site.  It gets a fair amount of traffic at a steady pace.  Even in this case I decided I was in need for a new server, so I looked into nginx.  It's fast and it can serve static content well and pass things to fastcgi.  Joshua Schachter explains the &lt;a href=&quot;http://joshua.schachter.org/2008/01/proxy.html&quot;&gt;proxy in front&lt;/a&gt; concept pretty well.&lt;/p&gt;

&lt;p&gt;Back to my web developer heart attack...&lt;/p&gt;

&lt;p&gt;Well this setup had been holding up for a better part of a month fairly well... then I saw that a lot of the pages just lagged.  I restarted fastcgi and nginx (it was a fastcgi issue).  Rather than try to debug something I couldn't, I quickly installed apache2 and setup the server the tried and tested way.&lt;/p&gt;

&lt;p&gt;This all took place in a half an hour.  Not the end of the world, but not elegant either.  In the future, I'll revert to using nginx (possibly nginx+apache versus nginx+fastcgi) but I'll keep my other configurations around when all hell breaks loose.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Unicode or How to deal with f'd up text in Django</title>
   <link href="http://davedash.com/2008/02/11/unicode-or-how-to-deal-with-fd-up-text-in-django/"/>
   <updated>2008-02-11T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/11/unicode-or-how-to-deal-with-fd-up-text-in-django</id>
   <content type="html">&lt;p&gt;So I've been going out of my mind trying to figure out why something like:&lt;/p&gt;

&lt;p&gt;Pham's would look like Phamâ€™s&lt;/p&gt;

&lt;p&gt;After searching as much as I could into django and unicode, I found &lt;a href=&quot;http://automatthias.wordpress.com/2006/12/10/mysql-encoding-problems-on-dreamhost/&quot;&gt;this article&lt;/a&gt; which worked.&lt;/p&gt;

&lt;p&gt;As near as my feeble USA-centric &quot;cafes not cafés&quot; understood it, my data was encoded incorrectly, and PHP's mysql ignores encoding and expects UTF-8 whereas django tries to understand the encoding.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>the magic of django: get_callable</title>
   <link href="http://davedash.com/2008/02/08/the-magic-of-django-get_callable/"/>
   <updated>2008-02-08T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/08/the-magic-of-django-get_callable</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; is an amazing framework simply because of the uphill battle it has
with PHP.  There's a lot of behind the scenes magic that ameliorates the
frustrations one might have with PHPs lack of syntactical magic.&lt;/p&gt;

&lt;p&gt;A lot of work is done to make sure certain things are autoloaded and that
certain things behave the way you expect.&lt;/p&gt;

&lt;p&gt;I hadn't had a chance to peek under Django's hood too much myself, but it
appears that despite the inherent magic of python, there were some much needed
additions.  My savior for today was &lt;code&gt;get_callable&lt;/code&gt;.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;I have a very particular vision of interacting with OpenID.  Basically I like
to decouple as much of the OpenID interactions away from the inner workings of
my web site.  I do, however, want a hook where I can supply some of my own
logic after OpenID has been verified.&lt;/p&gt;

&lt;p&gt;It's roughly how I implimented the sfOpenIDPlugin.   Luckily this time, I was
able to rely on the &lt;a href=&quot;http://www.openidenabled.com/openid/libraries/python/&quot;&gt;OpenID 2.1
libraries&lt;/a&gt; from
OpenIDEnabled.&lt;/p&gt;

&lt;p&gt;I did ditch their example code, because I wanted a sexy package.  The package
isn't quite ready for primetime, but you can &lt;a href=&quot;http://svn.spindrop.us/django/trunk/&quot;&gt;have a
gander&lt;/a&gt; (and yes, I do welcome
collaborators and am open to merging my work with the myriad of work that's out
there).&lt;/p&gt;

&lt;h3&gt;Getting to the point&lt;/h3&gt;

&lt;p&gt;Anyway, &lt;code&gt;get_callable&lt;/code&gt; is what allowed me to write my &quot;hook&quot; into my OpenID
logic.&lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;settings.py&lt;/code&gt; I defined:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;OPENID_SUCCESS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;myproject.myapp.views.openid_handler&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;The way my OpenID library works, is it does it's business and when it verifies
a url belongs to someone it then delivers he request to the &lt;code&gt;OPENID_SUCCESS&lt;/code&gt;
hook.  Here's how:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OPENID_SUCCESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_callable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OPENID_SUCCESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;get_callable&lt;/code&gt; works some python magic by splitting
&lt;code&gt;myproject.myapp.views.openid_handler&lt;/code&gt; into the module: &lt;code&gt;myproject.myapp.views&lt;/code&gt;
and the attribute &lt;code&gt;openid_handler&lt;/code&gt;.  It uses this to produce a callable object.
In the example above I gave it parameters of &lt;code&gt;request&lt;/code&gt; and &lt;code&gt;result&lt;/code&gt; as they
were required by my app.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfGuardUser to django.contrib.auth.models.User</title>
   <link href="http://davedash.com/2008/02/07/sfguarduser-to-djangocontribauthmodelsuser/"/>
   <updated>2008-02-07T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/07/sfguarduser-to-djangocontribauthmodelsuser</id>
   <content type="html">&lt;p&gt;Let's pretend that your assignment was to convert a symfony app that used sfGuardAuth to a django-based system.  Wouldn't it be great if someone just gave you a bunch of SQL that you could use to convert the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; &lt;code&gt;sf_guard_user&lt;/code&gt; table to a &lt;a href=&quot;http://djangoproject.com/&quot;&gt;django&lt;/a&gt; &lt;code&gt;auth_user&lt;/code&gt; table?&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;Here you go:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;REPLACE INTO 
  auth_user

SELECT 
  p.id AS id, 
  replace(username, 'http://', '') AS username,
  null AS first_name,
  null AS last_name,
  email,
  concat(algorithm,'$',salt,'$',password),
  0 as is_staff,
  1 as is_active,
  0 as is_superuser, 
  last_login,
  u.created_at as date_joined


FROM 
  `sf_guard_user` u, profile p 

WHERE
  p.userid = u.id
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>rsnapshot to the rescue</title>
   <link href="http://davedash.com/2008/02/05/rsnapshot-to-the-rescue/"/>
   <updated>2008-02-05T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/05/rsnapshot-to-the-rescue</id>
   <content type="html">&lt;p&gt;Sometimes I wonder what convinced me to buy a ReadyNAS and use rsnapshot to create incremental backups.&lt;/p&gt;

&lt;p&gt;Oh yeah, I know, because when I, like an idiot, accidentally hit &quot;Remove&quot; in svnX and realize there's no undo... I've got options.&lt;/p&gt;

&lt;p&gt;SVN revert, and then open up the file back in TextMate, find today's daily snapshot and drag it onto my editor window.  Verify everything and save and commit.&lt;/p&gt;

&lt;p&gt;I like that my past-self is friends with my current-self.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dear Bank</title>
   <link href="http://davedash.com/2008/02/04/dear-bank/"/>
   <updated>2008-02-04T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/04/dear-bank</id>
   <content type="html">&lt;p&gt;I can't do simple things because they inevitably make me ranty.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Dear Small Bank in MN,&lt;/p&gt;

&lt;p&gt;Your online banking system is difficult to log into and yet insecure.&lt;/p&gt;

&lt;p&gt;I have to jump through so many hurtles trying to log into the [Small Bank in MN]  eBiz &gt; bank, yet it still isn't that secure.&lt;/p&gt;

&lt;p&gt;Instead of entering in my password, it's easier to just reset my password... but the scary thing is... resetting my password means you email me my old password in the clear!  Instead of just providing a temporary link, you give me my old password.  That's crazy!  That means anybody snooping into my email could find it out.  Or worse, someone could steal my computer and have all my online banking info.&lt;/p&gt;

&lt;p&gt;My bank information should be secure even when my email gets compromised.  And it shouldn't be so hard to log into.  Look at ING, they have a sufficiently secure and accessible system.&lt;/p&gt;

&lt;p&gt;Also... your HTML is inaccessible.  checkboxes and radiobuttons should have &amp;lt;label&amp;gt; tags.&lt;/p&gt;

&lt;p&gt;Sincerely,&lt;/p&gt;

&lt;p&gt;Dave Dash&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;I hate online banking.  &lt;!--more--&gt; It's so frustrating.  Each company has a weird set of rules on login ids:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A random Number that they provide&lt;/li&gt;
&lt;li&gt;A username which you choose must have at least two numbers&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I get it... but really?  Is it all that necessary.  All it means for me is I only check those bank accounts when I have to, or I write those account numbers in an easy to remember place, like on the palm of my hand.  Or it means I call customer support like an idiot and waste more money and time.&lt;/p&gt;

&lt;p&gt;Then there's the magic questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where were you born?&lt;/li&gt;
&lt;li&gt;Who's your daddy?&lt;/li&gt;
&lt;li&gt;Who's your mommy?&lt;/li&gt;
&lt;li&gt;Which of your two cats do you like better?&lt;/li&gt;
&lt;li&gt;What's your best friends name? (I know &quot;friends&quot; should be &quot;friend's&quot; but that's not what the bank knows)&lt;/li&gt;
&lt;li&gt;What is the date that your second girlfriend started holding your hand?&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Okay... less annoying, but seriously, how many times do I have to answer them to log into my account?&lt;/p&gt;

&lt;p&gt;And then finally passwords:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passwords must contain numbers, letters, bears, tigers&lt;/li&gt;
&lt;li&gt;Passwords cannot be any of the passwords you've used in the last 9 months&lt;/li&gt;
&lt;li&gt;Passwords cannot be your daughter's name&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;And then the inevitable... I give up and click forgot password... and without question it emails my email account with the password I couldn't remember... no questions asked... just a simple request to change my password again.&lt;/p&gt;

&lt;p&gt;Um... yeah that's real secure.  Thanks Bank!&lt;/p&gt;

&lt;p&gt;Oh and never mind that their forms are hard to use because they don't know proper HTML.  If you don't use &amp;lt;label&amp;gt;s it's like trying to pee in a Cheerio using your mouse.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Django circular model references</title>
   <link href="http://davedash.com/2008/02/03/django-circular-model-references/"/>
   <updated>2008-02-03T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/03/django-circular-model-references</id>
   <content type="html">&lt;p&gt;I'm used to circular references in my model.  Often I do a versioning of an &lt;code&gt;Item&lt;/code&gt; with an &lt;code&gt;ItemVersion&lt;/code&gt;.  &lt;code&gt;Item&lt;/code&gt; will link to the latest &lt;code&gt;ItemVersion&lt;/code&gt; and &lt;code&gt;ItemVersion&lt;/code&gt; will link to the relevant &lt;code&gt;Item&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how you can define your appropriate &lt;a href=&quot;http://djangoproject.com/&quot;&gt;django&lt;/a&gt; models:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
class Item(models.Model):
  id      = models.IntegerField(primary_key=True)
  version = models.ForeignKey('ItemVersion', null=True, blank=True)

class ItemVersion(models.Model):
  id   = models.IntegerField(primary_key=True)
  item = models.ForeignKey(Item, null=True, blank=True)
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Note in the first model, &lt;code&gt;Item&lt;/code&gt;, we reference &lt;code&gt;ItemVersion&lt;/code&gt; in quotes because &lt;code&gt;ItemVersion&lt;/code&gt; is not yet defined.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Why Django Templating is awesome and why I get smarty again</title>
   <link href="http://davedash.com/2008/02/01/why-django-templating-is-awesome-and-why-i-get-smarty-again/"/>
   <updated>2008-02-01T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/01/why-django-templating-is-awesome-and-why-i-get-smarty-again</id>
   <content type="html">&lt;p&gt;I get Smarty thanks to django... yeah, it's weird.&lt;/p&gt;

&lt;p&gt;Back to my &lt;a href=&quot;http://spindrop.us/2008/01/28/templating/&quot;&gt;original comment about templating&lt;/a&gt;, smarty really is trying to limit the scope of PHP in a good way.  Too often I see a lot of heavy-lifting in the templates.  It's so bad it makes my MVC'd brain explode.&lt;/p&gt;

&lt;p&gt;Django templates are very limited, based on a philosophy of keeping view logic and only view logic in the templates.&lt;/p&gt;

&lt;p&gt;This is what smart tries to do and it's a reasonable solution to the fact that PHP is a templating language with a kitchen sink.  It's saying, okay... well let's treat PHP as a programming language, and keep Smarty as the template.&lt;/p&gt;

&lt;p&gt;symfony of course says (well not really, or in any official capacity, but would you believe... frameworks talk to me), some PHP is view and some is model/controller.  We'll suggest which ones are which, but really we're not getting in the way of making your life a living hell by sticking complicated business logic inline.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Girl Geek Dinner</title>
   <link href="http://davedash.com/2008/02/01/girl-geek-dinner/"/>
   <updated>2008-02-01T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/01/girl-geek-dinner</id>
   <content type="html">&lt;p&gt;Katie and I went to &lt;a href=&quot;http://upcoming.yahoo.com/event/408117/&quot;&gt;Girl Geek Dinner&lt;/a&gt;.  The best thing about being a Geek Husband is having a Geek Wife to bring you as a guest.&lt;/p&gt;

&lt;p&gt;The panel talk was pretty good.  Featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Irene Au, Director of User Experience, Google&lt;/li&gt;
&lt;li&gt;Rashmi Sinha, CEO, SlideShare&lt;/li&gt;
&lt;li&gt;Leah Culver, Lead Developer &amp;amp; Co-Founder, Pownce&lt;/li&gt;
&lt;li&gt;Sumaya Kazi, Entrepreneur &amp;amp; Social Media Manager, Sun Microsystems&lt;/li&gt;
&lt;li&gt;Katherine Barr (moderator), Partner, Mohr Davidow Ventures&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;What really made the talk was it had a lot of good advice in general.  Writing in a blog for example is how a lot of people got their credibility.  It's how I got mine, and why I now live in the bay area and ultimately why I work on del.icio.us.&lt;/p&gt;

&lt;p&gt;The talk was really reaffirming of my move out here.  It also makes me want to code-like-mad.&lt;/p&gt;

&lt;p&gt;Following the Django theme... Pownce was written in Django ;)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>django experiment: day 2 templates, and that's it?</title>
   <link href="http://davedash.com/2008/01/28/django-experiment-day-2-templates-and-thats-it/"/>
   <updated>2008-01-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/28/django-experiment-day-2-templates-and-thats-it</id>
   <content type="html">&lt;p&gt;[tags]django, symfony[/tags]&lt;/p&gt;

&lt;p&gt;[&lt;strong&gt;Note&lt;/strong&gt;: This really does have something to do with symfony.]&lt;/p&gt;

&lt;p&gt;So I finished the tutorials on Django, but the Django tutorial is nothing compared to the Askeet Advent Calendar.  Askeet was 24 one hour tutorials that go in depth to each nook and cranny of &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, whereas I still feel like there's got to be more to Django.&lt;/p&gt;

&lt;p&gt;I get Templating now, and I really like the flexibility offered in Django.  The one major difference is the layout structure.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; has this nice structure of defining a general layout, and then all following templates automatically inherit from the default layout (unless otherwise specified).  Django on the other hand requires you to explicitly extend other layouts and define regions within those layouts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I like the symfony style because I inherit almost always from the general template.&lt;/li&gt;
&lt;li&gt;The Django style however offers the flexibility of having subsections (which inherit from the base style) which you can inherit from.  E.g. you may have a site which has 1. a blog and 2. a gallery.  Both the blog and gallery sections share common elements from a main template, but all the pages within blog all have some unique bloggish features that the gallery lacks.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I've got a lot more to learn with Django, but I am enjoying it so far.&lt;/p&gt;

&lt;p&gt;I...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;del&gt;still have to learn how to create templates.&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;want better understanding of the available field types.&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;want to find a good CMS package&lt;/li&gt;
&lt;li&gt;want to know if there's the idea of environments in Django (like in symfony)&lt;/li&gt;
&lt;li&gt;want to override .save() on my models&lt;/li&gt;
&lt;li&gt;want to learn how to have self-referencing models (E.g. a hierarchical category)&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Serving Firefox xpi extensions over ssl/https</title>
   <link href="http://davedash.com/2008/01/22/serving-firefox-xpi-extensions-over-sslhttps/"/>
   <updated>2008-01-22T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/22/serving-firefox-xpi-extensions-over-sslhttps</id>
   <content type="html">&lt;p&gt;[tags]firefox, cache, xpi, https[/tags]&lt;/p&gt;

&lt;p&gt;Apparently serving XPI extensions over HTTPS is a headache.  What happens is you may have a link to an XPI served over HTTPS, but Firefox will ask intercept the download and ask you to add your domain to the allowed hosts.  After allowing it, you can't re-download the file (it gets cached, incorrectly, even if you request it to not do so).&lt;/p&gt;

&lt;p&gt;So the workaround is this:&lt;/p&gt;

&lt;p&gt;Don't link directly to the XPI file.  Link to an action (e.g. &lt;code&gt;/getxpi&lt;/code&gt;).  Have this action link to the actual XPI file with a random &lt;code&gt;GET&lt;/code&gt; parameter, e.g. &lt;code&gt;https://mydomain.com/xpi/myextension.xpi?ts=123456&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This will result in any XPI downloaded from &lt;code&gt;/getxpi&lt;/code&gt; to not be cached.&lt;/p&gt;

&lt;p&gt;This is a cruddy work-around for a problem with Firefox, but unfortunately I'm not a XUL hacker.&lt;/p&gt;

&lt;p&gt;-d&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>nginx and 'www' free urls</title>
   <link href="http://davedash.com/2008/01/19/nginx-and-www-free-urls/"/>
   <updated>2008-01-19T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/19/nginx-and-www-free-urls</id>
   <content type="html">&lt;p&gt;[tags]nginx, rewrite[/tags]&lt;/p&gt;

&lt;p&gt;I'm not a fan of 'www' in urls and I've been flirting with nginx (pronounced Engine-X as much as I want to call it N-jinx).&lt;/p&gt;

&lt;p&gt;Here's a one-liner that'll rewrite your urls:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# rewrite hostname
server {
  server_name www. mydomain.com;
  rewrite ^/(.*) http://mydomain.com/$1 permanent;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I put this at the end of the definition for &lt;code&gt;mydomain.com&lt;/code&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ReadyNAS redux</title>
   <link href="http://davedash.com/2008/01/04/readynas-redux/"/>
   <updated>2008-01-04T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/04/readynas-redux</id>
   <content type="html">&lt;p&gt;[tags]infrant, readynas, nv+, redux, harddrive, crash, gear[/tags]&lt;/p&gt;

&lt;p&gt;So... before I left for MSP (I live in Silicon Valley, btw) my solitary disk in my Infrant ReadyNAS NV+ crashed... damn.&lt;/p&gt;

&lt;p&gt;I lost backups (you know the backups I like to take just in case something happens to my laptop while in transit) and some non-essentials, like all the music I used to have.&lt;/p&gt;

&lt;p&gt;So, I ordered some more drives and returned my old one to Newegg.com (I had to pay the shipping).&lt;/p&gt;

&lt;p&gt;The upside is I got to learn the lesson fast and make sure everything is redundant right away.  I have 3 250GB drives giving me almost half a gig of redundant storage, plus I have a replacement 500GB drive that'll add some more.&lt;/p&gt;

&lt;p&gt;I worry that I may have been hammering the drive by doing a lot of backups all at once, but I'll give it another try.  I figure if another drive crashes, I can get it replaced fairly easily.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ReadyNAS is awesome, Netgear is not so awesome</title>
   <link href="http://davedash.com/2007/12/14/readynas-is-awesome-netgear-is-not-so-awesome/"/>
   <updated>2007-12-14T00:00:00-08:00</updated>
   <id>http://davedash.com/2007/12/14/readynas-is-awesome-netgear-is-not-so-awesome</id>
   <content type="html">&lt;p&gt;After much debate I decided to get the ReadyNAS NV+ with a single 500GB disk.  It's great so far. I even got &lt;a href=&quot;http://chapados.org/2007/5/6/readynas-shell-access-redux&quot;&gt;ssh to work&lt;/a&gt; which means I have &lt;a href=&quot;http://rsnapshot.org/&quot;&gt;rsnapshot&lt;/a&gt; working (I can post details if necessary).&lt;/p&gt;

&lt;p&gt;The problem is... I wouldn't have needed &lt;a href=&quot;http://rsnapshot.org/&quot;&gt;rsnapshot&lt;/a&gt; at all if the CD they shipped me wasn't cracked.  I called Netgear (who is headquartered down the road, literally) and they said they can't do anything and that I should call Newegg (the vendor) or call EMC the provider of the backup software (and my next door neighbor at work).  Newegg could only give me a $25 credit which is fine, but as someone at my work pointed out... that's not the same as a 5 seat Mac/Windows license for a backup software.&lt;/p&gt;

&lt;p&gt;Of course... it's impossible to hunt down an actual support email address for Netgear, so this might not get resolved.&lt;/p&gt;

&lt;p&gt;FYI, Here's some &lt;a href=&quot;http://del.icio.us/davedash/readynas&quot;&gt;good links about readyNAS&lt;/a&gt; that I've found.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Affordable IM/Phone-based consulting</title>
   <link href="http://davedash.com/2007/08/20/affordable-imphone-based-consulting/"/>
   <updated>2007-08-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/20/affordable-imphone-based-consulting</id>
   <content type="html">&lt;p&gt;[tags]symfony, spindrop, services, consulting[/tags]&lt;/p&gt;

&lt;p&gt;I'm now available for chat-based consulting.  If you are stuck in a rut and find yourself spending a lot of time in chat rooms or on forums to no avail trying to understand &lt;strong&gt;symfony, php, or javascript (AJAX)&lt;/strong&gt;, this is the solution for you.&lt;/p&gt;

&lt;p&gt;I can give you expert help on best practices and get you out of snags quickly.  The types of problems I can help you solve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;symfony/PHP white-screens-of-death&lt;/li&gt;
&lt;li&gt;other 404/500 errors&lt;/li&gt;
&lt;li&gt;symfony exceptions&lt;/li&gt;
&lt;li&gt;best practices&lt;/li&gt;
&lt;li&gt;apache configuration&lt;/li&gt;
&lt;li&gt;AJAX/Javascript questions&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I'm available by chat, or email or phone during pre-arranged times.  I can even remote into  your server via VNC and show you (literally) how to fix your problems.&lt;/p&gt;

&lt;p&gt;If you're interested in this service please &lt;a href=&quot;http://spindrop.us/contact/&quot;&gt;contact me&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Affordable IM/Phone-based consulting</title>
   <link href="http://davedash.com/2007/08/20/affordable-imphone-based-consulting/"/>
   <updated>2007-08-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/20/affordable-imphone-based-consulting</id>
   <content type="html">&lt;p&gt;[tags]symfony, spindrop, services, consulting[/tags]&lt;/p&gt;

&lt;p&gt;I'm now available for chat-based consulting.  If you are stuck in a rut and find yourself spending a lot of time in chat rooms or on forums to no avail trying to understand &lt;strong&gt;symfony, php, or javascript (AJAX)&lt;/strong&gt;, this is the solution for you.&lt;/p&gt;

&lt;p&gt;I can give you expert help on best practices and get you out of snags quickly.  The types of problems I can help you solve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;symfony/PHP white-screens-of-death&lt;/li&gt;
&lt;li&gt;other 404/500 errors&lt;/li&gt;
&lt;li&gt;symfony exceptions&lt;/li&gt;
&lt;li&gt;best practices&lt;/li&gt;
&lt;li&gt;apache configuration&lt;/li&gt;
&lt;li&gt;AJAX/Javascript questions&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I'm available by chat, or email or phone during pre-arranged times.  I can even remote into  your server via VNC and show you (literally) how to fix your problems.&lt;/p&gt;

&lt;p&gt;If you're interested in this service please &lt;a href=&quot;http://spindrop.us/contact/&quot;&gt;contact me&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Symfony Camp: Ajax and Zend, what would you like to know?</title>
   <link href="http://davedash.com/2007/08/16/symfony-camp-ajax-and-zend-what-would-you-like-to-know/"/>
   <updated>2007-08-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/16/symfony-camp-ajax-and-zend-what-would-you-like-to-know</id>
   <content type="html">&lt;p&gt;[tags]symfonyCamp, symfony, netherlands, ajax, zend search lucene, zsl, jquery[/tags]&lt;/p&gt;

&lt;p&gt;I've been asked to speak at &lt;a href=&quot;http://www.symfonycamp.com/&quot;&gt;SymfonyCamp&lt;/a&gt; (&lt;code&gt;symfony['camp']&lt;/code&gt;) next month (you should all go if you can) and I thought I'd present as well as I could on Ajax and the Zend Framework Bridge (including Zend Search Lucene).&lt;/p&gt;

&lt;p&gt;If you're attending the camp and/or would like to hear about these topics please let me know any specific questions you might have about &quot;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and Ajax&quot; and &quot;symfony and Zend&quot; and I'll try to address them in my presentations.&lt;/p&gt;

&lt;p&gt;If you are unable to go fear not, I'll try to post my notes on this site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>symfony and the .htaccess file</title>
   <link href="http://davedash.com/2007/08/15/symfony-and-the-htaccess-file/"/>
   <updated>2007-08-15T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/15/symfony-and-the-htaccess-file</id>
   <content type="html">&lt;p&gt;One performance boost that can be garnered from a symfony app (or any app for
that matter) is disabling &lt;code&gt;.htaccess&lt;/code&gt;.  &lt;code&gt;.htaccess&lt;/code&gt; does not need to be parsed
on each visit to your app.  Disabling &lt;code&gt;.htaccess&lt;/code&gt; is trivial in your
VirtualHost or another relevant part of your apache configuration place:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;AllowOverride None
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;symfony does require the Rewrite recipes in .htaccess to properly give you
pretty urls, so place those statements right after &lt;code&gt;AllowOverride None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In my specific case I used this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;VirtualHost *&amp;gt;
  ServerName contest.spindrop.sf
  DocumentRoot    /Users/davedash/Sites/spindrop/web/
  DirectoryIndex  frontend_dev.php

  &amp;lt;Directory /Users/davedash/Sites/spindrop/web/&amp;gt;
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    &amp;lt;IfModule mod_rewrite.c&amp;gt;
      RewriteEngine On

      RewriteCond %{REQUEST_URI} \..+$
      RewriteCond %{REQUEST_URI} !\.html$
      RewriteRule .* - [L]

      RewriteRule ^$ index.html [QSA]
      RewriteRule ^([^.]+)$ $1.html [QSA]
      RewriteCond %{REQUEST_FILENAME} !-f

      RewriteRule ^(.*)$ frontend_dev.php [QSA,L]
    &amp;lt;/IfModule&amp;gt;
  &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now all these rewriting rules are loaded when the server is restarted and the
&lt;code&gt;.htaccess&lt;/code&gt; is not examined upon each request.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FBML and CSS background images</title>
   <link href="http://davedash.com/2007/08/11/fbml-and-css-background-images/"/>
   <updated>2007-08-11T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/11/fbml-and-css-background-images</id>
   <content type="html">&lt;p&gt;I've been tediously building out the new &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; app for Facebook and I ran into some problem when I tried to embed this CSS:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;css&quot;&gt;
input.openid_login {
            background: url(&quot;http://openid.net/login-bg.gif&quot;) no-repeat #fff;
   background-position: 0 50%;
          padding-left: 18px;
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The CSS worked fine, just now background image.  The fix is mentioned in &lt;a href=&quot;http://www.facebook.com/topic.php?uid=2205007948&amp;amp;topic=5140&amp;amp;post=50818&amp;amp;pwstdfy=0434dec4ce75745f05b7396611234b7c#post50818&quot;&gt;this post&lt;/a&gt; which is to repeat the &lt;code&gt;background&lt;/code&gt; statement:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;css&quot;&gt;
input.openid_login {
            background: url(&quot;http://openid.net/login-bg.gif&quot;) no-repeat #fff;
            background: url(&quot;http://openid.net/login-bg.gif&quot;) no-repeat #fff;
   background-position: 0 50%;
          padding-left: 18px;
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Voila, it works.  Hopefully this hack won't be necessary in later versions of the Platform.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>doctine and getState()</title>
   <link href="http://davedash.com/2007/07/10/doctine-and-getstate/"/>
   <updated>2007-07-10T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/10/doctine-and-getstate</id>
   <content type="html">&lt;p&gt;[tags]doctrine, php, symfony, sfDoctrine, database,errors[/tags]&lt;/p&gt;

&lt;p&gt;I tend to have models with a field called &lt;code&gt;state&lt;/code&gt;.  Doctrine offers a few ways of getting to the &lt;code&gt;state&lt;/code&gt; field:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$obj-&amp;gt;get('state');
$obj['state'];
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;$obj-&amp;gt;getState()&lt;/code&gt; however conflicts with &lt;code&gt;Doctrine_Record::getState()&lt;/code&gt; from which all objects inherit.  Use one of the above alternatives.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>TextMate + YUI = YUI snippets!</title>
   <link href="http://davedash.com/2007/04/30/textmate-yui-yui-snippets/"/>
   <updated>2007-04-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/30/textmate-yui-yui-snippets</id>
   <content type="html">&lt;p&gt;I do a lot of &lt;a href=&quot;http://developer.yahoo.com/yui/grids/&quot;&gt;YUI grid layouts&lt;/a&gt; and I love the nestable grids:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;yui-g$1&quot;&amp;gt; 
    &amp;lt;div class=&quot;yui-u first&quot;&amp;gt;
        $2
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;yui-u&quot;&amp;gt;
        $3
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There's a tab stop after &lt;code&gt;yui-g&lt;/code&gt; in case you want to use one of the variants (&lt;code&gt;yui-gb&lt;/code&gt;, &lt;code&gt;yui-gc&lt;/code&gt;, etc).&lt;/p&gt;

&lt;p&gt;I'm working on a site that uses two equal width columns... a lot... so this comes in quite handy.  So long tables.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Creative Friends</title>
   <link href="http://davedash.com/2007/04/10/creative-friends/"/>
   <updated>2007-04-10T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/10/creative-friends</id>
   <content type="html">&lt;p&gt;[tags]photography, design, pretty[/tags]&lt;/p&gt;

&lt;p&gt;A college friend of mine swims some creative waters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://corvail.com/&quot;&gt;corvail photographic agency&lt;/a&gt; has some interesting.  &lt;a href=&quot;http://corvail.com/images/food/flourlesschocolatecake-dennislee.jpg&quot;&gt;This picture&lt;/a&gt; was pretty awesome.  If we could get people to upload delicious photos like that for &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsBy.us&lt;/a&gt;... we'd all be fat and broke ;)&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;http://www.striel.com/&quot;&gt;full screen pictures on striel&lt;/a&gt; were also fun.  I especially liked &lt;a href=&quot;http://www.striel.com/082005.html&quot;&gt;8/2005&lt;/a&gt;, &lt;a href=&quot;http://www.striel.com/102005.html&quot;&gt;10/2005&lt;/a&gt;, &lt;a href=&quot;http://www.striel.com/112005.html&quot;&gt;11/2005&lt;/a&gt;, &lt;a href=&quot;http://www.striel.com/022006.html&quot;&gt;2/2006&lt;/a&gt;, &lt;a href=&quot;http://www.striel.com/052006.html&quot;&gt;5/2006&lt;/a&gt; and &lt;a href=&quot;http://www.striel.com/032007.html&quot;&gt;3/2007&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://grasshoppermag.com/&quot;&gt;Grasshopper&lt;/a&gt; is pretty... pretty awesome!&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Finding a co-founder</title>
   <link href="http://davedash.com/2007/04/05/finding-a-co-founder/"/>
   <updated>2007-04-05T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/05/finding-a-co-founder</id>
   <content type="html">&lt;p&gt;[tags]startups[/tags]
A friend of mine directed me to &lt;a href=&quot;http://www.paulgraham.com/notnot.html&quot;&gt;Why to Not Not Start a Startup&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Not having a cofounder is a real problem. A startup is too much for one person to bear. And though we differ from other investors on a lot of questions, we all agree on this. All investors, without exception, are more likely to fund you with a cofounder than without.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;This is probably the single largest road-block in the way of starting a start-up.  One solution is to move to where there are more interested parties (e.g. Silicon Valley), but that's not always an easy option (although I'm warming up to it, Minneapolis isn't exactly heaven for several months out of the year).&lt;/p&gt;

&lt;p&gt;Another option is to find someone online.  &lt;a href=&quot;http://www.subelsky.com/2007/03/first-web-app-launch_10.html&quot;&gt;Mike Subelsky&lt;/a&gt; created &lt;a href=&quot;http://www.founderfinder.com/&quot;&gt;Founder Finder&lt;/a&gt; as an attempt at addressing this very issue.&lt;/p&gt;

&lt;p&gt;Unfortunately, deep down inside I think that is a difficult proposition as well.  &lt;em&gt;I&lt;/em&gt; have a &lt;a href=&quot;http://spindrop.us/2007/03/01/how-do-you-find-good-programmers/&quot;&gt;hard time finding good programmers&lt;/a&gt;, I've worked as a one-man island most of my life, and haven't maintained good contact with my fellow CS major's from &lt;a href=&quot;http://uiuc.edu/&quot;&gt;UIUC&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Believe it or not, social networking is helping in some ways, because I'm able to reconnect with people I knew at one time or another who are very much start-up driven and really have the drive.&lt;/p&gt;

&lt;p&gt;A better network of developers needs to exist, not just people we scrape out of monster.com and jobster... how's that for a cheap idea?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>MarsEdit</title>
   <link href="http://davedash.com/2007/02/24/marsedit/"/>
   <updated>2007-02-24T00:00:00-08:00</updated>
   <id>http://davedash.com/2007/02/24/marsedit</id>
   <content type="html">&lt;p&gt;[tags]MarsEdit,software,blogs[/tags]&lt;/p&gt;

&lt;p&gt;I recently read about the acquisition of &lt;a href=&quot;http://www.red-sweater.com/marsedit/&quot;&gt;MarsEdit&lt;/a&gt; by Red Sweater Software so I decided to check it out.  I'm quite glad I did it does make my workflow a lot easier.  What I had been doing up until today was this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I'd come up with an idea.&lt;/li&gt;
&lt;li&gt;Open up my writings project in &lt;a href=&quot;http://macromates.com/&quot;&gt;TextMate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open a new file and start typing ideas, etc.&lt;/li&gt;
&lt;li&gt;Find the right blog to post it on (e.g. &lt;a href=&quot;http://spindrop.us/&quot;&gt;Spindrop&lt;/a&gt;, &lt;a href=&quot;http://davedash.com/&quot;&gt;davedash.com&lt;/a&gt;, &lt;a href=&quot;http://yumbo.reviewsby.us/&quot;&gt;yumbo the reviewsBy.us blog&lt;/a&gt; or &lt;a href=&quot;http://twincities.metblogs.com/&quot;&gt;Metroblogging&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Login to the blog.&lt;/li&gt;
&lt;li&gt;Cut and paste titles, categories, tags, etc.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;It was easy to drop my blogging habbit ;)&lt;/p&gt;

&lt;p&gt;Now, I can do this:
1. Come up with my idea.
2. Select which blog I want to post it in.
3. Write my idea.
4. When it's ready to go live, just hit submit to blog.&lt;/p&gt;

&lt;p&gt;The one slight problem I have is I use a tags feature of wordpress in addition to categories (not sure why I don't just use categories, but that's another story), currently there's no easy way to deal with WordPress plugins' custom fields that I know of.  So there's an optional 5th step of examining the post on the site and possibly adding tags.&lt;/p&gt;

&lt;p&gt;Still, these 4 steps are easy and all done from one app.  It also features a preview of your post that you can customize per blog with a custom HTML template.  Which makes it easy to just drop your blog's style sheet and see if your post looks right.&lt;/p&gt;

&lt;p&gt;Definitely looks like a keeepr!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Vim Undo</title>
   <link href="http://davedash.com/2006/09/20/vim-undo/"/>
   <updated>2006-09-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/09/20/vim-undo</id>
   <content type="html">&lt;p&gt;So apparently you can &quot;time-travel&quot; in &lt;code&gt;vim&lt;/code&gt;.  Commands like &lt;code&gt;:earlier 10m&lt;/code&gt; and &lt;code&gt;:later 5m&lt;/code&gt; will move your document to points 10 minutes ago and from their 5 minutes after.&lt;/p&gt;

&lt;p&gt;Is it just me, or is this acceptable?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vim phd_thesis.txt

:later 5y
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;:)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Cropping Images using DHTML (Prototype) and symfony</title>
   <link href="http://davedash.com/2006/09/16/cropping-images-using-dhtml-prototype-and-symfony/"/>
   <updated>2006-09-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/09/16/cropping-images-using-dhtml-prototype-and-symfony</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://demos.spindrop.us/image_cropper/&quot;&gt;&lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/screenshot.png&quot; style=&quot;float:left;margin-right:1em&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Like many of my tutorials, you don't &lt;em&gt;need&lt;/em&gt; &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, just &lt;a href=&quot;http://php.net/&quot;&gt;PHP&lt;/a&gt;.  However, I develop in &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and take advantage of the &lt;acronym title=&quot;Model Viewer Controller&quot;&gt;MVC&lt;/acronym&gt;-support that it offers.&lt;/p&gt;

&lt;p&gt;Years ago when I was working on a photo gallery for &lt;a href=&quot;http://davedash.com/&quot;&gt;davedash.com&lt;/a&gt; I got the art of making tumbnails down fairly well.  It was automated and didn't allow for specifying how the thumbnail should be made.  With dozens of photos (which was a lot back then), when would I find that kind of time.&lt;/p&gt;

&lt;p&gt;Flashback to today, for &lt;a href=&quot;http://workface.com/&quot;&gt;my company&lt;/a&gt;... we want users with avatars... but nothing too large.  Maybe a nice 80x80 picture.  Well the coolest &lt;acronym title=&quot;User Interface&quot;&gt;UI&lt;/acronym&gt; I've seen was Apple's Address Book which let you use this slider mechanism to crop a fixed sized image from a larger image.&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href=&quot;http://demos.spindrop.us/image_cropper/&quot;&gt;demo&lt;/a&gt;.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Overview&lt;/h3&gt;

&lt;p&gt;The front-end &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt; is based on code from &lt;a href=&quot;http://digg.com/&quot;&gt;digg&lt;/a&gt; which is based on the look and feel (as near as I can tell) from Apple.&lt;/p&gt;

&lt;p&gt;The &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt; provides a clever visual way of telling the server how to chop the image.  The gist is this, sliding the image around and zooming in and out change a few form values that get passed to another script which uses this data to produce the image.&lt;/p&gt;

&lt;h3&gt;Frontend: What would you like to crop?&lt;/h3&gt;

&lt;p&gt;In this tutorial, we're going to be cropping an 80x80 avatar from an uploaded image.  The front-end requires the correct mix of Javascript, &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;, &lt;acronym title=&quot;HypterText Markup Languag&quot;&gt;HTML&lt;/acronym&gt; and images.  The Javascript sets up the initial placements of the image and the controls.  The &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; presents some necessary styling.  The images makeup some of the controls.  The &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt; glues everything together.&lt;/p&gt;

&lt;h4&gt;&lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt;&lt;/h4&gt;

&lt;p&gt;Let's work on our &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt; first.  Since I used &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, I created a &lt;code&gt;crop&lt;/code&gt; action for a &lt;code&gt;userpics&lt;/code&gt; module.  So in our &lt;code&gt;cropSuccess.php&lt;/code&gt; template:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;ava&quot;&amp;gt;
    &amp;lt;?php echo form_tag(&quot;userpics/crop&quot;) ?&amp;gt;
        &amp;lt;div id=&quot;ava_img&quot;&amp;gt;
            &amp;lt;div id=&quot;ava_overlay&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div id=&quot;ava_drager&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;img src=&quot;&amp;lt;?php echo $image ?&amp;gt;&quot; id=&quot;avatar&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id=&quot;ava_slider&quot;&amp;gt;&amp;lt;div id=&quot;ava_handle&quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_width&quot; name=&quot;width&quot; value=&quot;80&quot; /&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_x&quot; name=&quot;x&quot; value=&quot;100&quot; /&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_y&quot; name=&quot;y&quot; value=&quot;100&quot; /&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_image&quot; name=&quot;file&quot; value=&quot;&amp;lt;?php echo $image ?&amp;gt;&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;input type=&quot;submit&quot; name=&quot;submit&quot; id=&quot;ava_submit&quot; value=&quot;Crop&quot; style=&quot;width: auto; font-size: 105%; font-weight: bold; margin: 1em 0;&quot; /&amp;gt;
    &amp;lt;/form&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Right now a lot of this doesn't quite make sense.  If you attempt to render it, you will just see only the image.  As we add the corresponding &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; and images it will make some more sense.&lt;/p&gt;

&lt;h4&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; and corresponding images&lt;/h4&gt;

&lt;p&gt;We'll go through each style individually and explain what purpose it serves in terms of the &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#ava&lt;/code&gt; is our container.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava {
    border: 1px solid gray;
     width: 200px;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#ava_img&lt;/code&gt; is the area that contains our image.  Our window for editing this image 200x200 pixels.  If we drag out image out of bounds we just want the overflowing image to be clipped.  We also want our position to be relative so any child elements can be positioned absolutely with respect to &lt;code&gt;#ava_img&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_img {
    width: 200px;
    height: 200px;
    overflow: hidden;
    position: relative;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div style=&quot;float:left;margin: 1em 1em 1em 0&quot;&gt;
    &lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/overlay.png&quot; alt=&quot;overlay&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;#ava_overlay&lt;/code&gt; is a window we use to see what exactly will be our avatar.  If it's in the small 80x80 window in the center of the image, then it's part of the avatar.  If it's in the fuzzy region, then it's getting cropped out.  This overlay of course needs to be positioned absolutely.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_overlay {
    width: 200px;
    height: 200px;
    position: absolute;
    top: 0px;
    left: 0px;
    background: url('/images/overlay.png');
    z-index: 50;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#ava_drager&lt;/code&gt; is probably the least intuitive element (Heck, I'm not even sure if I've even got it right).  In &lt;a href=&quot;http://demos.spindrop.us/image_cropper/&quot;&gt;our demo&lt;/a&gt; you're not actually dragging the image, because you can drag anywhere within the &lt;code&gt;#ava_img&lt;/code&gt; container and move the image around.  You're using dragging an invisible handle.  It's a 400x400 pixel square that can be dragged all over the container and thusly move the image as needed.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_drager {
    width: 400px;
    height: 400px;
    position: absolute;
    z-index: 100;
    color: #fff;
    cursor: move;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#avatar&lt;/code&gt; is our image, and since it will be moving all around the window, it requires absolute positioning.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#avatar {
    position: absolute;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div style=&quot;float:left;margin: 1em 1em 1em 0&quot;&gt;
    &lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/slider_back.png&quot; alt=&quot;overlay&quot; /&gt;
&lt;/div&gt;


&lt;div style=&quot;float:left;margin-left: 1em 1em 1em 0;&quot;&gt;
    &lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/handle.png&quot; alt=&quot;overlay&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;#ava_slider&lt;/code&gt; and &lt;code&gt;#ava_handle&lt;/code&gt; are our slider components.  They should be self-explanatory.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_slider {
    width: 200px;
    height: 27px;
    background: #eee;
    position: relative;
    border-top: 1px solid gray; 
    background: url('/images/slider_back.png');
}
#ava_handle {
    width: 19px;
    height: 20px;
    background: blue;
    position: absolute;
    background: url('/images/handle.png');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;Internet Explorer&lt;/h5&gt;

&lt;p&gt;&lt;acronym title=&quot;Portable Network Graphics&quot;&gt;PNG&lt;/acronym&gt; do not work so well in Internet Explorer, but there is a small trick, adding these components into a style sheet that only IE can read will make things work:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_overlay {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/overlay.png', sizingMethod='crop');
}

#ava_handle {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/handle.png', sizingMethod='crop');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;The Javascript&lt;/h4&gt;

&lt;p&gt;The Javascript is actually not as complicated as you'd expect thanks to the wonder of &lt;a href=&quot;http://prototype.conio.net/&quot;&gt;prototype&lt;/a&gt;.  This framework provides so much so easily.  You'll need to include &lt;a href=&quot;http://prototype.conio.net/&quot;&gt;prototype.js&lt;/a&gt; and &lt;a href=&quot;http://boring.youngpup.net/2001/domdrag/project&quot;&gt;dom-drag.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So let's take a look.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type=&quot;text/javascript&quot; language=&quot;javascript&quot; charset=&quot;utf-8&quot;&amp;gt;
// &amp;lt;![CDATA[
function setupAva() {
    if ($(&quot;avatar&quot;)) {
        var handle = $(&quot;ava_handle&quot;);
        var avatar = $(&quot;avatar&quot;);
        var drager = $(&quot;ava_drager&quot;);
        var slider = $(&quot;ava_slider&quot;);
        var ava_width = $(&quot;ava_width&quot;);
        var ava_x = $(&quot;ava_x&quot;);
        var ava_y = $(&quot;ava_y&quot;);
        // four numbers are minx, maxx, miny, maxy
        Drag.init(handle, null, 0, 134, 0, 0);
        Drag.init(drager, avatar, -100, 350, -100, 350);
        var start_w = avatar.width;
        var start_h = avatar.height;
        var ratio = (start_h / start_w);
        var new_h;
        var new_w;
        if (ratio &amp;gt; 1) {
            new_w = 80;
            new_h = (80*start_h)/start_w;
        } else {
            new_h = 80;
            new_w = (80*start_w)/start_h;
        }
        // these need to be set after we init
        avatar.style.top = '100px';
        avatar.style.left = '100px';
        avatar.style.width = new_w + 'px';
        avatar.style.height = new_h + 'px';
        avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';
        handle.style.margin = '3px 0 0 20px';
        avatar.onDrag = function(x, y) {
            ava_x.value = x;
            ava_y.value = y;
        }
        handle.onDrag = function(x, y) {
            var n_width = (new_w + (x * 2));
            var n_height = (new_h + ((x * 2) * ratio));         
            avatar.style.width = n_width + 'px';
            avatar.style.height = n_height+ 'px';
            ava_width.value = n_width;  
            avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
        }
    }
}
Event.observe(window,'load',setupAva, false);
// ]]&amp;gt;
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If this isn't exactly crystal clear, I can explain.  If you're new to &lt;a href=&quot;http://prototype.conio.net/&quot;&gt;prototype&lt;/a&gt;, &lt;code&gt;$()&lt;/code&gt; is the same as &lt;code&gt;doucment.getElementByID()&lt;/code&gt; (at least for our purposes).&lt;/p&gt;

&lt;p&gt;We need to initialize two draggable elements, one is our slider for zooming and the other is our avatar itself.  We initialize the draggers using &lt;code&gt;Drag.init()&lt;/code&gt;.  We specify what to drag, if another element should be used as a handle and then the range of motion in xy coordinates.  In the second call we use that &lt;code&gt;#dragger&lt;/code&gt; to move around the image in this manner.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Drag.init(handle, null, 0, 134, 0, 0);
Drag.init(drager, avatar, -100, 350, -100, 350);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We want to initialize the the size and placement of the avatar.  We do that using maths.  First we want it in our 80x80 pixel box.  So it should be roughly 80x80.  I've set the math up so that the &lt;em&gt;smallest&lt;/em&gt; side is 80 pixels (there's reasons for doing this the other way around).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    if (ratio &amp;gt; 1) {
        new_w = 80;
        new_h = (80*start_h)/start_w;
    } else {
        new_h = 80;
        new_w = (80*start_w)/start_h;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We then place the avatar element.  We initialize it to be in the center of the screen (&lt;code&gt;top: 100px;left:100px&lt;/code&gt;) and then nudge the image using margins.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    avatar.style.top = '100px';
    avatar.style.left = '100px';
    avatar.style.width = new_w + 'px';
    avatar.style.height = new_h + 'px';
    avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We also use margins to place the handle.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    handle.style.margin = '3px 0 0 20px';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#ava_x&lt;/code&gt; and &lt;code&gt;#ava_y&lt;/code&gt; tell us where the center of the avatar is.  So when the avatar is moved we need to set these again:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    avatar.onDrag = function(x, y) {
        ava_x.value = x;
        ava_y.value = y;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That was easy.  Slighly more complicated is the zoomer function.  We are basically adjusting the width and the height proportionately based on roughly where the slider is.  Note that we're still using that &lt;code&gt;ratio&lt;/code&gt; variable that we calculated earlier.  We basically take the new x-coordinate of the handle and allow our image to get just slightly larger than the &lt;code&gt;#ava_image&lt;/code&gt; container.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    handle.onDrag = function(x, y) {
        var n_width = (new_w + (x * 2));
        var n_height = (new_h + ((x * 2) * ratio));         
        avatar.style.width = n_width + 'px';
        avatar.style.height = n_height+ 'px';
        ava_width.value = n_width;  
        avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We want to load initialize the slider right away when the page loads: &lt;code&gt;Event.observe(window,'load',setupAva, false);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Not terribly hard or complicated.  Once these elements are all in place you have a working functioning slider.  It returns the x and y coordinates of the center of the image with respect to our 200x200 pixel &lt;code&gt;#ava_image&lt;/code&gt;.  It also tells us the new width of our image.  We feed this information into a new script and out should pop a new image which matches &lt;em&gt;exactly&lt;/em&gt; what we see in our &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt;.&lt;/p&gt;

&lt;!--nextpage--&gt;


&lt;h3&gt;Processing the crop&lt;/h3&gt;

&lt;p&gt;Initially I was frustrated with the data that was being sent.  I knew the center of the image in relation to this 200x200 pixel canvas and its width... but what could I do with that.  Well I could just recreate what I saw in the &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt;.  I needed to create a 200x200 pixel image first, place my original avatar resized (and resampled) at the precise coordinates and &lt;em&gt;then&lt;/em&gt; cut out the center most 80x80 pixels to become the final avatar image.&lt;/p&gt;

&lt;p&gt;If you note in our template above for &lt;code&gt;cropSuccess.php&lt;/code&gt; we submit our form back to the &lt;code&gt;crop&lt;/code&gt; action.  Let's look at the action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function executeCrop()
{
    if ($this-&amp;gt;getRequestParameter('file')&amp;amp;&amp;amp;$this-&amp;gt;getRequestParameter('width')) {          // we are saving our cropped image
        // Load the original avatar into a GD image so we can manipulate it with GD
        $o_filename = $this-&amp;gt;getRequestParameter('file');  // we'll use this to find the file on our system
        $o_filename = sfConfig::get('sf_root_dir').'/web' . $o_filename;
        $o_im = @imagecreatetruecolor(80, 80) or die(&quot;Cannot Initialize new GD image stream&quot;);
        $o_imagetype = exif_imagetype($o_filename); // is this gif/jpeg/png

        // appropriately create the GD image
        switch ($o_imagetype) {
            case 1: // gif
                $o_im = imagecreatefromgif($o_filename);
                break;
            case 2: // jpeg
                $o_im = imagecreatefromjpeg($o_filename);   
                break;
            case 3: // png
                $o_im = imagecreatefrompng($o_filename);
                break;
        }

        // Let's create our canvas
        $im = @imagecreatetruecolor(200, 200) or die(&quot;Cannot Initialize new GD image stream&quot;);
        imagecolortransparent ( $im, 127 ); // set the transparency color to 127
        imagefilledrectangle( $im, 0, 0, 200, 200, 127 ); // fill the canvas with a transparent rectangle

        // let's get the new dimension for our image

        $new_width = $this-&amp;gt;getRequestParameter('width');
        $o_width = imageSX($o_im);
        $o_height = imageSY($o_im);

        $new_height = $o_height/$o_width * $new_width;

        // we place the image at the xy coordinate and then shift it so that the image is now centered at the xy coordinate
        $x = $this-&amp;gt;getRequestParameter('x') - $new_width/2;
        $y = $this-&amp;gt;getRequestParameter('y') - $new_height/2;

        // copy the original image resized and resampled onto the canvas
        imagecopyresampled($im,$o_im,$x,$y,0,0,$new_width,$new_height,$o_width,$o_height); 
        imagedestroy($o_im);

        // $final will be our final image, we will chop $im and take out the 80x80 center
        $final = @imagecreatetruecolor(80, 80) or die(&quot;Cannot Initialize new GD image stream&quot;);
        imagecolortransparent ( $final, 127 ); // maintain transparency

        //copy the center of our original image and store it here
        imagecopyresampled ( $final, $im, 0, 0, 60, 60, 80, 80, 80, 80 );
        imagedestroy($im);

        //save our new user pic
        $p = new Userpic();
        $p-&amp;gt;setUser($this-&amp;gt;getUser()-&amp;gt;getUser());
        $p-&amp;gt;setGD2($final);
        $p-&amp;gt;save();
        imagedestroy($final);
        $this-&amp;gt;userpic = $p;
        return &quot;Finished&quot;;
    }


    $this-&amp;gt;getResponse()-&amp;gt;addJavascript(&quot;dom-drag&quot;);
    $this-&amp;gt;getResponse()-&amp;gt;addJavascript('/sf/js/prototype/prototype');
    $this-&amp;gt;getResponse()-&amp;gt;addJavascript('/sf/js/prototype/effects');
    $this-&amp;gt;image = '/images/userpics/originals/' . $this-&amp;gt;getRequestParameter('file');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's doing exactly what the paragraph above explains when the image dimensions are given.  The code is well commented so it should be easy enough to follow.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.php.net/manual/en/ref.image.php&quot;&gt;GD image functions in PHP&lt;/a&gt; are fairly robust and can help you do a lot of tricks with image data.  Note the code to save the image, we'll cover it in detail soon.&lt;/p&gt;

&lt;h3&gt;The Model&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$p = new Userpic();
$p-&amp;gt;setUser($this-&amp;gt;getUser()-&amp;gt;getUser());
$p-&amp;gt;setGD2($final);
$p-&amp;gt;save();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;First some clarification the second line.  &lt;code&gt;myUser::getUser()&lt;/code&gt; gets the &lt;code&gt;User&lt;/code&gt; object associated with the currently logged in user.  The third line, however, is where the magic happens.  Before we look at it, let's have a quick look at our model:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;userpic:
 _attributes: { phpName: Userpic }
 id:
 user_id:
 image: blob
 thumb: blob
 created_at:
 updated_at:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We have an &lt;code&gt;image&lt;/code&gt; attribute and a &lt;code&gt;thumb&lt;/code&gt; property to our &lt;code&gt;Userpic&lt;/code&gt; object.  This is where we store &lt;acronym title=&quot;Portable Network Graphics&quot;&gt;PNG&lt;/acronym&gt; versions of each icon and their 16x16 thumbnails respectively.  We do this in &lt;code&gt;Userpic::setGD2()&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function setGD2($gd2_image)
{
    //convert to PNG
    ob_start();
    imagepng($gd2_image);
    $png = ob_get_clean();
    //save 16x16
    $gd2_tn = @imagecreatetruecolor(16, 16) or die(&quot;Cannot Initialize new GD image stream&quot;);
    imagealphablending( $gd2_tn, true );
    imagecolortransparent ( $gd2_tn, 127 );

    imagecopyresampled ( $gd2_tn, $gd2_image, 0, 0, 0, 0, 16, 16, 80, 80 );
    ob_start();
    imagepng($gd2_tn);
    $tn = ob_get_clean();

    $this-&amp;gt;setImage($png);
    $this-&amp;gt;setThumb($tn);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We capture the output of the full size &lt;acronym title=&quot;Portable Network Graphics&quot;&gt;PNG&lt;/acronym&gt;, then we scale it again and capture the output of the thumbnail and set them.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;When it comes to web apps, having a relatively simple &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt; for people to resize images can go a long way in terms of adoption rate of avatars and custom user pictures by non technical users.&lt;/p&gt;

&lt;p&gt;Enjoy, and if you found this useful (or better implemented it) let me know.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Restaurants can be (tagged) spicy too!</title>
   <link href="http://davedash.com/2006/07/27/restaurants-can-be-tagged-spicy-too/"/>
   <updated>2006-07-27T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/27/restaurants-can-be-tagged-spicy-too</id>
   <content type="html">&lt;p&gt;We had a change of heart on tagging.  Tag everything!  Initially I felt that only menu items should be tagged.  &quot;People want to know &lt;a href=&quot;http://reviewsby.us/tag/spicy&quot;&gt;what dishes are spicy&lt;/a&gt;&quot;... true, but people also want to know what restaurants are in &lt;a href=&quot;http://reviewsby.us/tag/uptown&quot;&gt;Uptown&lt;/a&gt; or are &lt;a href=&quot;http://reviewsby.us/tag/mexican&quot;&gt;Mexican&lt;/a&gt; or are &lt;a href=&quot;http://reviewsby.us/tag/closedmondays&quot;&gt;closed on mondays&lt;/a&gt;.  So... we now have restaurant tags.  &lt;a href=&quot;http://reviewsby.us/login&quot;&gt;Login&lt;/a&gt; and tag something!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Menu item versions</title>
   <link href="http://davedash.com/2006/07/24/menu-item-versions/"/>
   <updated>2006-07-24T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/24/menu-item-versions</id>
   <content type="html">&lt;p&gt;We make a lot of mistakes, and by we, I don&amp;#8217;t mean myself, but I mean we as humanity.  That&amp;#8217;s why we like to keep multiple versions of things in our &lt;a href=&quot;http://reviewsby.us/&quot;&gt;restaurant review&lt;/a&gt; database.  That also means menu items.  Did you describe the &lt;a href=&quot;http://reviewsby.us/restaurant/newsroom/menu/pesto-chicken-sandwich&quot;&gt;Pesto Chicken&lt;/a&gt; as having &lt;em&gt;pisto&lt;/em&gt; and not &lt;em&gt;pesto&lt;/em&gt;?  Problem solved, now any user can log in and change it.  It&amp;#8217;s like a wiki&amp;#8230;. for restaurant menus.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Safari Fixes</title>
   <link href="http://davedash.com/2006/07/11/safari-fixes/"/>
   <updated>2006-07-11T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/11/safari-fixes</id>
   <content type="html">&lt;p&gt;Safari interprets &lt;code&gt;/* */&lt;/code&gt;s differently than FireFox or &lt;acronym title=&quot;Internet Explorer&quot;&gt;IE&lt;/acronym&gt;.  &lt;acronym title=&quot;FireFox&quot;&gt;FF&lt;/acronym&gt; and &lt;acronym title=&quot;Internet Explorer&quot;&gt;IE&lt;/acronym&gt; will ignore a unmatched &lt;code&gt;/*&lt;/code&gt; or &lt;code&gt;*/&lt;/code&gt;, whereas Safari will ignore parts of code if there's a lone &lt;code&gt;*/&lt;/code&gt;.  Once I found that out, I was able to get the list items that are used throughout the site to render properly.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Spindrop objectives</title>
   <link href="http://davedash.com/2006/06/05/spindrop-objectives/"/>
   <updated>2006-06-05T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/06/05/spindrop-objectives</id>
   <content type="html">&lt;p&gt;I had wanted to write about this later, but &lt;a href=&quot;http://www.problogger.net/archives/2006/06/05/blog-goals-group-writing-project/&quot;&gt;Darren at ProBlogger&lt;/a&gt; started this group writing project on &quot;Blog Goals.&quot;  So I am jumping in quickly.  This blog was &lt;a href=&quot;http://spindrop.us/purpose/&quot;&gt;created for two reasons&lt;/a&gt;, one, to document any technical things that I've learned, code samples, best practices, strategies, etc. as they pertain to web development and open source.  The second is to serve as a site to record updates to any of my projects.&lt;/p&gt;

&lt;p&gt;I'm still in my infancy for this blog, but I've seen a lot of little things that keep me optimistic.&lt;/p&gt;

&lt;p&gt;The objectives I have for &lt;a href=&quot;http://spindrop.us/&quot;&gt;Spindrop&lt;/a&gt; are both internal and external.  Internal goals are things that I can change myself.  For example, the style of the site, linking to other places, posting more content, changing the way ads are presented, etc.&lt;/p&gt;

&lt;p&gt;External goals depend on readers like you.  I can do my best to make this site be relevant to a lot of people, but I can't make people click on my site, comment, or any of that.  I can still make goals for them, and that will subconsciously get me to position myself better.&lt;/p&gt;

&lt;p&gt;They are both related.  If I achieve my internal objectives, I'm better suited to getting external objectives done.  If I get my external objectives done, it encourages my behavior of making the site better.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;What am I willing to do&lt;/h3&gt;

&lt;p&gt;My &quot;9-5&quot; is a fairly demanding job as a lead web developer for a health and wellness website.  On top of that, I'm &lt;a href=&quot;ktdd.org&quot;&gt;getting married this month&lt;/a&gt;, so I'm &lt;strong&gt;very&lt;/strong&gt; pressed for time when it comes to a side project like this.  Luckily I'm very well organized, and do wake up early and spend time writing.&lt;/p&gt;

&lt;h4&gt;Writing&lt;/h4&gt;

&lt;p&gt;In an OmniOutliner file, I keep a detailed list of what I'll be writing as well as other &quot;to-do list&quot; items for this site.  I've already taken into account that I'm not going to be pushing anything useful for the week or two surrounding my wedding.  Hopefully, I can still muster two or three articles this month.  Some of those articles are on software choices, strategies for using &lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;propel&lt;/a&gt; and &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and general work habbits.&lt;/p&gt;

&lt;p&gt;By spending an hour (or more) a day on writing, I'm generally looking over things and making sure I did a halfway decent job.  I prefer not to have typos, spelling errors, etc, I prefer to have a readable article, and I prefer to throw in images, when useful.  Of course, I do this early in the morning, so there are mistakes.  But, I've noticed a lot of heavy hit articles on &lt;a href=&quot;http://del.icio.us/&quot;&gt;del.icio.us&lt;/a&gt; and &lt;a href=&quot;http://digg.com/&quot;&gt;digg&lt;/a&gt; have typos too.&lt;/p&gt;

&lt;h4&gt;Non-content changes&lt;/h4&gt;

&lt;p&gt;I'm willing to tweak ad placement, and practice all the other ad-voodoo that is involved with blogging.    I'm also willing to announce the site at appropriate moments, communicate with people via forums and other blogs.  I'm willing to listen and execute on others advice.  The best advice I've heard is to stick with it.&lt;/p&gt;

&lt;h3&gt;What can I get at the very least?&lt;/h3&gt;

&lt;p&gt;With all my efforts, at the very least, I know I'll have something to show for.  For one, I tend to do things over and over again.  I'm a web developer, and the bulk of my blog is about how to do things I've done before.  So if I have to make &lt;a href=&quot;http://spindrop.us/2006/04/26/easy_yahoo_maps_and_georss_with_symfony&quot;&gt;another map&lt;/a&gt; or &lt;a href=&quot;http://spindrop.us/2006/05/19/migrating-from-drupal-47-to-wordpress&quot;&gt;migrate a blog&lt;/a&gt;, I can see how I did it before.  Now, anything else this blog achieves is gravy.&lt;/p&gt;

&lt;h3&gt;External goals&lt;/h3&gt;

&lt;p&gt;Some of my hopes for the site are out of my control.  I want what a lot of other blogs want: traffic, revenue and community.  Additionally I want traffic to be sent to my other sites, like the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; site so that they can share the success.&lt;/p&gt;

&lt;h4&gt;Traffic&lt;/h4&gt;

&lt;p&gt;The most important goal for me is traffic.  I want this site to get a lot of traffic.  For the last two weeks, I've averaged 112/users a day.  Of course, part of that is due to a spike because of last Friday's article on &lt;a href=&quot;http://spindrop.us/2006/06/02/editing-css-live-in-firefox&quot;&gt;Editing CSS from Firefox&lt;/a&gt; which had an anomalous amount of hits due to getting on &lt;a href=&quot;http://programming.reddit.com/goto?id=73c6&quot;&gt;reddit&lt;/a&gt;, &lt;a href=&quot;http://del.icio.us/popular/&quot;&gt;del.icio.us popular&lt;/a&gt;, &lt;a href=&quot;http://digg.com/&quot;&gt;digg&lt;/a&gt;.&lt;sup id=&quot;fnr1&quot;&gt;&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Traffic is important because I without it I can't expect to have revenue or community.  There's also a sense of validation that you have something useful to say, and sites like &lt;a href=&quot;http://del.icio.us/&quot;&gt;del.icio.us&lt;/a&gt; make you feel that not only was it useful, it's worth hanging on to.&lt;/p&gt;

&lt;h4&gt;Revenue&lt;/h4&gt;

&lt;p&gt;I'd like to earn enough to actually make blogging make a significant change in my lifestyle.  Right now it's a hobby.  A dedicated hobby, but it's still a hobby.  I love writing, I love to see myself improve, I love what I write about.  I'd like to continue to do that full time.  I'm sure with some tweaks to adsense and some other forms of advertising, I can kick that up.  But content, which attracts traffic, will be the biggest driver of them all.  My less-than-&quot;adsense optimized&quot; &lt;a href=&quot;http://reviewsby.us/&quot;&gt;restaurant review site&lt;/a&gt; generates a lot more money (seriously, we're talking in small terms, like the cost of an iced tea), but it has a lot of content and a lot of useful ads that get generated from adsense.&lt;/p&gt;

&lt;p&gt;Last month (May 2006) I made just shy over $2.00 from the web site.  That's not much, but it's better than $0.  I'd like to make $4 in a given month.  On one hand it's a doubling of earnings, but on another, it's not very much money.  I'm not expecting to double the earnings this month, although I might easily do that, but I am expecting to hit that $4 mark eventually.  It's an easily obtainable goal, but for me easy goals are good.  I feel just as good when I hit them, as I would if I got a raise or a bonus at my &quot;9 to 5.&quot;&lt;/p&gt;

&lt;h4&gt;Community&lt;/h4&gt;

&lt;p&gt;I'd like for there to be some level of interaction with me.  I don't  mean community in the &lt;a href=&quot;http://livejournal.com/&quot;&gt;LiveJournal&lt;/a&gt; sense of the word, I just want some interaction with me the poster, and my commenters.  The last week or two has seen a few &quot;real comments,&quot; which is promising.  I hope that trend continues over time.  It's an opportunity to improve myself if I get good feedback, and it's an opportunity to further express or clarify a point.  I also like to help people out.&lt;/p&gt;

&lt;h4&gt;Linking out to my other sites&lt;/h4&gt;

&lt;p&gt;Lastly, I do want this site to assist my other sites.  With a decent amount of traffic, this site can be a great link in to my other sites, and bring them up to speed.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Overall, I'm happy where I am, and I'm happy where I intend to go.  Each week brings in a new useful content which is a nice record of how I do things, and proves some degree of usefulness to others.&lt;/p&gt;

&lt;p&gt;I hope to expand on those strengths.  This body of content might be small now, but it only gets bigger.  My long term goal is to be able to generate the traffic and revenue I need so I can justify dedicating more time to this project.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn1&quot;&gt;Perhaps it's not anomalous, but if you saw my statistics you'd understand why I might think so. &lt;a href=&quot;#fnr1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 

</feed>

