<?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>Coffee on the Keyboard &#187; webdev</title>
	<atom:link href="http://coffeeonthekeyboard.com/tag/webdev/feed/" rel="self" type="application/rss+xml" />
	<link>http://coffeeonthekeyboard.com</link>
	<description>by James Socol</description>
	<lastBuildDate>Mon, 06 Feb 2012 23:33:42 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/>		<item>
		<title>Acronyms you should know: MTTD and MTTR</title>
		<link>http://coffeeonthekeyboard.com/acronyms-you-should-know-mttd-and-mttr-597/</link>
		<comments>http://coffeeonthekeyboard.com/acronyms-you-should-know-mttd-and-mttr-597/#comments</comments>
		<pubDate>Tue, 10 May 2011 19:32:47 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[continuous deployment]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[sumo]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=597</guid>
		<description><![CDATA[If you&#8217;re a SUMO contributor, there are two acronyms you will start to hear more often from us developers: MTTD and MTTR. They mean &#8220;mean time to detect&#8221; and &#8220;mean time to resolve,&#8221; respectively, and they refer to how long it takes to detect an issue in production, and how long it takes to resolve that [...]]]></description>
			<content:encoded><![CDATA[<p>If you&#8217;re a <a href="https://support.mozilla.com/">SUMO</a> contributor, there are two acronyms you will start to hear more often from us developers: <strong>MTTD</strong> and <strong>MTTR</strong>.</p>
<p>They mean &#8220;<strong>mean time to detect</strong>&#8221; and &#8220;<strong>mean time to resolve</strong>,&#8221; respectively, and they refer to how long it takes to <strong>detect</strong> an issue in production, and how long it takes to <strong>resolve</strong> that issue once it&#8217;s detected.</p>
<p>As we move toward <strong><a href="http://coffeeonthekeyboard.com/the-future-of-sumo-development-511/">continuous deployment</a></strong>, these are two of the metrics we&#8217;ll be using to gauge the effectiveness of our tools and processes.</p>
<p>For major production issues, <strong>our MTTR is actually fairly good</strong> right now—if it&#8217;s something that cannot wait until the next scheduled release, it takes us 60-90 minutes from becoming aware of an issue to pushing a fix. I think we can do better with better release processes, but we&#8217;re starting off pretty good and going to <strong>get better</strong>, which is great.</p>
<p>Our <strong>MTTD</strong>, on the other hand, needs work. <a href="http://moxie.jamessocol.com/bugstats/sumo/2.8.1">SUMO 2.8.1</a> upgraded <a href="http://www.djangoproject.com/">Django</a> and included a sweeping change to our CSRF protection—this necessarily affected every form on the site. We discovered three related issues that warranted immediate hotfixes, but we didn&#8217;t discover two of them for <strong>almost two days</strong> when our contributors brought them to our attention.</p>
<p>It&#8217;s <strong>great</strong> that our contributors pointed out these issues to us. Our <strong>community is a critical part</strong> of &#8220;detection&#8221; and I want to encourage everyone to point out issues in the <a href="https://support.mozilla.com/forums/contributors">forums</a> or <a href="irc://irc.mozilla.org/sumodev">IRC</a>. It&#8217;s extremely helpful!</p>
<p>But there are things we can do, too, to <strong>notice things faster</strong>. One thing we are working to add is <a href="http://codeascraft.etsy.com/2010/12/08/track-every-release/"><strong>business metric graphs</strong></a>. We have useful data in <a href="http://ganglia.sourceforge.net/">Ganglia</a> right now, but we will be using <a href="http://graphite.wikidot.com/">Graphite</a> and <a href="http://codeascraft.etsy.com/">Etsy</a>&#8216;s <a href="https://github.com/etsy/statsd">StatsD</a> to peer into <strong>what our users are doing</strong>. If we deploy a change and notice that no one is <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=654827">previewing articles</a>, for example, we know immediately that we have an issue and can <strong>start diagnosing and fixing it</strong>.</p>
<p>If you follow SUMO development, you&#8217;ll hear us start using terms like MTTD, MTTR, &#8220;detection,&#8221; more, and talking about how to reduce them. We welcome your input and ideas as we start working on these challenges. And of course, keep telling us when things are broken!</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/acronyms-you-should-know-mttd-and-mttr-597/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>A brief SumoDev update</title>
		<link>http://coffeeonthekeyboard.com/a-brief-sumodev-update-578/</link>
		<comments>http://coffeeonthekeyboard.com/a-brief-sumodev-update-578/#comments</comments>
		<pubDate>Sat, 12 Mar 2011 18:54:52 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[continuous deployment]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[sumo]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=578</guid>
		<description><![CDATA[A little while ago, I said that I thought we got a B in Q1, but we could move up to an A with a little more work. (This is my favorite grading system: everyone starts at 0 and works up.) Well, we landed two things: Initial JavaScript tests. (And for showfor.) Stuck a crontab [...]]]></description>
			<content:encoded><![CDATA[<p>A <a title="SUMO in Q2" href="http://coffeeonthekeyboard.com/sumo-in-q2-563/">little while ago</a>, I said that I thought we got a B in Q1, but we could move up to an A with a little more work. (This is my favorite grading system: everyone starts at 0 and works up.)</p>
<p>Well, we landed two things:</p>
<ul>
<li>Initial <a href="https://github.com/jsocol/kitsune/commit/1b03a4de5">JavaScript tests</a>. (And for <a href="https://github.com/jsocol/kitsune/commit/c28ee79bfc0">showfor</a>.)</li>
<li>Stuck a <a href="https://github.com/jsocol/kitsune/commit/bb98aede2">crontab</a> in git.</li>
</ul>
<p>I said these two things would bring us up to an A, so, way to go team!</p>
<p>We entered this quarter with 5 goals around <a href="https://wiki.mozilla.org/Support:Sumodev/Continuous_Deployment#Q1_2011">Continuous Deployment</a>. We&#8217;ve hit three. The other two were stretch goals, and it looks like we&#8217;ll miss them. It&#8217;s been a particularly busy quarter for IT, and there&#8217;s just a bunch of work left to get the JS tests into CI. We&#8217;ll carry those forward into Q2 along with our goals of releasing every week and dropping the &#8216;next&#8217; branch.</p>
<p>I&#8217;m really proud of my team and the work we&#8217;ve done this quarter. We&#8217;ve not only done great work improving the user experience across the site—especially for mobile users—we&#8217;ve also made significant progress toward simplifying and streamlining our releases, which will be crucial to CD.</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/a-brief-sumodev-update-578/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Weekly Update for 11/3/11</title>
		<link>http://coffeeonthekeyboard.com/weekly-update-for-11311-575/</link>
		<comments>http://coffeeonthekeyboard.com/weekly-update-for-11311-575/#comments</comments>
		<pubDate>Fri, 11 Mar 2011 23:55:58 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[node python]]></category>
		<category><![CDATA[sumo]]></category>
		<category><![CDATA[webdev]]></category>
		<category><![CDATA[weekly update]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=575</guid>
		<description><![CDATA[Been a busy week! Helped run down an issue with our ads on Reddit. Updated django-multidb-router. Learned a little about ContextDecorator and how to do that in Python 2.6. Shipped SUMO 2.6.1. Wrapped up SUMO 2.6.2, ready to go this weekend. Rolling through activity logs in SUMO 2.7. Building models. Reviewing code. Did some work [...]]]></description>
			<content:encoded><![CDATA[<p>Been a busy week!</p>
<ul>
<li>Helped run down an issue with our ads on Reddit.</li>
<li>Updated <a href="https://github.com/jbalogh/django-multidb-router">django-multidb-router</a>.
<ul>
<li>Learned a little about <a href="http://docs.python.org/dev/whatsnew/3.2.html#contextlib">ContextDecorator </a>and how to do that in Python 2.6.</li>
</ul>
</li>
<li>Shipped <a href="http://moxie.jamessocol.com/bugstats/sumo/2.6.1">SUMO 2.6.1</a>.</li>
<li>Wrapped up <a href="http://moxie.jamessocol.com/bugstats/sumo/2.6.2">SUMO 2.6.2,</a> ready to go this weekend.</li>
<li>Rolling through activity logs in <a href="http://moxie.jamessocol.com/bugstats/sumo/2.7">SUMO 2.7</a>.
<ul>
<li>Building models.</li>
<li>Reviewing code.</li>
</ul>
</li>
<li>Did some work on <a href="http://rasputinproject.org">Rasputin</a>:
<ul>
<li>Github let me have the URL <a href="https://github.com/rasputin">github.com/rasputin</a>!</li>
<li><a href="https://github.com/rasputin/rasputin-node">rasputin-node</a> is way more node-like.
<ul>
<li>I could really use input on the <a href="https://github.com/rasputin/rasputin-node/tree/amqp">AMQPBackend</a>.</li>
</ul>
</li>
<li>Added better threaded and multiprocess dispatchers to <a href="https://github.com/rasputin/rasputin">rasputin for Django</a>.
<ul>
<li>I think I need to punt on the logging module and go with <a href="https://github.com/mitsuhiko/logbook">Logbook</a>.</li>
<li>Want to get an AMQPBackend started.</li>
</ul>
</li>
</ul>
</li>
<li>Updated <a href="https://github.com/jsocol/logbot">logbot</a> to work with node 0.5.0 and <a href="https://github.com/indexzero/daemon.node">daemonize.node</a>.</li>
<li>Fixed a <a href="https://github.com/jsocol/bleach/commit/d9f2cf6cc">Bleach bug</a>.</li>
<li>Spent a long time tracking down an issue with celery: it just won&#8217;t quit. Literally, it doesn&#8217;t much care about SIGINT and SIGTERM.</li>
</ul>
<p>Now to start a busy weekend!</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/weekly-update-for-11311-575/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Weekly Update 04/03/2011</title>
		<link>http://coffeeonthekeyboard.com/weekly-update-04032011-558/</link>
		<comments>http://coffeeonthekeyboard.com/weekly-update-04032011-558/#comments</comments>
		<pubDate>Fri, 04 Mar 2011 19:01:41 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[webdev]]></category>
		<category><![CDATA[weekly update]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=558</guid>
		<description><![CDATA[OK, in line with my 2011 goals and because I&#8217;m sick of not remembering what I did last week, I&#8217;m restarting the weekly update posts. I hope you like hearing about the minutia of my job! (Just kidding. I write these for me.) I&#8217;m going to try a &#8220;sometime Friday&#8221; schedule instead of weekends or [...]]]></description>
			<content:encoded><![CDATA[<p>OK, in line with my <a href="http://coffeeonthekeyboard.com/2011-goals-520/">2011 goals</a> and because I&#8217;m sick of not remembering what I did last week, I&#8217;m restarting the weekly update posts. I hope you like hearing about the minutia of my job! (Just kidding. I write these for me.) I&#8217;m going to try a &#8220;sometime Friday&#8221; schedule instead of weekends or at night.</p>
<p>Also, this one will cover the last four-and-a-half-ish weeks, since it&#8217;s been a while.</p>
<ul>
<li>Released <a href="http://pypi.python.org/pypi/bleach">Bleach 1.0</a> to almost no fanfare. Still, it&#8217;s an improvement and you should upgrade.</li>
<li>Demoed <a href="https://github.com/jsocol/django-waffle">Waffle</a> at <a href="http://hackandtell.org/">Hack and Tell</a> to <a href="http://hackandtell.blip.tv/file/4738389/">moderate interest</a> and good feedback.</li>
<li>Started pushing <a href="https://github.com/jsocol/phake">old</a> <a href="https://github.com/jsocol/maveric">projects</a> to Github. Next up? Old WordPress plugins.</li>
<li>Kinda/sorta released <a href="https://github.com/jsocol/rasputin">Rasputin</a> and <a href="https://github.com/jsocol/rasputin-node">Rasputin-Node</a> super-pre-alpha code.</li>
<li>Wrote <a href="http://pypi.python.org/pypi/django-dnt">Django-DNT</a>.</li>
<li>Wrote <a href="https://github.com/jsocol/django-adminplus">Django AdminPlus</a>. (<a title="O Hai Django AdminPlus" href="http://coffeeonthekeyboard.com/o-hai-django-adminplus-568/">blog post</a>)</li>
<li>Was in Mountain View for three days, during which:
<ul>
<li>Helped Luke put together a development <a href="https://wiki.mozilla.org/MDN/MDNv1">roadmap for MDN</a>.</li>
<li>Agreed to help Sid put together a <a href="http://dnt.mozilla.org/">Do-Not-Track demo</a>.</li>
<li>Helped, for my part, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=622752">move SUMO to Phoenix</a>.</li>
<li>Was interviewed on camera for <a href="http://rypple.com/tour">Rypple</a>.</li>
<li>Interviewed an awesome intern candidate.</li>
<li>Went to <a href="http://www.flickr.com/photos/jamessocol/sets/72157626036265594/">Alcatraz</a>. (It&#8217;s OK, they let me out.)</li>
</ul>
</li>
<li>Released <a href="http://moxie.jamessocol.com/bugstats/sumo/2.5">SUMO 2.5</a>. <a href="http://2.bp.blogspot.com/_47mDU4vB2hk/S7YlsUtcLRI/AAAAAAAAAP8/giQXGFkYuIg/s1600/fireworks.jpg">Nice work, team</a>!</li>
<li>Patched and released a quick <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=634608">SUMO 2.5.1</a>.</li>
<li>Release <a href="http://moxie.jamessocol.com/bugstats/sumo/2.6">SUMO 2.6</a>.</li>
<li>Releasing <a href="http://moxie.jamessocol.com/bugstats/sumo/2.6.1">SUMO 2.6.1</a> on Tuesday.</li>
<li>Rolling on <a href="http://moxie.jamessocol.com/bugstats/sumo/2.6.2">SUMO 2.6.2</a>. Lots of reviews, lots of little things.
<ul>
<li>Including an <a href="https://wiki.mozilla.org/PostCrash#API">API for Socorro</a> for our Post Crash experience.</li>
</ul>
</li>
<li>Finished fixing <a href="https://hudson.mozilla.org/job/sumo-next/?">Hudson builds for our &#8220;next&#8221; branch</a>.</li>
<li>Wrote up a design doc for a new SUMO feature.</li>
<li>Started planning out Q2, both our feature roadmap and our <a title="SUMO in Q2" href="http://coffeeonthekeyboard.com/sumo-in-q2-563/">Continuous Deployment progress</a>.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/weekly-update-04032011-558/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>O Hai Django AdminPlus</title>
		<link>http://coffeeonthekeyboard.com/o-hai-django-adminplus-568/</link>
		<comments>http://coffeeonthekeyboard.com/o-hai-django-adminplus-568/#comments</comments>
		<pubDate>Fri, 04 Mar 2011 15:28:03 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=568</guid>
		<description><![CDATA[Last night, as happens sometimes, I was wishing it was possible to add some of our custom admin views to the Django admin&#8217;s index page. It&#8217;s kind of a pain to have to type the URL every time, especially when talking to other people: &#8220;It&#8217;s in the admin, but no, you can&#8217;t&#8230; just go to [...]]]></description>
			<content:encoded><![CDATA[<p>Last night, as happens sometimes, I was wishing it was possible to add some of our custom admin views to the Django admin&#8217;s index page. It&#8217;s kind of a pain to have to type the URL every time, especially when talking to other people: &#8220;It&#8217;s in the admin, but no, you can&#8217;t&#8230; just go to this URL.&#8221;</p>
<p>So I was reading the <a href="http://docs.djangoproject.com/en/dev/ref/contrib/admin/">Django admin docs</a> and, frankly, they aren&#8217;t great. They tell you about the benefits of subclassing <code>AdminSite</code>, or running multiple <code>AdminSite</code>s, but they never really mention <em>how to do it</em>.</p>
<p>So, with a little trial and error, I wrote my <code>AdminSite</code> subclass and figured out how to use it. It simplified a bunch of code in our custom admin app, so I thought I would share it.</p>
<p>Here&#8217;s <a href="https://github.com/jsocol/django-adminplus">Django AdminPlus</a> (and <a href="http://pypi.python.org/pypi/django-adminplus">on PyPI</a>).</p>
<p>AdminPlus adds a method, <code>admin.site.register_view</code>, that connects an arbitrary view function to your admin, and puts a link to it on the admin index page. If you put calls to <code>register_view</code> in <code>someapp/admin.py</code>, they&#8217;re picked up during the normal <code>admin.autodiscover()</code> process.</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="co1"># myapp/admin.py</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">from</span> django.<span class="me1">contrib</span> <span class="kw1">import</span> admin</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">from</span> django.<span class="me1">shortcuts</span> <span class="kw1">import</span> render_to_response</div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li2">
<div class="de2"><span class="kw1">def</span> my_admin_view<span class="br0">&#40;</span>request<span class="br0">&#41;</span>:</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">return</span> render_to_response<span class="br0">&#40;</span><span class="st0">&#8216;myapp/admin/view.html&#8217;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">admin.<span class="kw3">site</span>.<span class="me1">register_view</span><span class="br0">&#40;</span><span class="st0">&#8216;mypath&#8217;</span>, my_admin_view<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="co1"># With an optional &#8216;name&#8217; parameter:</span></div>
</li>
<li class="li2">
<div class="de2"><span class="kw1">def</span> my_other_admin_view<span class="br0">&#40;</span>request<span class="br0">&#41;</span>:</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">return</span> render_to_response<span class="br0">&#40;</span><span class="st0">&#8216;myapp/admin/other.html&#8217;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">admin.<span class="kw3">site</span>.<span class="me1">register_view</span><span class="br0">&#40;</span><span class="st0">&#8216;otherpath&#8217;</span>, my_other_admin_view, <span class="st0">&#8216;Fancy Stuff!&#8217;</span><span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>Assuming your admin URLs start with <code>admin/</code>, this will make <code>my_admin_view</code> available at <code>admin/mypath</code> and <code>my_other_admin_view</code> available at <code>admin/otherpath</code>, but it also puts them in a new section on the admin index, so you don&#8217;t even need to worry about the URL.</p>
<p>If no name is given we try to guess from the name of the view function.</p>
<p>That&#8217;s about it! Read the <a href="https://github.com/jsocol/django-adminplus#readme">README</a> and <a href="https://github.com/jsocol/django-adminplus/tree/master/docs">the other docs</a> and enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/o-hai-django-adminplus-568/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>The Future of TodaysMeet</title>
		<link>http://coffeeonthekeyboard.com/the-future-of-todaysmeet-552/</link>
		<comments>http://coffeeonthekeyboard.com/the-future-of-todaysmeet-552/#comments</comments>
		<pubDate>Sun, 30 Jan 2011 22:02:28 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[comet]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[events]]></category>
		<category><![CDATA[html5]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[node.js]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[sockets]]></category>
		<category><![CDATA[todaysmeet]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=552</guid>
		<description><![CDATA[This is the second half of a two-part post. Start with part 1. TodaysMeet is an interesting challenge because it has components that are absolutely real-time and should be built like a messaging system, not a CMS, and parts that aren&#8217;t real-time at all, and can totally be built like a CMS. TodaysMeet has one [...]]]></description>
			<content:encoded><![CDATA[<p><em>This is the second half of a two-part post. Start with <a href="http://coffeeonthekeyboard.com/the-problem-with-todaysmeet-550/">part 1</a>.</em></p>
<p><a href="http://todaysmeet.com/">TodaysMeet</a> is an interesting challenge because it has components that are absolutely real-time and should be built like a messaging system, not a CMS, and parts that aren&#8217;t real-time at all, and can totally be built like a CMS.</p>
<p>TodaysMeet has one loosely-coupled component, its <a href="http://twitter.com/">Twitter</a> integration. The site knows nothing about the daemon that executes Twitter searches and populates the database. If anything, I think it&#8217;s currently <em>too</em> loosely coupled, but it&#8217;s given me some insight into how to build the new system.</p>
<p>The guiding principles of this design are:</p>
<ul>
<li><strong><abbr title="Don't Repeat Yourself.">DRY</abbr></strong>. Only one part of the app should know about the canonical data store (MySQL for now).</li>
<li><strong>Loosely coupled</strong>. A failure in one part of the service shouldn&#8217;t affect other parts.</li>
<li><strong>Real-time</strong>, event-driven. Periodic polling is bad. Periodic Twitter searches are particularly so.</li>
<li>Use the <strong>right tool</strong> for the right job.</li>
</ul>
<p>Obviously I&#8217;m a fan of <a href="http://www.djangoproject.com/">Django</a>. I could port TodaysMeet directly, using something like <a href="https://github.com/mozilla/playdoh">Playdoh</a> to bootstrap the process, and get done relatively quickly. Django gets me a lot of stuff for free: a solid data store, users (something I want to add in a limited capacity, eventually), even a framework for building the crons and daemons necessary for running the site.</p>
<p>But Django is a lousy way to serve real-time content via long polling or <a href="http://socket.io/">socket</a> connections.</p>
<p>I can easily serve real-time content via <a href="http://nodejs.org/">Node.js</a>. I can do long polling or sockets. It&#8217;s also a great way to handle Twitter integration because I can just leave a connection to their <a href="http://dev.twitter.com/pages/streaming_api">streaming API</a>, and handle new tweets as soon as they happen instead of checking periodically.</p>
<p>But even with things like <a href="http://sequelizejs.com/">Sequelize</a>, MySQL connectivity through Node still feels awkward. (I think it will get better but I&#8217;m impatient.) And serving fairly static content is just weird.</p>
<p>So, <a href="http://en.wikipedia.org/wiki/Towelie">Towelie</a>, do you want to go real-time with Node, or do you want to use Django?</p>
<p>I&#8217;ll use&#8230; both!</p>
<p>Read on for a more technical overview. And a picture!</p>
<p><span id="more-552"></span></p>
<p>The core of this system is <a href="http://redis.io/">Redis</a>, acting as a message broker and light-weight data store.</p>
<p><a href="http://coffeeonthekeyboard.com/wp-content/uploads/2011/01/TodaysMeetInfra.png"><img class="aligncenter size-full wp-image-555" title="TodaysMeetInfra" src="http://coffeeonthekeyboard.com/wp-content/uploads/2011/01/TodaysMeetInfra.png" alt="" width="580" height="379" /></a></p>
<p>The three yellow blocks are event-driven daemons. <a href="https://github.com/jsocol/rasputin">Rasputin</a> is a binding that allows Django to subscribe to events from the message broker and act on them. That means I don&#8217;t need to pass messages between Node and Django over HTTP. Rasputin also includes bindings for Python to send messages back to the message broker.</p>
<p>Django will run in FastCGI mode, and <a href="http://wiki.nginx.org/Main">Nginx</a> will sit in front and proxy traffic to Node or Django based on URL.</p>
<p>I&#8217;ll write more about Rasputin when it&#8217;s more mature, but it&#8217;s designed to build an extremely loosely coupled system. It essentially makes Django an <a href="http://en.wikipedia.org/wiki/Observer_pattern">observer</a>, and observable, in a multi-process, multi-language system. When one actor publishes a message, there&#8217;s no guarantee anyone is listening, and there may be multiple people listening.</p>
<p>This architecture will give me a ton of flexibility in the future. I can add or remove services from the message broker without affecting the others, so if I wanted to, for example, replace MySQL with <a href="http://www.mongodb.org/">MongoDB</a> or <a href="http://couchdb.apache.org/">CouchDB</a>, I could easily build a daemon to start dumping data into the data store without so much as changing a configuration in Django. If I want to replace a component, for example replacing Node with <a href="http://twistedmatrix.com/trac/">Twisted</a> for Twitter integration, it&#8217;s as easy as starting one service and stopping another. I can load test new components with production data my subscribing to the right channels.</p>
<p>It&#8217;s taken a long time, but I think this is finally a way of building TodaysMeet that does it right and keeps me interested—attention is what&#8217;s best for users in the end.</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/the-future-of-todaysmeet-552/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The Problem with TodaysMeet</title>
		<link>http://coffeeonthekeyboard.com/the-problem-with-todaysmeet-550/</link>
		<comments>http://coffeeonthekeyboard.com/the-problem-with-todaysmeet-550/#comments</comments>
		<pubDate>Sat, 29 Jan 2011 16:35:42 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[todaysmeet]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=550</guid>
		<description><![CDATA[TodaysMeet is a project I started in 2008 to help my father solve a problem in one of his classes. The fact that it&#8217;s as popular as it is—mostly in education—never ceases to amaze me. Unfortunately, I don&#8217;t give TodaysMeet the attention it, and more importantly its users, deserve. This is because TodaysMeet has two [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://todaysmeet.com/">TodaysMeet</a> is a project I started in 2008 to help <a href="http://speedchange.blogspot.com">my father</a> solve a problem in one of his classes. The fact that it&#8217;s as popular as it is—mostly in education—never ceases to amaze me.</p>
<p>Unfortunately, I don&#8217;t give TodaysMeet the attention it, and more importantly its users, deserve. This is because TodaysMeet has two fatal flaws that, if they haven&#8217;t crippled it yet, will someday.</p>
<ul>
<li>The UI is based on proof-of-concept JavaScript.</li>
<li>The back-end is based on <a href="http://svn.jamessocol.com/maveric">my own framework</a>.</li>
</ul>
<p>What follows is the sad history of TodaysMeet development.</p>
<h3>Origin Story</h3>
<p>TodaysMeet came out of a conversation between my father and I, but it&#8217;s origins are slightly older. In some downtime in late 2007 I was trying to familiarize myself with various JavaScript frameworks by writing a UI for the same back-end in each of them. It was a pretty basic Ajax comment system. I believe it polled the server every minute. If I remember correctly, I got busy and abandoned it after creating <a href="http://www.prototypejs.org/">Prototype</a> and <a href="http://jquery.com/">jQuery</a> versions.</p>
<p>Around the same time I was enamored of <a href="http://rubyonrails.org/">Rails</a>, and trying to round out <a href="http://svn.jamessocol.com/maveric">Maveric</a> into a decent Rails-inspired PHP framework.</p>
<p>So when my father said he wanted something like Twitter for a single classroom, that he could project on a wall, and wouldn&#8217;t require signing up, I put these things together in my head. TodaysMeet is basically the proof-of-concept Prototype JS running on top of an old version of Maveric.</p>
<h3>The Situation Now</h3>
<p><a href="http://www.brandonsavage.net/why-every-developer-should-write-their-own-framework/">Every developer should write a framework</a>, I think it&#8217;s a fantastic learning experience. But they should never build a production website out of it.</p>
<p>Even though Maveric got a little better after I created TodaysMeet, it&#8217;s still based on an untested, unsupported framework with no support for basic things like storage back-ends or caching.</p>
<p>The UI is still based on Prototype, which I haven&#8217;t used in years, and the fundamental client-server interactions are still that original &#8220;learning the library&#8221; code.</p>
<p>Essentially, TodaysMeet is a prototype masquerading as a production-ready product.</p>
<p>The result is that working on it is slow, difficult, and frankly unpleasant. Adding features—like the long-promised password protected rooms—is painful and, with no test suite, dangerous. The one real feature I added, Twitter integration, barely works when it works at all.</p>
<p>But users don&#8217;t care about any of that. They see that it works, mostly. They might see that it doesn&#8217;t get much attention and the UI feels three years old (because it is, of course).</p>
<h3>Where Do We Go From Here?</h3>
<p>TodaysMeet <em>could</em> be awesome, but it needs to go all the way down to the basic stack and get rebuilt. TodaysMeet is an absolutely perfect candidate for all sorts of new, exciting tools and techniques. To use any of them means starting over.</p>
<p>This is the first of a two-part post. In the next part, I&#8217;m going to outline the architecture I <em>want</em>, instead of the architecture I <em>have</em>.</p>
<p>Hopefully, some social aspect of talking about this will lead to me actually doing something about it.</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/the-problem-with-todaysmeet-550/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Bleach 1.0rc2</title>
		<link>http://coffeeonthekeyboard.com/bleach-1-0rc2-544/</link>
		<comments>http://coffeeonthekeyboard.com/bleach-1-0rc2-544/#comments</comments>
		<pubDate>Tue, 25 Jan 2011 14:30:30 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[bleach]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=544</guid>
		<description><![CDATA[Bleach is very nearly ready for a 1.0! After I announced Bleach 1.0rc1, a couple of important issues were found. Those have now been fixed. (Thanks, guys!) One of these was backported to 0.5, and I uploaded 0.5.1 to PyPI yesterday. If you use Bleach, please try out the new version. You can get it [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://github.com/jsocol/bleach"><strong>Bleach</strong></a> is very nearly ready for a 1.0!</p>
<p>After <a href="http://coffeeonthekeyboard.com/bleach-1-0rc-527/">I announced Bleach 1.0rc1</a>, a couple of important issues were found. Those have now been fixed. (Thanks, guys!) One of these was backported to 0.5, and I uploaded 0.5.1 to PyPI yesterday.</p>
<p>If you use Bleach, please try out the new version. You can get it from Github and install with pip.</p>
<h3>Upgrade Path</h3>
<p>Bleach 1.0 drops the Bleach class. 0.5.x is intended as a stepping stone that will let you start using the new API.</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="co1"># Bleach &lt;= 0.5</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">from</span> bleach <span class="kw1">import</span> Bleach</div>
</li>
<li class="li1">
<div class="de1">b = Bleach<span class="br0">&#40;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">b.<span class="me1">clean</span><span class="br0">&#40;</span><span class="st0">&#8216;&lt;script&gt;evil()&lt;/script&gt;&#8217;</span><span class="br0">&#41;</span></div>
</li>
<li class="li2">
<div class="de2">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="co1"># Bleach &gt;= 0.5</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">import</span> bleach</div>
</li>
<li class="li1">
<div class="de1">bleach.<span class="me1">clean</span><span class="br0">&#40;</span><span class="st0">&#8216;&lt;script&gt;evil()&lt;/script&gt;&#8217;</span><span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>Hopefully this change will be minor unless you were using URL filters on <code>linkify()</code>. In that case, the change is:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="co1"># Bleach &lt;= 0.5</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">from</span> bleach <span class="kw1">import</span> Bleach</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">class</span> MyBleach<span class="br0">&#40;</span>Bleach<span class="br0">&#41;</span>:</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">def</span> filter_url<span class="br0">&#40;</span><span class="kw2">self</span>, url<span class="br0">&#41;</span>:</div>
</li>
<li class="li2">
<div class="de2">&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">return</span> <span class="st0">&#8216;http://google.com&#8217;</span></div>
</li>
<li class="li1">
<div class="de1">b = MyBleach<span class="br0">&#40;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">b.<span class="me1">linkify</span><span class="br0">&#40;</span><span class="st0">&#8216;a link to example.com&#8217;</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="co1"># Bleach &gt;= 0.5</span></div>
</li>
<li class="li2">
<div class="de2"><span class="kw1">import</span> bleach</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">def</span> filter_url<span class="br0">&#40;</span>url<span class="br0">&#41;</span>:</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">return</span> <span class="st0">&#8216;http://google.com&#8217;</span></div>
</li>
<li class="li1">
<div class="de1">bleach.<span class="me1">linkify</span><span class="br0">&#40;</span><span class="st0">&#8216;a link to example.com&#8217;</span>, filter_url=filter_url<span class="br0">&#41;</span></div>
</li>
</ol>
</div>
<p>This is a bigger change, but I hope it won&#8217;t be too bad for users, and I think it makes the whole API much more consistent. Lots of kwargs, yes, but more consistent and logical.</p>
<h3>Features</h3>
<p>Bleach 1.0 doesn&#8217;t have much in the way of new features, instead focusing on clean up, improving test coverage and security, and being ready to call it a 1.0. It does have the following changes over 0.5:</p>
<ul>
<li>A <code>skip_pre</code> option for <code>linkify()</code> to ignore URLs in <code>&lt;pre&gt;</code> sections.</li>
<li>Drops support for <code>nofollow_relative</code>. I still think that&#8217;s a good option but it needs work.</li>
</ul>
<p>So please, check out <a href="http://github.com/jsocol/bleach">Bleach</a>. I&#8217;d love feedback on the new direction. Feel free to comment or open issues.</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/bleach-1-0rc2-544/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Introducing Waffle for Django</title>
		<link>http://coffeeonthekeyboard.com/introducing-waffle-for-django-541/</link>
		<comments>http://coffeeonthekeyboard.com/introducing-waffle-for-django-541/#comments</comments>
		<pubDate>Sun, 23 Jan 2011 18:48:45 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=541</guid>
		<description><![CDATA[Waffle is a feature-flipping library for Django that strives to be easy and intuitive, and work with both the Django/Jingo/Jinja2 stack we use at Mozilla, and Django templates out of the box. Waffle lets you define various reasons a flag can be active for a given request. You can make flags active for all superusers, [...]]]></description>
			<content:encoded><![CDATA[<p><strong>Waffle</strong> is a feature-flipping library for <strong>Django</strong> that strives to be easy and intuitive, and work with both the Django/Jingo/Jinja2 stack we use at Mozilla, and Django templates out of the box.</p>
<p>Waffle lets you define various reasons a <strong>flag</strong> can be active for a given request. You can make flags active for all superusers, or authenticated users, for example. You can also define a percentage of &#8220;everyone else&#8221; who will see the flag as active.</p>
<p>If Waffle uses a dice roll to determine if the current request will have the flag turned on, it sets a <strong>cookie</strong> so the flag will continue to be active or inactive for subsequent requests. For all subsequent requests, the cookie value is respected. Waffle only sets cookies if a given flag was actually used during the request.</p>
<p>Flags are global: once a given flag is set for a user, that flag is set on every request or page view. You can test a flag on <code>/foo</code> and trust that a given user will see the same flag value on <code>/bar</code>.</p>
<p>Flags can be used in <strong>templates</strong>, in <strong>views</strong>, or even to <strong>hide whole views</strong>.</p>
<p>Optionally, Waffle can activate flags based on the <strong>query string</strong>, so you can guarantee a flag will be on or off for any request for testing.</p>
<p>Check out <a href="http://github.com/jsocol/django-waffle"><strong>Waffle</strong> on Github</a> and let me know what you think!</p>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/introducing-waffle-for-django-541/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Bleach 1.0rc</title>
		<link>http://coffeeonthekeyboard.com/bleach-1-0rc-527/</link>
		<comments>http://coffeeonthekeyboard.com/bleach-1-0rc-527/#comments</comments>
		<pubDate>Mon, 17 Jan 2011 00:40:02 +0000</pubDate>
		<dc:creator>James</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[bleach]]></category>
		<category><![CDATA[mozilla]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[webdev]]></category>

		<guid isPermaLink="false">http://coffeeonthekeyboard.com/?p=527</guid>
		<description><![CDATA[After nearly a year, I&#8217;ve got something I&#8217;d like to call Bleach 1.0. But first, I want your feedback! I incorporated some patches from the community this afternoon, and closed an issue that had been bugging me. These are all available in backwards-compatible changes between versions 0.3.5 and 0.4.0. Then there&#8217;s 0.5.0, which is the [...]]]></description>
			<content:encoded><![CDATA[<p>After nearly a year, I&#8217;ve got something I&#8217;d like to call <a href="https://github.com/jsocol/bleach/tree/1.0-branch">Bleach 1.0</a>. But first, I want your feedback!</p>
<p>I incorporated some patches from the community this afternoon, and closed an issue that had been bugging me. These are all available in backwards-compatible changes between versions <a href="https://github.com/jsocol/bleach/tree/0.3.5">0.3.5</a> and <a href="https://github.com/jsocol/bleach/tree/0.4.0">0.4.0</a>.</p>
<p>Then there&#8217;s <a href="https://github.com/jsocol/bleach/tree/0.5.0">0.5.0</a>, which is the current version on <a href="http://pypi.python.org/pypi/bleach/">PyPI</a> and is only backwards-incompatible if you&#8217;re using the <code>linkify</code> filters.</p>
<p>In 0.5.0 I added a new/old API that doesn&#8217;t require a <code>Bleach</code> object. I say &#8220;new/old&#8221; because this is actually how it worked in the first place. As of 0.5.0, much like 0.1.0, you can do:</p>
<pre>&gt;&gt;&gt; import bleach
&gt;&gt;&gt; bleach.clean('a &lt;script&gt;bad()&lt;/script&gt; string')
'a &amp;lt;script&amp;gt;bad()&amp;lt;/script&amp;gt; string'</pre>
<p>At some point, there&#8217;s a commit that says &#8220;Move to a more maintainable architecture.&#8221; I really wish I knew what I meant by that. My suspicion is that I was trying to avoid passing callables into <code>linkify()</code>.</p>
<p>So why did I change my mind and alter the API completely?</p>
<p>As I was considering a set of patches, I started thinking about whether this was going to make the API more or less logical. I didn&#8217;t want to add a huge collection of kwargs, but I didn&#8217;t want to make it necessary to instantiate multiple <code>Bleach</code> objects just because you wanted slightly different options in different places. I also wasn&#8217;t thrilled with needing to subclass <code>Bleach</code> at all.</p>
<p>Thinking this through, I came to the conclusion that lots of kwargs, with sane defaults, is better than a kind-of-stateful, unnecessarily-class-based API.</p>
<p>Please look at the <a href="https://github.com/jsocol/bleach/compare/0.3.4...1.0-branch">changes between 0.3.5 and 1.0-branch</a> and let me know what you think.</p>
<p>At the very least, before creating an official 1.0, I&#8217;m going to take the time to fix all the PEP-8 violations in <code>sanitizer.py</code>.</p>
<p>Here are the recent changes, by version:</p>
<dl>
<dt>0.3.5</dt>
<dd>
<ul>
<li>Add a <code>strip</code> kwarg to <code>clean</code> that strips blacklisted HTML instead of escaping it. (Default: <code>False</code>.)</li>
</ul>
</dd>
<dt>0.4.0</dt>
<dd>
<ul>
<li>Add a <code>strip_comments</code> kwarg to <code>clean</code> that strips HTML comments. (Note that this always happened before.) (Default: <code>True</code>.)</li>
<li>Add a <code>styles</code> kwarg to <code>clean</code> that takes a list of whitelisted CSS properties. (Note that before, allowing <code>style</code> attributes essentially allowed all CSS properties.) (Default: <code>[]</code>.)</li>
</ul>
</dd>
<dt>0.5.0</dt>
<dd>
<ul>
<li>Add a <code>nofollow_relative</code> kwarg to <code>linkify</code> that controls setting <code>rel="nofollow"</code> on relative links within the text (links starting with <code>/</code>). (Default: <code>True</code>.)</li>
<li>Add the optional, class-less API. <code>bleach.clean()</code> works exactly like <code>bleach.Bleach().clean()</code>, and similarly with <code>bleach.linkify()</code>.</li>
<li>Drop <code>Bleach.filter_url</code> and <code>Bleach.filter_text</code>. These are now kwargs passed into either version of <code>linkify</code>.</li>
</ul>
</dd>
<dt>1.0.0rc1</dt>
<dd>
<ul>
<li>Drop the <code>Bleach</code> class completely. All access is through the new API.</li>
</ul>
</dd>
<dt>latest</dt>
<ul>
<li>Clean up PEP8 violations and some general coding style.</li>
<li>Add a <code>skip_pre</code> option to <code>linkify</code> that can skip creating links inside <code>&lt;pre&gt;</code> sections. (Default: <code>False</code>.)</li>
<li>Drop <code>nofollow_relative</code>. Fred&#8217;s concerns below are 100% valid and, even though it&#8217;s not a security issue per se, I don&#8217;t want to give false impressions.</li>
</ul>
</dl>
]]></content:encoded>
			<wfw:commentRss>http://coffeeonthekeyboard.com/bleach-1-0rc-527/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

