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

<channel>
	<title>Spindrop &#187; mozilla</title>
	<atom:link href="http://spindrop.us/tag/mozilla/feed/" rel="self" type="application/rss+xml" />
	<link>http://spindrop.us</link>
	<description>look at all this code you don't have to write</description>
	<lastBuildDate>Tue, 09 Mar 2010 22:44:36 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>&#955;^2: safely doing class based views in Django</title>
		<link>http://spindrop.us/2010/03/09/2-safely-doing-class-based-views-in-django/</link>
		<comments>http://spindrop.us/2010/03/09/2-safely-doing-class-based-views-in-django/#comments</comments>
		<pubDate>Tue, 09 Mar 2010 22:44:35 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[lambda]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[views]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=348</guid>
		<description><![CDATA[When I started rewriting the API for addons.mozilla.org, 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 api_version parameter, so I decided class based views would be best.  This way my classes could just inherit [...]]]></description>
			<content:encoded><![CDATA[<p>When I started rewriting the API for <a href="https://addons.mozilla.org/">addons.mozilla.org</a>, 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 <code>api_version</code> parameter, so I decided class based views would be best.  This way my classes could just inherit from a base class.</p>

<p>To do this I had to implement a <a href="http://github.com/davedash/zamboni/blob/b5a147820840e66b542691e7239f15eccdebeec9/apps/api/views.py#L39"><code>__call__</code> method</a>.  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.</p>

<p><span id="more-348"></span></p>

<h3>&lambda;</h3>

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

<h3>&lambda; &lambda;</h3>

<p>But wrapping all your urls with <code>lambda</code> is tedious and remembering to pass <code>*args</code> and <code>**kwargs</code> is error prone.</p>

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

<p>We can now return to coding and not think about thread safety.</p>

<p>&lambda;&lambda;&lambda;</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2010/03/09/2-safely-doing-class-based-views-in-django/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>django-fixture-magic: Testing issues with real data.</title>
		<link>http://spindrop.us/2010/03/05/django-fixture-magic-testing-issues-with-real-data/</link>
		<comments>http://spindrop.us/2010/03/05/django-fixture-magic-testing-issues-with-real-data/#comments</comments>
		<pubDate>Fri, 05 Mar 2010 21:43:24 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[addons.mozilla.org]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[fixtures]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[testing]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=342</guid>
		<description><![CDATA[I just released Fixture Magic.

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 Django, it's nice to actually use non-dummy data. [...]]]></description>
			<content:encoded><![CDATA[<p>I just released <a href="http://github.com/davedash/django-fixture-magic">Fixture Magic</a>.</p>

<p>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 <a href="http://djangoproject.com/">Django</a>, it's nice to actually use non-dummy data.  </p>

<p>Luckily Django has a way of pulling real data out of your database using <code>django.core.serializers</code>:</p>

<pre><code>from addons.models import Addon
a = Addon.objects.get(id=3615)
from django.core.serializers import serialize
jsonize = lambda a: serialize("json", a, indent=4)
jsonize([a])
</code></pre>

<p>This solution runs well in a Django shell and can be lots of fun for the whole family... until things get complicated.
<span id="more-342"></span></p>

<h3>Serializing alone isn't enough.</h3>

<p>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 <a href="http://addons.mozilla.org/">addons.mozilla.org</a>, we have a spidery-web of dependencies: <code>File</code>s need a <code>Version</code> which needs an <code>Addon</code> which need <code>Translation</code>s.</p>

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

<p>Example:</p>

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

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

<h3>Too much serial</h3>

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

<p>To combat this we can use <code>merge_fixtures</code> to dedupe our fixtures:</p>

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

<p>This should make creating test data slightly less painful.  So <a href="http://github.com/davedash/django-fixture-magic">give it a try</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2010/03/05/django-fixture-magic-testing-issues-with-real-data/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Palm Pré: A retraction, I really like it now</title>
		<link>http://spindrop.us/2010/01/11/palm-pre-a-retraction-i-really-like-it-now/</link>
		<comments>http://spindrop.us/2010/01/11/palm-pre-a-retraction-i-really-like-it-now/#comments</comments>
		<pubDate>Mon, 11 Jan 2010 21:07:05 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[palm]]></category>
		<category><![CDATA[pre]]></category>
		<category><![CDATA[retraction]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=333</guid>
		<description><![CDATA[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 my issues, might be a welcome distraction.

Before I packed it, I updated to WebOS 1.3.x (a few [...]]]></description>
			<content:encoded><![CDATA[<p>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 <a href="/2009/11/19/palm-pre-always-hot/">my issues</a>, might be a welcome distraction.</p>

<p>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:</p>

<pre><code>* 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.
</code></pre>

<p>All these improvements helped me get over
    * The tiny keyboard... not so bad in practice.
    * No soft keyboard - I missed it, but I could deal without it.</p>

<p>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).</p>

<p>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.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2010/01/11/palm-pre-a-retraction-i-really-like-it-now/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Google Chrome Extensions Puzzle</title>
		<link>http://spindrop.us/2010/01/06/google-chrome-extensions-puzzle/</link>
		<comments>http://spindrop.us/2010/01/06/google-chrome-extensions-puzzle/#comments</comments>
		<pubDate>Thu, 07 Jan 2010 04:08:36 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[chrome]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[puzzle]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=329</guid>
		<description><![CDATA[


I went to Add-on-Con some weeks back to represent my employer, the Mozilla Corporation.

One of the goodies you get as a registrant was a jigsaw puzzle from the Google Chrome Extensions team.

Perfect, my wife and I love solving jigsaw puzzles.  We finally finished a few days ago.  Anybody who has started at all [...]]]></description>
			<content:encoded><![CDATA[<div style="float:left; margin-right:1em"><a href="http://www.flickr.com/photos/44124375866@N01/4252390433" title="View 'puzzle' on Flickr.com"><div style="text-align:center;"><img src="http://farm3.static.flickr.com/2684/4252390433_b49093b583_m.jpg" alt="puzzle" border="0" width="161" height="240" /></div></a>
</div>

<p>I went to Add-on-Con some weeks back to represent my employer, the Mozilla Corporation.</p>

<p>One of the goodies you get as a registrant was a jigsaw puzzle from the Google Chrome Extensions team.</p>

<p>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.</p>

<p>Although finding a QR code scanner was a bit difficult, I had to borrow a HTC Magic from <a href="http://fligtar.com/">Justin Scott</a> and installed a decent barcode scanner.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2010/01/06/google-chrome-extensions-puzzle/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Django: Model Inheritance or Related Tables wrt AMO</title>
		<link>http://spindrop.us/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo/</link>
		<comments>http://spindrop.us/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo/#comments</comments>
		<pubDate>Tue, 15 Dec 2009 20:13:51 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[addons.mozilla.org]]></category>
		<category><![CDATA[amo]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[mozilla]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=323</guid>
		<description><![CDATA[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, announced development [...]]]></description>
			<content:encoded><![CDATA[<p>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, <a href="http://micropipes.com/blog/2009/11/17/amo-development-changes-in-2010/">announced development changes</a> for <a href="http://addons.mozilla.org">addons.mozilla.org</a> (AMO) that says we'll be moving to Django.  </p>

<p>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 <a href="http://github.com/jbalogh/zamboni">Zamboni</a>.  I've been working on some grunt-work tasks inside and outside of Django.</p>

<p>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:</p>

<blockquote>
  <p>How much do I change my app, in order to accommodate the framework?</p>
</blockquote>

<p><span id="more-323"></span></p>

<p>More specifically:</p>

<blockquote>
  <p>Should I use the <code>django.contrib.auth</code> User module, and to what extent?</p>
</blockquote>

<p>The more we looked into what features of Django we might want to use, <code>django.contrib.auth</code> 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 <a href="http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/">inheritance approach</a> or do we treat our legacy users table as a sort of User Profile and utilize the User module using the <a href="http://www.b-list.org/weblog/2007/feb/20/about-model-subclassing/">related table approach</a>?</p>

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

<p>Looking at our <code>users</code> table more closely:</p>

<pre><code>mysql&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                |                |
+-------------------------+---------------------+------+-----+---------------------+----------------+
</code></pre>

<p>You can very easily argue that this is a profile table, which happens to have credential information thrown in.</p>

<p>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.</p>

<p>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.</p>

<p>I'm curious what other people who port their apps to Django have done.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Palm Pre: Always hot</title>
		<link>http://spindrop.us/2009/11/19/palm-pre-always-hot/</link>
		<comments>http://spindrop.us/2009/11/19/palm-pre-always-hot/#comments</comments>
		<pubDate>Fri, 20 Nov 2009 01:50:33 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[palm]]></category>
		<category><![CDATA[pre]]></category>
		<category><![CDATA[usabilitiy]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=321</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>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.</p>

<p>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.  </p>

<p>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.</p>

<p>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</p>

<p>Despite the sadness there were a few good things:</p>

<ul>
<li>When it did fetch email, and other notices, it displayed them nicely</li>
<li>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.</li>
<li>The Icons were pretty.</li>
<li>The card interface was interesting.</li>
<li>The travel charger could be modified to work in non US chargers fairly easily.</li>
</ul>

<p>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.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2009/11/19/palm-pre-always-hot/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>AMO Search: Powered by Sphinx</title>
		<link>http://spindrop.us/2009/09/30/amo-search-powered-by-sphinx/</link>
		<comments>http://spindrop.us/2009/09/30/amo-search-powered-by-sphinx/#comments</comments>
		<pubDate>Thu, 01 Oct 2009 00:47:49 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[addons.mozilla.org]]></category>
		<category><![CDATA[amo]]></category>
		<category><![CDATA[amochi09]]></category>
		<category><![CDATA[chicago]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[search]]></category>
		<category><![CDATA[sphinx]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=319</guid>
		<description><![CDATA[Last night, I gave a talk at the Addons Meetup at Threadless HQ in Chicago on the new search engine powering addons.mozilla.org.  I'll recap the technical portion of the talk and give a bit more details.

First, I'd like to thank Harper and Threadless.  It was a great location in the greatest city in [...]]]></description>
			<content:encoded><![CDATA[<p>Last night, I gave a talk at the <a href="https://wiki.mozilla.org/AddonMeetups:2009:Chicago">Addons Meetup</a> at Threadless HQ in Chicago on the new search engine powering <a href="http://addons.mozilla.org/">addons.mozilla.org</a>.  I'll recap the technical portion of the talk and give a bit more details.</p>

<p>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.</p>

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

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

<p><span id="more-319"></span></p>

<h3>A bit about addons</h3>

<p>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.</p>

<p>We can just skip that step.  Because Addons are small things that people can easily "get".</p>

<p>"It's an easy way to customize the internet when your surfing." </p>

<p>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.</p>

<p>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 <a href="http://addons.mozilla.org/">AMO</a> she was really intrigued.</p>

<p>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.</p>

<p>So we better be prepared to handle them, and get them what they want.</p>

<h3>The technical details of addons.mozilla.org</h3>

<p>Everytime you open Firefox, it pings <a href="http://addons.mozilla.org/">AMO</a> 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.</p>

<p>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.</p>

<p>Some stats:</p>

<ul>
<li>1 mySQL master</li>
<li>4 mySQL slaves</li>
<li>2 memached servers</li>
<li>2 Sphinx indexer/search daemons</li>
<li>24 Web Frontend</li>
<li>Multiple Zeus ZXTM clusters all</li>
</ul>

<p>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.</p>

<p>To see just how amazing they are read our <a href="http://blog.mozilla.com/mrz/">mrz's ops blog</a>.</p>

<h3>Why search matters</h3>

<p>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 <a href="http://addons.mozilla.org/">AMO</a> 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.</p>

<h3>Improve Search</h3>

<p>So my first job on AMO was to <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=498999">improve addons search</a>.  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.</p>

<p>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.</p>

<p>So here were the goals I made for myself:</p>

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

<h3>Complex Data</h3>

<p>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:</p>

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

<p>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?</p>

<p>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.</p>

<h3>Using Sphinx</h3>

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

<h3>The challenges</h3>

<p>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.</p>

<p>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.</p>

<p>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 <a href="http://spindrop.us/2009/08/07/v-is-for-version-hell/">acceptable version strings into integers</a>.  </p>

<p>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.</p>

<p>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.</p>

<h3>Wins</h3>

<p>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 "on demand."</p>

<p>Indexing happens rather quickly, just over a minute.</p>

<p>The API was a breeze to work with, and was easy to drop into our own codebase.</p>

<p>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.</p>

<p>Our ops team can monitor the sphinx clusters and just deploy additional nodes as needed.</p>

<h3>Building a platform</h3>

<p>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.</p>

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

<p>Enjoy.  And if you find anything, <a href="http://bit.ly/search-bugs">let me know</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2009/09/30/amo-search-powered-by-sphinx/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>mySQL and the grand regexp retardedness with lettercasing</title>
		<link>http://spindrop.us/2009/09/19/mysql-and-the-grand-regexp-retardedness-with-lettercasing/</link>
		<comments>http://spindrop.us/2009/09/19/mysql-and-the-grand-regexp-retardedness-with-lettercasing/#comments</comments>
		<pubDate>Sat, 19 Sep 2009 18:35:14 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[regexp]]></category>
		<category><![CDATA[sphinx]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=317</guid>
		<description><![CDATA[I wanted to find a list of Firefox addons that had smushed text in their title.  E.g. FireBug or StumbleUpon.  The normal porter stemming algorithm that Sphinx uses does not turn "StumbleUpon" into "stumbl upon" as it would with "Stumble Upon".  I was hoping for, and unfortunately could not find a method [...]]]></description>
			<content:encoded><![CDATA[<p>I wanted to find a list of Firefox addons that had smushed text in their title.  E.g. FireBug or StumbleUpon.  The normal porter stemming algorithm that Sphinx uses does not turn "StumbleUpon" into "stumbl upon" as it would with "Stumble Upon".  I was hoping for, and unfortunately could not find a method to do a regular expression search/replace using mysql.  If I could, I could have Sphinx read "StumbleUpon" as "Stumble Upon" and all would be well (although in theory this would backfire).</p>

<p>So my Plan B was to get a list of common smushed named addons (I'd say camelCase, but camelCase is different from SmushedText).  Naturally I used my exceptional skill at regular expressions to concoct this query:</p>

<pre><code>mysql&gt; SELECT name FROM translated_addons WHERE name REGEXP '[a-z][A-Z][a-z]' = 1 LIMIT 10;
+------------------------+
| name                   |
+------------------------+
| Orbit Grey             | 
| Phoenity               | 
| Pinball                | 
| Qute                   | 
| FirefoxModern          | 
| Adblock                | 
| Add Bookmark Here      | 
| All-in-One Gestures    | 
| Bookmarks Synchronizer | 
| Browser Uptime         | 
+------------------------+
10 rows in set (41.28 sec)
</code></pre>

<p>Wait... none of these match.  I scratched my head for a bit and then thought, oh wait, mysql is case insenstivie maybe it's turning <code>[a-z][A-Z][a-z]</code> into <code>[a-z][a-z][a-z]</code> &#8213; stupid, but consistent with mysql.  Then I pulled my other regexp card out of my sleve, character classes:</p>

<pre><code>mysql&gt; SELECT name FROM translated_addons WHERE name REGEXP '[[:lower:]][[:upper:]][[:lower:]]' = 1 LIMIT 10;
+------------------------+
| name                   |
+------------------------+
| Orbit Grey             |
| Phoenity               |
| Pinball                |
| Qute                   |
| FirefoxModern          |
| Adblock                |
| Add Bookmark Here      |
| All-in-One Gestures    |
| Bookmarks Synchronizer |
| Browser Uptime         |
+------------------------+
10 rows in set (12.96 sec)
</code></pre>

<p>No difference.  Time to pull out the <a href="http://dev.mysql.com/doc/refman/5.1/en/regexp.html">mysql documentation</a>:</p>

<blockquote>
  <p>REGEXP is not case sensitive, except when used with binary strings. </p>
</blockquote>

<p>ORLY?</p>

<p>Case-insenstive regular expressions when looking for <code>[[:upper:]]</code> or <code>[[:lower:]]</code>?  Fine... I'll add some syntax to make you work right:</p>

<pre><code>mysql&gt; SELECT DISTINCT name FROM translated_addons WHERE name REGEXP BINARY '[[:lower:]][[:upper:]][[:lower:]]' = 1 LIMIT 10;
+---------------------------+
| name                      |
+---------------------------+
| FirefoxModern             |
| ChatZilla                 |
| ChromEdit                 |
| CuteMenus                 |
| DownloadWith              |
| easyGestures              |
| JavaScript Console Status |
| LinkVisitor               |
| OpenBook                  |
| QuickNote                 |
+---------------------------+
10 rows in set (9.68 sec)
</code></pre>

<p>That's more like it!</p>

<p>Unfortunately there's about 2609 addons matching this query and since I can't automatically fix these in mysql, I'll need to do some work:</p>

<pre><code>1.  Create a new table for additional indexable data.
2.  Upon creation of any new addons with names that have SmushedText - store the "un smushed text".
3.  Index this "extras" field in Sphinx.
</code></pre>

<p>Bug: <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=517699">517699</a></p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2009/09/19/mysql-and-the-grand-regexp-retardedness-with-lettercasing/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Getting started with pipe viewer</title>
		<link>http://spindrop.us/2009/09/16/getting-started-with-pipe-viewer/</link>
		<comments>http://spindrop.us/2009/09/16/getting-started-with-pipe-viewer/#comments</comments>
		<pubDate>Wed, 16 Sep 2009 22:16:34 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[pv]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=313</guid>
		<description><![CDATA[Despite working on slimming the addons.mozilla.org database through dieting and exercise - I still have to occasionally do long running database tasks.  So I finally tried out pipe viewer.  As someone who's impatient this has been awesome.  Here's some quick examples:

[root@ml-db10 sun]# pv -cN source &#60; addons_remora.2009.09.15.sql.gz &#124; gunzip&#124;pv -cN gunzip &#62; [...]]]></description>
			<content:encoded><![CDATA[<p>Despite working on slimming the <code>addons.mozilla.org</code> database through dieting and exercise - I still have to occasionally do long running database tasks.  So I finally tried out <a href="http://www.ivarch.com/programs/pv.shtml">pipe viewer</a>.  As someone who's impatient this has been awesome.  Here's some quick examples:</p>

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

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

<p>Now a simpler example is checking the progress of loading a large sql file into mysql:</p>

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

<p>We could have probably combined all this, however:</p>

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

<p>Armed with this knowledge you can determine whether to grab a soda, a sandwich or a 2-hour lunch.</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2009/09/16/getting-started-with-pipe-viewer/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>DjangoCon wrapup</title>
		<link>http://spindrop.us/2009/09/15/djangocon-wrapup/</link>
		<comments>http://spindrop.us/2009/09/15/djangocon-wrapup/#comments</comments>
		<pubDate>Tue, 15 Sep 2009 18:50:37 +0000</pubDate>
		<dc:creator>Dave Dash</dc:creator>
				<category><![CDATA[spindrop]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[djangocon]]></category>
		<category><![CDATA[mozilla]]></category>

		<guid isPermaLink="false">http://spindrop.us/?p=311</guid>
		<description><![CDATA[I went to DjangoCon 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 [...]]]></description>
			<content:encoded><![CDATA[<p>I went to <a href="http://djangocon.org/">DjangoCon</a> 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.<span id="more-311"></span></p>

<p>Here's a collection of notes I collected through the conference.</p>

<h3>Day one</h3>

<h4>Keynote - Avi Bryant</h4>

<blockquote>
  <p>Frameworks lock us into RDBMS = bad</p>
</blockquote>

<p>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.</p>

<h4>UR doing it wrong - James Bennet</h4>

<p>James outlined a few key problems that many Django developers run into:</p>

<ul>
<li><p>learning python as you go</p>

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

<ul>
<li>subclasses</li>
<li>super()</li>
<li>slides went too fast... hopefully they'll be posted</li>
</ul></li>
</ul>

<p>All in all RTFM for python and Django <img src='http://spindrop.us/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>

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

<p>Bennet's Django App review smoketests:</p>

<ul>
<li>installable via pip, easy_install or setup.py

<ul>
<li>read distutils-guide</li>
<li>stay away from setuptools</li>
</ul></li>
<li>have a README</li>
<li>INSTALL file list deps</li>
<li>Write DOCUMENTATION

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

<p>pro-django is a decent book, but not written by Bennet.</p>

<h4>Testing - Eric Holscher</h4>

<ul>
<li>Django 1.1 encourages you to test by auto-creating tests.py.</li>
<li>Support for:

<ul>
<li>Unittests</li>
<li>Doctest</li>
<li>Tests done in a db transacation</li>
</ul></li>
<li>Test Driven Documentation (TDD + DDD)</li>
<li>Doctest

<ul>
<li>easy</li>
<li>can't use PDB</li>
<li>Hides certain failures</li>
</ul></li>
<li>Unittests via Django TestCase

<ul>
<li>XUnit</li>
<li>setup/Teardown</li>
<li>adds db fixtures</li>
<li>assertions</li>
<li>mail testing/inbox testing</li>
<li>url testing</li>
</ul></li>
<li>TestCase

<ul>
<li>Browserless Request/Response testing</li>
<li>Similar to sfBrowser in symfony</li>
</ul></li>
<li>Google Summer of Code (for Django 1.2)

<ul>
<li>Coverage reports!</li>
</ul></li>
<li>I need to learn PDB</li>
</ul>

<h4>Deploying Django -</h4>

<p>Run mod_wsgi in daemon mode.</p>

<h3>Day 2</h3>

<h4><a href="http://blog.ianbicking.org/2009/09/10/a-new-self-definition-for-foss/">Keynote - Ian Bicking</a></h4>

<p>GNU Manifest:</p>

<blockquote>
  <p>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. ...</p>
  
  <p>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. </p>
</blockquote>

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

<h3>Using Django in Non-standard ways - Eric Florenzano</h3>

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

<ul>
<li>reasons: writing a fb app - no auth needed</li>
<li>no shoehorning needed - saves time - less overhead</li>
</ul></li>
<li>skip the orm?

<ul>
<li>legacy dbs</li>
<li>non standard or db (or non-relational database)</li>
<li>no database</li>
</ul></li>
<li>wsgi middleware has some cool shit

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

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

<h4>Real-time web and other Buzzwords - Chris Wanstrath</h4>

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

<p>Also:
* see webhooks 
* see pubsubhubub</p>

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

<ul>
<li>PBS moved from perl to django - build a lot of reusable apps</li>
<li>convincing your superiors

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

<h3>Day 3</h3>

<h4><a href="http://www.slideshare.net/twleung/djangocon-2009-keynote">Keynote</a> - Ted Leung - Sun</h4>

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

<h4><a href="http://immike.net/files/scaling_django_dc09.pdf">Scaling Django</a> Mike Malone</h4>

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

<h4><a href="http://heisel.org/blog/2009/09/11/gearman/">Gearman - working later</a> - Chris Heisel</h4>

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

<p>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.</p>

<p>More talks <a href="http://djangocon.pbworks.com/Slides">here</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://spindrop.us/2009/09/15/djangocon-wrapup/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
