<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10titles.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemtitles.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0"><title>Steve Losh</title><link href="http://stevelosh.com" /><updated>2012-02-05T15:53:54Z</updated><id>http://stevelosh.com/</id><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/stevelosh" /><feedburner:info uri="stevelosh" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2Fstevelosh" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fstevelosh" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/stevelosh" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fstevelosh" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fstevelosh" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fstevelosh" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><entry><title type="html">Writing Vim Plugins</title><author><name>Steve Losh</name></author><link href="http://feedproxy.google.com/~r/stevelosh/~3/5bQmL-SN0Zw/" /><updated>2011-09-06T09:13:00Z</updated><published>2011-09-06T09:13:00Z</published><id>http://stevelosh.com/blog/2011/09/writing-vim-plugins/</id><content type="html">
                    
                        
                
                    &lt;p&gt;A while ago I wrote a &lt;a href="http://stevelosh.com/blog/2010/09/coming-home-to-vim/"&gt;post&lt;/a&gt; about switching back to &lt;a href="http://www.vim.org/"&gt;Vim&lt;/a&gt;.  Since then
I&amp;#8217;ve written two plugins for Vim, one of which has been&amp;nbsp;officially&amp;nbsp;&amp;#8220;released&amp;#8221;.&lt;/p&gt;
&lt;p&gt;A couple of people have asked me if I&amp;#8217;d write a guide to creating Vim plugins.
I don&amp;#8217;t feel confident enough to write an official &amp;#8220;guide&amp;#8221;, but I do have some advice
for Vim plugin authors that might&amp;nbsp;be&amp;nbsp;useful.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#other-people-who-know-more-than-i-do"&gt;Other People Who Know More Than I Do&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#tim-pope"&gt;Tim&amp;nbsp;Pope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#scrooloose"&gt;Scrooloose&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#be-pathogen-compatible"&gt;Be&amp;nbsp;Pathogen-Compatible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#please-for-the-love-of-god-use-normal"&gt;Please, For the Love of God,&amp;nbsp;Use&amp;nbsp;normal!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mapping-keys-the-right-way"&gt;Mapping Keys the Right Way&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#when-to-map-keys"&gt;When to&amp;nbsp;Map&amp;nbsp;Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#imap-and-nmap-are-pure-evil"&gt;imap and nmap are&amp;nbsp;Pure&amp;nbsp;Evil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#let-me-configure-mappings"&gt;Let Me&amp;nbsp;Configure&amp;nbsp;Mappings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#localize-mappings-and-settings"&gt;Localize Mappings and Settings&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#localizing-mappings"&gt;Localizing&amp;nbsp;Mappings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#localizing-settings"&gt;Localizing&amp;nbsp;Settings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#autoload-is-your-friend"&gt;Autoload is&amp;nbsp;Your&amp;nbsp;Friend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#backwards-compatibility-is-a-big-deal"&gt;Backwards Compatibility is a Big Deal&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#what-matters-for-backards-compatibility"&gt;What Matters for&amp;nbsp;Backards&amp;nbsp;Compatibility?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#use-semantic-versioning-so-i-can-stay-sane"&gt;Use Semantic Versioning So I Can&amp;nbsp;Stay&amp;nbsp;Sane&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#document-everything"&gt;Document Everything&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#pick-some-requirements-and-stick-to-them"&gt;Pick Some Requirements and Stick&amp;nbsp;to&amp;nbsp;Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#write-a-readme"&gt;Write a&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create-a-simple-website"&gt;Create a&amp;nbsp;Simple&amp;nbsp;Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#write-a-vim-help-document"&gt;Write a Vim&amp;nbsp;Help&amp;nbsp;Document&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#keep-a-changelog"&gt;Keep&amp;nbsp;a&amp;nbsp;Changelog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#making-vimscript-palatable"&gt;Making Vimscript Palatable&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#wrap-everything"&gt;Wrap.&amp;nbsp;Everything.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#scripting-vim-with-other-languages"&gt;Scripting Vim with&amp;nbsp;Other&amp;nbsp;Languages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#unit-testing-will-make-you-drink"&gt;Unit Testing Will Make&amp;nbsp;You&amp;nbsp;Drink&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#tldr"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;TL&lt;/span&gt;&lt;/span&gt;;&lt;span class="caps"&gt;&lt;span class="caps"&gt;DR&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="other-people-who-know-more-than-i-do"&gt;Other People Who Know More Than&amp;nbsp;I&amp;nbsp;Do&lt;/h2&gt;
&lt;p&gt;Writing two decently-sized Vim plugins has given me some experience, but there are
a lot of people that know far more than I do.  There are two in particular that come
to mind.  I&amp;#8217;d love for them to write some guides (or even books) about modern-day&amp;nbsp;Vim&amp;nbsp;scripting.&lt;/p&gt;
&lt;h3 id="tim-pope"&gt;Tim&amp;nbsp;Pope&lt;/h3&gt;
&lt;p&gt;The first is &lt;a href="http://tpo.pe/"&gt;Tim Pope&lt;/a&gt;.  He&amp;#8217;s written a ton of Vim plugins like &lt;a href="https://github.com/tpope/vim-pathogen"&gt;Pathogen&lt;/a&gt;,
&lt;a href="https://github.com/tpope/vim-surround"&gt;Surround&lt;/a&gt;, &lt;a href="https://github.com/tpope/vim-repeat"&gt;Repeat&lt;/a&gt;, &lt;a href="https://github.com/tpope/vim-speeddating"&gt;Speeddating&lt;/a&gt; and &lt;a href="https://github.com/tpope/vim-fugitive"&gt;Fugitive&lt;/a&gt;.  Each of those is clear,
focused&amp;nbsp;and&amp;nbsp;polished.&lt;/p&gt;
&lt;p&gt;It would be awesome to read a guide on the ins and outs of Vim scripting&amp;nbsp;by&amp;nbsp;him.&lt;/p&gt;
&lt;h3 id="scrooloose"&gt;Scrooloose&lt;/h3&gt;
&lt;p&gt;The other person that comes to mind is &lt;a href="http://got-ravings.blogspot.com/"&gt;Scrooloose&lt;/a&gt;, author of &lt;a href="https://github.com/scrooloose/nerdtree"&gt;NERDTree&lt;/a&gt;,
&lt;a href="https://github.com/scrooloose/nerdcommenter"&gt;NERDCommenter&lt;/a&gt; and &lt;a href="https://github.com/scrooloose/syntastic"&gt;Syntastic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;His plugins are large and full-featured but work incredibly well, considering how
tricky and painful Vimscript is to work with.  I&amp;#8217;d love to read a guide on writing
large-scale Vim plugins&amp;nbsp;by&amp;nbsp;him.&lt;/p&gt;
&lt;h2 id="be-pathogen-compatible"&gt;Be&amp;nbsp;Pathogen-Compatible&lt;/h2&gt;
&lt;p&gt;It&amp;#8217;s 2011.  When writing your plugin, &lt;em&gt;please&lt;/em&gt; make its source compatible with
&lt;a href="https://github.com/tpope/vim-pathogen"&gt;Pathogen&lt;/a&gt;.  It&amp;#8217;s very easy to do this &amp;#8212; just set up your project&amp;#8217;s files&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;yourplugin/
    doc/
        yourplugin.txt
    plugin/
        yourplugin.vim
    ...
    README
    LICENSE
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will let users use Pathogen (or &lt;a href="https://github.com/gmarik/vundle"&gt;Vundle&lt;/a&gt;) to install and use&amp;nbsp;your&amp;nbsp;plugin.&lt;/p&gt;
&lt;p&gt;The days of &amp;#8220;unzip and drag the files into the right directories&amp;#8221; and the horror of
Vimballs are over.  Pathogen and Vundle are the right way to manage plugins, so let
your users&amp;nbsp;use&amp;nbsp;them.&lt;/p&gt;
&lt;h2 id="please-for-the-love-of-god-use-normal"&gt;Please, For the Love of God,&amp;nbsp;Use&amp;nbsp;normal!&lt;/h2&gt;
&lt;p&gt;My first piece of actual scripting advice is something simple but important.  If
you&amp;#8217;re writing a Vim plugin and need to perform some actions, you might be tempted to
use &lt;code&gt;normal&lt;/code&gt;.  Don&amp;#8217;t.  Instead, you need to use &lt;code&gt;normal!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;normal!&lt;/code&gt; is like &lt;code&gt;normal&lt;/code&gt;, but ignores mappings the user has set up.  If you use
plain old &lt;code&gt;normal dd&lt;/code&gt; and I&amp;#8217;ve remapped &lt;code&gt;dd&lt;/code&gt; to do something else, the call will use
my mapping and probably not do what your plugin expects.  Using &lt;code&gt;normal!&lt;/code&gt; ensures
that the call will do what you expect no matter what the user&amp;nbsp;has&amp;nbsp;mapped.&lt;/p&gt;
&lt;p&gt;This is a single instance of a more general theme. Vim is very customizable and users
will do lots of crazy things in their &lt;code&gt;.vimrc&lt;/code&gt; files.  If a key can be mapped or
a setting changed, you &lt;em&gt;have&lt;/em&gt; to assume that some user of your plugin will have
mapped or&amp;nbsp;changed&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="mapping-keys-the-right-way"&gt;Mapping Keys the&amp;nbsp;Right&amp;nbsp;Way&lt;/h2&gt;
&lt;p&gt;Most plugins add key mappings to make them easier to use.  Unfortunately this can be
tricky to get right.  You can never tell what keys your users have already mapped
themselves, and shadowing someone&amp;#8217;s favorite key mapping will break their muscle
memory and annoy them to&amp;nbsp;no&amp;nbsp;end.&lt;/p&gt;
&lt;h3 id="when-to-map-keys"&gt;When to&amp;nbsp;Map&amp;nbsp;Keys&lt;/h3&gt;
&lt;p&gt;The first question to ask is whether your plugin needs to map keys itself&amp;nbsp;at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;My &lt;a href="http://sjl.bitbucket.org/gundo.vim/"&gt;Gundo&lt;/a&gt; plugin has only one feature that needs to be mapped to a key in order to
make it useful: the &amp;#8220;toggle&amp;nbsp;Gundo&amp;#8221;&amp;nbsp;action.&lt;/p&gt;
&lt;p&gt;Gundo doesn&amp;#8217;t map this key itself, because no matter what &amp;#8220;default&amp;#8221; mapping I pick
someone will have already mapped it.  Instead I added a section right in the &lt;span class="caps"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;
file that shows how a user can map the&amp;nbsp;key&amp;nbsp;themselves:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;F5&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; :GundoToggle&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;By making users add this line to their &lt;code&gt;.vimrc&lt;/code&gt; themselves it shows them which key is
used to toggle Gundo (which they would have to know anyway) and also makes it obvious
how to change it to suit&amp;nbsp;their&amp;nbsp;taste.&lt;/p&gt;
&lt;h3 id="imap-and-nmap-are-pure-evil"&gt;imap and nmap are&amp;nbsp;Pure&amp;nbsp;Evil&lt;/h3&gt;
&lt;p&gt;Sometimes forcing the user to map their own keys won&amp;#8217;t work.  Perhaps your plugin has
many mappings that would be tedious for a user to set up manually (like my
&lt;a href="http://sjl.bitbucket.org/threesome.vim"&gt;Threesome&lt;/a&gt; plugin), or its mappings are mnemonic and wouldn&amp;#8217;t really make sense if
mapped to&amp;nbsp;other&amp;nbsp;keys.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ll talk more about how to deal with this in a moment, but the most important thing
to remember when mapping your own keys is that you must always, &lt;em&gt;always&lt;/em&gt;,
&lt;strong&gt;&lt;em&gt;always&lt;/em&gt;&lt;/strong&gt; use the &lt;code&gt;noremap&lt;/code&gt; forms of the various &lt;code&gt;map&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;If you map a key with &lt;code&gt;nmap&lt;/code&gt; and the user has remapped a key that your mapping uses,
your mapped key will almost certainly not do what you want.  Using &lt;code&gt;nnoremap&lt;/code&gt; will
ignore user mappings and do what&amp;nbsp;you&amp;nbsp;expect.&lt;/p&gt;
&lt;p&gt;This is the same principle as &lt;code&gt;normal&lt;/code&gt; and &lt;code&gt;normal!&lt;/code&gt;: &lt;em&gt;never&lt;/em&gt; trust your&amp;nbsp;users&amp;#8217;&amp;nbsp;configurations.&lt;/p&gt;
&lt;h3 id="let-me-configure-mappings"&gt;Let Me&amp;nbsp;Configure&amp;nbsp;Mappings&lt;/h3&gt;
&lt;p&gt;If you feel that your plugin must map some keys, please make those mappings
configurable in&amp;nbsp;some&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;There are a number of ways to do this.  The easiest way is to provide a configuration
option that disables all mappings.  The user can them remap the keys as they see fit.&amp;nbsp;For&amp;nbsp;example:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;exists&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;g:yourplugin_map_keys&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; g:yourplugin_map_keys &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; g:yourplugin_map_keys
    nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;d&lt;/span&gt; :&lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;sid&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;YourPluginDelete&lt;span class="p"&gt;()&amp;lt;&lt;/span&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Normal users will get the mappings automatically set up for them, and power users can
remap the keys to whatever they wish to avoid shadowing their&amp;nbsp;own&amp;nbsp;mappings.&lt;/p&gt;
&lt;p&gt;If your plugin&amp;#8217;s mappings all start with a common prefix (like &lt;code&gt;&amp;lt;leader&amp;gt;&lt;/code&gt; or
&lt;code&gt;&amp;lt;localleader&amp;gt;&lt;/code&gt;) you have another option: allow users to configure this prefix.  This
is the approach I&amp;#8217;ve used in &lt;a href="http://sjl.bitbucket.org/threesome.vim"&gt;Threesome&lt;/a&gt;.  It works&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;exists&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;g:yourplugin_map_prefix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; g:yourplugin_map_prefix &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;leader&amp;gt;&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

execute &lt;span class="c"&gt;&amp;quot;nnoremap&amp;quot;  g:yourplugin_map_prefix.&amp;quot;d&amp;quot;  &amp;quot;:call &amp;lt;sid&amp;gt;YourPluginDelete()&amp;lt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&amp;gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;execute&lt;/code&gt; command lets you build the mapping string dynamically so your users can
change the&amp;nbsp;mapping&amp;nbsp;prefix.&lt;/p&gt;
&lt;p&gt;There is a third option for solving this problem: the &lt;code&gt;hasmapto()&lt;/code&gt; Vim function.
Some plugins will use this to map a command to a key &lt;em&gt;unless&lt;/em&gt; the user has already
mapped that command to something else.  I don&amp;#8217;t personally like this option because
it feels less clear to me, but I know other people feel differently so I wanted to&amp;nbsp;mention&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="localize-mappings-and-settings"&gt;Localize Mappings&amp;nbsp;and&amp;nbsp;Settings&lt;/h2&gt;
&lt;p&gt;The next step in being a good Vim plugin author is to try to minimize the effects of
your key mappings and setting changes.  Some plugins will need to have global
effects but others&amp;nbsp;will&amp;nbsp;not.&lt;/p&gt;
&lt;p&gt;For example: if you&amp;#8217;re writing a plugin for working with Python files it should only
take effect for Python buffers, not&amp;nbsp;all&amp;nbsp;buffers.&lt;/p&gt;
&lt;h3 id="localizing-mappings"&gt;Localizing&amp;nbsp;Mappings&lt;/h3&gt;
&lt;p&gt;Key binding are easy to localize to single buffers.  All of the &lt;code&gt;noremap&lt;/code&gt; commands
can take an extra &lt;code&gt;&amp;lt;buffer&amp;gt;&lt;/code&gt; argument that will localize the mapping to the&amp;nbsp;current&amp;nbsp;buffer.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;&amp;quot; Remaps &amp;lt;leader&amp;gt;z globally&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;z :YourPluginFoo&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;quot; Remaps &amp;lt;leader&amp;gt;z only in the current buffer&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;buffer&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;z :YourPluginFoo&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;However, the problem is that you need to run this command in every buffer you want
the mapping active.  To do this your plugin can use an &lt;code&gt;autocommand&lt;/code&gt;.  Here&amp;#8217;s a full
example, using this concept plus the previously mentioned&amp;nbsp;configuration&amp;nbsp;options:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;g:yourplugin_map_keys&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;g:yourplugin_map_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;endif&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;g:yourplugin_map_prefix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;g:yourplugin_map_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&amp;lt;leader&amp;gt;&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;endif&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;g:yourplugin_map_keys&lt;/span&gt;
    &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;autocommand FileType python&amp;quot;&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;nnoremap &amp;lt;buffer&amp;gt;&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;g:yourplugin_map_prefix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;  &lt;span class="s"&gt;&amp;quot;:call &amp;lt;sid&amp;gt;YourPluginDelete()&amp;lt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;endif&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now your plugin will define a key mapping only for Python buffers, and your users can
disable or customize this mapping as they&amp;nbsp;see&amp;nbsp;fit.&lt;/p&gt;
&lt;p&gt;This mapping command is quite ugly.  Unfortunately that&amp;#8217;s the price of using
Vimscript and trying to make a plugin that will work for many users.  Later I&amp;#8217;ll talk
about one possible solution to&amp;nbsp;this&amp;nbsp;ugliness.&lt;/p&gt;
&lt;h3 id="localizing-settings"&gt;Localizing&amp;nbsp;Settings&lt;/h3&gt;
&lt;p&gt;Just as you should make mappings local to buffers when appropriate, you should do the
same with settings like &lt;code&gt;foldmethod&lt;/code&gt;, &lt;code&gt;foldmarker&lt;/code&gt; and &lt;code&gt;shiftwidth&lt;/code&gt;.  Not all
settings can be set locally in a buffer.  You can read &lt;code&gt;:help &amp;lt;settingname&amp;gt;&lt;/code&gt; to see
if&amp;nbsp;it&amp;#8217;s&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;You can use &lt;code&gt;setlocal&lt;/code&gt; instead of &lt;code&gt;set&lt;/code&gt; to localize settings to individual buffers.
Like with mappings you&amp;#8217;ll need to use an autocommand to run the &lt;code&gt;setlocal&lt;/code&gt; command
every time the users opens a&amp;nbsp;new&amp;nbsp;buffer.&lt;/p&gt;
&lt;h2 id="autoload-is-your-friend"&gt;Autoload is&amp;nbsp;Your&amp;nbsp;Friend&lt;/h2&gt;
&lt;p&gt;If your plugin is something that users will be using all the time you can skip&amp;nbsp;this&amp;nbsp;section.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re writing something that will only be used in specific cases, you can help
your users by using Vim&amp;#8217;s &lt;code&gt;autoload&lt;/code&gt; functionality to delay loading its code until
the user actually tries to&amp;nbsp;use&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The way &lt;code&gt;autoload&lt;/code&gt; works is fairly simple.  Normally you would bind a key to call one of your
plugin&amp;#8217;s functions with something&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;z :&lt;span class="k"&gt;call&lt;/span&gt; YourPluginFunction&lt;span class="p"&gt;()&amp;lt;&lt;/span&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can use autoloading by prepending &lt;code&gt;yourplugin#&lt;/code&gt; to the name of&amp;nbsp;the&amp;nbsp;function:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;z :&lt;span class="k"&gt;call&lt;/span&gt; yourplugin#YourPluginFunction&lt;span class="p"&gt;()&amp;lt;&lt;/span&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When this mapping is run, Vim will do&amp;nbsp;the&amp;nbsp;following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check to see if &lt;code&gt;YourPluginFunction&lt;/code&gt; is already defined.  If so,&amp;nbsp;call&amp;nbsp;it.&lt;/li&gt;
&lt;li&gt;Otherwise, look in &lt;code&gt;~/.vim/autoload/&lt;/code&gt; for a file named &lt;code&gt;yourplugin.vim&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it exists, parse and load the file (which presumably defines
  &lt;code&gt;YourPluginFunction&lt;/code&gt; somewhere inside&amp;nbsp;of&amp;nbsp;it).&lt;/li&gt;
&lt;li&gt;Call&amp;nbsp;the&amp;nbsp;function.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means that instead of putting all of your plugin&amp;#8217;s code in
&lt;code&gt;plugin/yourplugin.vim&lt;/code&gt; you can put just the key mapping code there and pull the rest
out into &lt;code&gt;autoload/yourplugin.vim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If your plugin has a decent amount of code this can reduce the startup time of Vim by
a&amp;nbsp;significant&amp;nbsp;amount.&lt;/p&gt;
&lt;p&gt;Check out the full documentation of &lt;code&gt;autoload&lt;/code&gt; by running &lt;code&gt;:help autoload&lt;/code&gt; to learn&amp;nbsp;much&amp;nbsp;more.&lt;/p&gt;
&lt;h2 id="backwards-compatibility-is-a-big-deal"&gt;Backwards Compatibility is a&amp;nbsp;Big&amp;nbsp;Deal&lt;/h2&gt;
&lt;p&gt;Once you&amp;#8217;ve written your Vim plugin and released it into the wild, you have to
maintain it.  Users will find bugs and ask for&amp;nbsp;new&amp;nbsp;features.&lt;/p&gt;
&lt;p&gt;Part of being a responsible developer of any kind, including a Vim plugin author, is
maintaining backwards compatibility, &lt;em&gt;especially&lt;/em&gt; for tools that users will use every
day and burn into their muscle memory.  Users rely on tools to work, and tools that
break backwards compatibility will quickly lose&amp;nbsp;users&amp;#8217;&amp;nbsp;trust.&lt;/p&gt;
&lt;p&gt;Maintaining backwards compatibility will cause your plugin&amp;#8217;s code to get crufty in
spots, but it&amp;#8217;s the price of maintaining your&amp;nbsp;users&amp;#8217;&amp;nbsp;happiness.&lt;/p&gt;
&lt;h3 id="what-matters-for-backards-compatibility"&gt;What Matters for&amp;nbsp;Backards&amp;nbsp;Compatibility?&lt;/h3&gt;
&lt;p&gt;For a Vim plugin the most important part of staying backwards compatible is ensuring
that key mappings, customized or not, continue to do what&amp;nbsp;users&amp;nbsp;expect.&lt;/p&gt;
&lt;p&gt;If your plugin maps key &lt;code&gt;X&lt;/code&gt; to do &lt;code&gt;Y&lt;/code&gt;, then pressing &lt;code&gt;X&lt;/code&gt; should &lt;em&gt;always&lt;/em&gt; do &lt;code&gt;Y&lt;/code&gt;, even
if you change how &lt;code&gt;Y&lt;/code&gt; is called by renaming &lt;code&gt;Y&lt;/code&gt; to &lt;code&gt;Z&lt;/code&gt;.  This may mean changing &lt;code&gt;Y&lt;/code&gt;
into a wrapper function which simply calls &lt;code&gt;Z&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are many other aspects of backwards compatibility that you will have to
consider, depending on the purpose of your plugin.  The rule of thumb you should
follow is: if a user uses this plugin on a daily basis and has its usage burned into
their muscle memory, updating the plugin should not make them&amp;nbsp;relearn&amp;nbsp;anything.&lt;/p&gt;
&lt;h3 id="use-semantic-versioning-so-i-can-stay-sane"&gt;Use Semantic Versioning So I Can&amp;nbsp;Stay&amp;nbsp;Sane&lt;/h3&gt;
&lt;p&gt;A fast, simple, easy way to document your plugin&amp;#8217;s state is to use &lt;a href="http://semver.org/"&gt;semantic
versioning&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Semantic versioning is simply the idea that instead of picking arbitrary version
numbers for releases of your project, you use version numbers that describe the
backwards-compatible state in a&amp;nbsp;meaningful&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;In a nutshell, these rules describe how you should select version numbers for&amp;nbsp;new&amp;nbsp;releases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Version numbers have three components: &lt;code&gt;major.minor.bugfix&lt;/code&gt;.  For example: &lt;code&gt;1.2.4&lt;/code&gt;
  or &lt;code&gt;2.13.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Versions with a major version of 0 (e.g. &lt;code&gt;0.2.3&lt;/code&gt;) make no guarantees about
  backwards compatibility.  You are free to break anything you want.  It&amp;#8217;s only after
  you release &lt;code&gt;1.0.0&lt;/code&gt; that you begin&amp;nbsp;making&amp;nbsp;promises.&lt;/li&gt;
&lt;li&gt;If a release introduces backwards-incompatible changes, increment the major&amp;nbsp;version&amp;nbsp;number.&lt;/li&gt;
&lt;li&gt;If a release is backwards-compatible, but adds &lt;em&gt;new&lt;/em&gt; features, increment the minor&amp;nbsp;version&amp;nbsp;number.&lt;/li&gt;
&lt;li&gt;If a release simply fixes bugs, refactors code, or improves performance, increment
  the bugfix&amp;nbsp;version&amp;nbsp;number.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This simple scheme makes it easy for users to tell (in a broad sense) what has
changed when they update&amp;nbsp;your&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;If only the bugfix number has changed they can update without fear and continue on
without worrying about changes unless&amp;nbsp;they&amp;#8217;re&amp;nbsp;curious.&lt;/p&gt;
&lt;p&gt;If the minor version number has changed they might want to look at the changelog to
see what new features they may want to take advantage of, but if they&amp;#8217;re busy they
can simply update and&amp;nbsp;move&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;If the major version number has changed it&amp;#8217;s a major red flag, and they&amp;#8217;ll want to
read the changelog carefully to see what&amp;nbsp;is&amp;nbsp;different.&lt;/p&gt;
&lt;p&gt;Some people don&amp;#8217;t like semantic versioning for the&amp;nbsp;following&amp;nbsp;reason:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If I have to increment the major version number every time I make
backwards-incompatible changes, I&amp;#8217;ll quickly be at ugly versions&amp;nbsp;like&amp;nbsp;24.1.2!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To this I say: &amp;#8220;Yes, but if that happens you&amp;#8217;re doing things wrong in the&amp;nbsp;first&amp;nbsp;place.&amp;#8221;&lt;/p&gt;
&lt;p&gt;Keep your project in &amp;#8220;beta&amp;#8221; (i.e. version &lt;code&gt;0.*.*&lt;/code&gt;) for as long as you need to
experiment freely.  &lt;em&gt;Take your time&lt;/em&gt; and make sure you&amp;#8217;ve gotten things (mostly)
right.  Once you release &lt;code&gt;1.0.0&lt;/code&gt; it&amp;#8217;s time to start being responsible and caring
about&amp;nbsp;backwards&amp;nbsp;compatibility.&lt;/p&gt;
&lt;p&gt;Breaking functionality all the time harms your users by reducing their productivity
and frustrating them.  Yes, it means adding some cruft to your code over time, but
it&amp;#8217;s the price of not&amp;nbsp;being&amp;nbsp;evil.&lt;/p&gt;
&lt;h2 id="document-everything"&gt;Document&amp;nbsp;Everything&lt;/h2&gt;
&lt;p&gt;A critical part of releasing a Vim plugin to the world is writing documentation for
it.  Vim has fantastic documentation itself, so your plugins should follow in its
footsteps and provide&amp;nbsp;thorough&amp;nbsp;docs.&lt;/p&gt;
&lt;h3 id="pick-some-requirements-and-stick-to-them"&gt;Pick Some Requirements and Stick&amp;nbsp;to&amp;nbsp;Them&lt;/h3&gt;
&lt;p&gt;The most important part of your documentation is telling users what they need to have
in order to use your plugin.  Vim runs on nearly every system imaginable and can be
compiled in many different ways, so being specific about your plugin&amp;#8217;s requirements
will save users a lot of trial&amp;nbsp;and&amp;nbsp;error.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does your plugin only work with Vim version X.Y&amp;nbsp;or&amp;nbsp;later?&lt;/li&gt;
&lt;li&gt;Does it require Python/Ruby/etc support compiled in?&amp;nbsp;Which&amp;nbsp;version?&lt;/li&gt;
&lt;li&gt;Does it not work&amp;nbsp;on&amp;nbsp;Windows?&lt;/li&gt;
&lt;li&gt;Does it rely on an&amp;nbsp;external&amp;nbsp;tool?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the answer to any of those questions is &amp;#8220;yes&amp;#8221;, you &lt;em&gt;must&lt;/em&gt; mention it in&amp;nbsp;the&amp;nbsp;documentation.&lt;/p&gt;
&lt;h3 id="write-a-readme"&gt;Write a&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;The first step to documenting your plugin is to write a &lt;span class="caps"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt; file for the
repository.  You can also use the text of this file as the description if you upload
your plugin to the &lt;a href="http://www.vim.org/"&gt;vim website&lt;/a&gt;, or the content of your plugin&amp;#8217;s website if you
create one&amp;nbsp;for&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Some examples of things to include in your &lt;span class="caps"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An overview of what the&amp;nbsp;plugin&amp;nbsp;does.&lt;/li&gt;
&lt;li&gt;Screenshots,&amp;nbsp;if&amp;nbsp;possible.&lt;/li&gt;
&lt;li&gt;Requirements.&lt;/li&gt;
&lt;li&gt;Installation&amp;nbsp;instructions.&lt;/li&gt;
&lt;li&gt;Common configuration options that many users will want&amp;nbsp;to&amp;nbsp;know.&lt;/li&gt;
&lt;li&gt;Links to:&lt;ul&gt;
&lt;li&gt;A canonical web address to find&amp;nbsp;the&amp;nbsp;plugin.&lt;/li&gt;
&lt;li&gt;The bug tracker for&amp;nbsp;the&amp;nbsp;plugin.&lt;/li&gt;
&lt;li&gt;The source code or repository of&amp;nbsp;the&amp;nbsp;plugin.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="create-a-simple-website"&gt;Create a&amp;nbsp;Simple&amp;nbsp;Website&lt;/h3&gt;
&lt;p&gt;This isn&amp;#8217;t strictly necessary, but having a simple website for your plugin is an
extra touch that makes it seem&amp;nbsp;more&amp;nbsp;polished.&lt;/p&gt;
&lt;p&gt;It also gives you a canonical &lt;span class="caps"&gt;&lt;span class="caps"&gt;URL&lt;/span&gt;&lt;/span&gt; that people can visit to get the latest information
about&amp;nbsp;your&amp;nbsp;plugin.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve made simple sites for both of my plugins: &lt;a href="http://sjl.bitbucket.org/gundo.vim/"&gt;Gundo&lt;/a&gt; and &lt;a href="http://sjl.bitbucket.org/threesome.vim"&gt;Threesome&lt;/a&gt;.  Feel
free to use them as an example or even take their code and use it for your own plugin
sites if&amp;nbsp;you&amp;nbsp;like.&lt;/p&gt;
&lt;h3 id="write-a-vim-help-document"&gt;Write a Vim&amp;nbsp;Help&amp;nbsp;Document&lt;/h3&gt;
&lt;p&gt;The bulk of your plugin&amp;#8217;s documentation should be in the form of a Vim help document.
Users are used to using Vim&amp;#8217;s &lt;code&gt;:help&lt;/code&gt; and they&amp;#8217;ll expect to be able to use it to
learn about&amp;nbsp;your&amp;nbsp;plugin.&lt;/p&gt;
&lt;p&gt;Creating a help document is as easy as creating a &lt;code&gt;doc/yourplugin.txt&lt;/code&gt; file in your
project.  It will be indexed automatically by &lt;code&gt;pathogen#helptags()&lt;/code&gt; so your users
will have the docs at&amp;nbsp;their&amp;nbsp;fingertips.&lt;/p&gt;
&lt;p&gt;Two easy ways to learn the syntax of help files are by reading &lt;code&gt;:help help-writing&lt;/code&gt;
and using an existing plugin&amp;#8217;s help file as&amp;nbsp;an&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;Take your time and craft a beautiful help file you can be proud of.  Don&amp;#8217;t be afraid
to add a bit of personality to your docs to break the dryness.  The &lt;a href="https://github.com/scrooloose/syntastic/blob/master/doc/syntastic.txt"&gt;syntastic help
file&lt;/a&gt; is a great example (especially the &lt;code&gt;About&lt;/code&gt; section).&lt;/p&gt;
&lt;p&gt;Things to include in&amp;nbsp;your&amp;nbsp;documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A brief overview of&amp;nbsp;the&amp;nbsp;plugin.&lt;/li&gt;
&lt;li&gt;A more in-depth description of how the plugin&amp;nbsp;is&amp;nbsp;used.&lt;/li&gt;
&lt;li&gt;Every single key mapping the&amp;nbsp;plugin&amp;nbsp;creates.&lt;/li&gt;
&lt;li&gt;Ways to extend the plugin,&amp;nbsp;if&amp;nbsp;applicable.&lt;/li&gt;
&lt;li&gt;All configuration variables (including their&amp;nbsp;default&amp;nbsp;values!).&lt;/li&gt;
&lt;li&gt;The&amp;nbsp;plugin&amp;#8217;s&amp;nbsp;changelog.&lt;/li&gt;
&lt;li&gt;The&amp;nbsp;plugin&amp;#8217;s&amp;nbsp;license.&lt;/li&gt;
&lt;li&gt;Links to the plugin&amp;#8217;s repository and&amp;nbsp;bug&amp;nbsp;tracker.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a nutshell: your help file should contain &lt;em&gt;anything&lt;/em&gt; a user would ever need to know
about&amp;nbsp;your&amp;nbsp;plugin.&lt;/p&gt;
&lt;h3 id="keep-a-changelog"&gt;Keep&amp;nbsp;a&amp;nbsp;Changelog&lt;/h3&gt;
&lt;p&gt;The last part of documenting your project is keeping a changelog.  You can skip this
while your project is still in &amp;#8220;beta&amp;#8221; (i.e. less than version &lt;code&gt;1.0.0&lt;/code&gt;) but once you
officially release a real version you need to keep your users informed about what has
changed&amp;nbsp;between&amp;nbsp;releases.&lt;/p&gt;
&lt;p&gt;I like to include this log in the &lt;span class="caps"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;, the plugin&amp;#8217;s website, and the
documentation to make it as easy as possible for users to see&amp;nbsp;what&amp;#8217;s&amp;nbsp;changed.&lt;/p&gt;
&lt;p&gt;Try to keep the language of the changelog at a high enough level for your users to
understand without knowing anything about the implementation of your plugin.  Things
like &amp;#8220;added feature X&amp;#8221; and &amp;#8220;fixed bug Y&amp;#8221; are great, while things like &amp;#8220;refactored the
inner workings of utility function Z&amp;#8221; are best left in&amp;nbsp;commit&amp;nbsp;messages.&lt;/p&gt;
&lt;h2 id="making-vimscript-palatable"&gt;Making&amp;nbsp;Vimscript&amp;nbsp;Palatable&lt;/h2&gt;
&lt;p&gt;The worst part about writing Vim plugins is, without a doubt, dealing with Vimscript.
It&amp;#8217;s an esoteric language that&amp;#8217;s grown organically over the years seemingly without
any strong&amp;nbsp;design&amp;nbsp;direction.&lt;/p&gt;
&lt;p&gt;Features are added to Vim, then Vimscript features are added to control those
features, then hacky workarounds are added&amp;nbsp;for&amp;nbsp;flexibility.&lt;/p&gt;
&lt;p&gt;The syntax is terse, ugly and inconsistent.  Is &lt;code&gt;" foo&lt;/code&gt; a&amp;nbsp;comment?&amp;nbsp;Sometimes.&lt;/p&gt;
&lt;p&gt;Much of the time you&amp;#8217;ll spend writing your first plugin will be learning how to do
things in Vimscript.  The help documentation on all of its features is thorough, but
it can be hard to find what you&amp;#8217;re looking for if you don&amp;#8217;t know the exact name.
Looking through other plugins is often very helpful in pointing you toward what&amp;nbsp;you&amp;nbsp;need.&lt;/p&gt;
&lt;p&gt;There are a couple of ways to ease the pain of Vimscript, and I&amp;#8217;ll briefly talk about
two of&amp;nbsp;them&amp;nbsp;here.&lt;/p&gt;
&lt;h3 id="wrap-everything"&gt;Wrap.&amp;nbsp;Everything.&lt;/h3&gt;
&lt;p&gt;The first piece of advice I have is this: if you want to make your plugins readable
and maintainable then you need to wrap up functionality even more than you would in&amp;nbsp;other&amp;nbsp;languages.&lt;/p&gt;
&lt;p&gt;For example, my &lt;a href="http://sjl.bitbucket.org/gundo.vim/"&gt;Gundo&lt;/a&gt; plugin has a few utility functions that look&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; s:GundoGoToWindowForBufferName&lt;span class="p"&gt;(&lt;/span&gt;name&lt;span class="p"&gt;)&lt;/span&gt;&amp;quot;{{{
    &lt;span class="k"&gt;if&lt;/span&gt; bufwinnr&lt;span class="p"&gt;(&lt;/span&gt;bufnr&lt;span class="p"&gt;(&lt;/span&gt;a:name&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;
        exe bufwinnr&lt;span class="p"&gt;(&lt;/span&gt;bufnr&lt;span class="p"&gt;(&lt;/span&gt;a:name&lt;span class="p"&gt;))&lt;/span&gt; . &lt;span class="c"&gt;&amp;quot;wincmd w&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;endif&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;&amp;quot;}}}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This function will go to the window for the given buffer name and gracefully handle
the case where the buffer/window does not exist.  It&amp;#8217;s verbose but much more readable
than the alternative of using that &lt;code&gt;if&lt;/code&gt; statement in every place I need to&amp;nbsp;switch&amp;nbsp;windows.&lt;/p&gt;
&lt;p&gt;As you write your plugin you&amp;#8217;ll &amp;#8220;grow&amp;#8221; a number of these utility functions.  Any time
you duplicate code you should think about creating one, but you should also do so any
time you write a particularly hairy line of Vimscript.  Pulling complex lines out
into named functions will save you a lot of reviewing and rethinking down&amp;nbsp;the&amp;nbsp;line.&lt;/p&gt;
&lt;h3 id="scripting-vim-with-other-languages"&gt;Scripting Vim with&amp;nbsp;Other&amp;nbsp;Languages&lt;/h3&gt;
&lt;p&gt;Another option for making Vimscript less painful is to simply not use it much at all.
Vim includes support for creating plugins in a number of other languages like Python
and Ruby.  Many plugin authors choose to move nearly all of their code into another
language, using a small Vimscript &amp;#8220;wrapper&amp;#8221; to expose it to&amp;nbsp;the&amp;nbsp;user.&lt;/p&gt;
&lt;p&gt;I decided to try this approach with &lt;a href="http://sjl.bitbucket.org/threesome.vim"&gt;Threesome&lt;/a&gt; after seeing it used in the
&lt;a href="https://github.com/jceb/vim-orgmode"&gt;vim-orgmode&lt;/a&gt; plugin to great effect.  Overall I consider it to be a good idea,
with a&amp;nbsp;few&amp;nbsp;caveats.&lt;/p&gt;
&lt;p&gt;First, using another language will requires your plugin&amp;#8217;s users to use a version of
Vim compiled with support for that version.  In this day and age it&amp;#8217;s usually not
a problem, but if you want your plugin to run everywhere then it&amp;#8217;s not&amp;nbsp;an&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;Using another language adds overhead.  You need to not only learn Vimscript but also
the interface between Vim and the language.  For small plugins this can add more
complexity to the project than it saves, but for larger plugins it can pay for
itself.  It&amp;#8217;s up to you to decide whether it&amp;#8217;s&amp;nbsp;worth&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Finally, using another language does not entirely insulate you from the
eccentricities of Vimscript.  You still need to learn how to do most things in
Vimscript &amp;#8212; using another language simply lets you wrap most of this up more neatly
than you&amp;nbsp;otherwise&amp;nbsp;could.&lt;/p&gt;
&lt;h3 id="unit-testing-will-make-you-drink"&gt;Unit Testing Will Make&amp;nbsp;You&amp;nbsp;Drink&lt;/h3&gt;
&lt;p&gt;Unit testing (and other types of testing) is becoming more and more popular today.
In particular the Python and Ruby communities seem to be getting more and more
excited about it as time&amp;nbsp;goes&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;Unfortunately, unit testing Vim plugins lies somewhere between &amp;#8220;painful&amp;#8221; and
&amp;#8220;&lt;a href="http://www.amazon.com/Garden-Weasel-90206/dp/B002ECYRH4"&gt;garden-weasel&lt;/a&gt;ing your face&amp;#8221; on the&amp;nbsp;difficulty&amp;nbsp;scale.&lt;/p&gt;
&lt;p&gt;I tried adding some unit tests to &lt;a href="http://sjl.bitbucket.org/gundo.vim/"&gt;Gundo&lt;/a&gt;, but even after looking at a number of
frameworks I was spending hours simply trying to get my tests&amp;nbsp;to&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;I didn&amp;#8217;t even bother trying to add tests to &lt;a href="http://sjl.bitbucket.org/threesome.vim"&gt;Threesome&lt;/a&gt; because for every hour
I would have spent fighting Vim to create tests I could have cleaned up the code and
fixed&amp;nbsp;bugs&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ll gladly change my opinion on the subject if someone writes a unit testing
framework for Vim that&amp;#8217;s as easy to use as &lt;a href="https://bitheap.org/cram/"&gt;Cram&lt;/a&gt;.  In fact, I&amp;#8217;ll even buy the
author a $100 bottle of scotch (or whatever&amp;nbsp;they&amp;nbsp;prefer).&lt;/p&gt;
&lt;p&gt;Until that happens I personally don&amp;#8217;t think it&amp;#8217;s worth your time to unit test Vim
plugins.  Spend your extra hours reading documentation, testing things manually with
a variety of settings, and thinking hard about your&amp;nbsp;code&amp;nbsp;instead.&lt;/p&gt;
&lt;h2 id="tldr"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;TL&lt;/span&gt;&lt;/span&gt;;&lt;span class="caps"&gt;&lt;span class="caps"&gt;DR&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Writing Vim plugins is tricky.  Vimscript is a rabbit hole of sadness and despair,
and trying to please all your users while maintaining backwards compatibility is
a&amp;nbsp;monumental&amp;nbsp;task.&lt;/p&gt;
&lt;p&gt;With that said, creating something that people use every day to help them make
beautiful software projects is extremely rewarding.  Even if your plugin doesn&amp;#8217;t get
many users, being able to use a tool &lt;em&gt;you wrote&lt;/em&gt; is&amp;nbsp;very&amp;nbsp;satisfying.&lt;/p&gt;
&lt;p&gt;So if you&amp;#8217;ve got an idea for a plugin that would make Vim better just sit down, learn
about Vimscript, create it, and release it so we can&amp;nbsp;all&amp;nbsp;benefit.&lt;/p&gt;
&lt;p&gt;If you have any questions or comments feel free to hit me up &lt;a href="http://twitter.com/stevelosh"&gt;on
Twitter&lt;/a&gt;.  You might also enjoy following &lt;a href="http://twitter.com/dotvimrc"&gt;@dotvimrc&lt;/a&gt; where I try to
tweet random, bite-sized lines you might like to put in your &lt;code&gt;.vimrc&lt;/code&gt; file.&lt;/p&gt;
                
            
                    
                &lt;img src="http://feeds.feedburner.com/~r/stevelosh/~4/5bQmL-SN0Zw" height="1" width="1"/&gt;</content><feedburner:origLink>http://stevelosh.com/blog/2011/09/writing-vim-plugins/</feedburner:origLink></entry><entry><title type="html">Django Advice</title><author><name>Steve Losh</name></author><link href="http://feedproxy.google.com/~r/stevelosh/~3/3yBaI0DYSWc/" /><updated>2011-06-30T08:30:00Z</updated><published>2011-06-30T08:30:00Z</published><id>http://stevelosh.com/blog/2011/06/django-advice/</id><content type="html">
                    
                        
                
                    &lt;p&gt;For the past year and a half or so I&amp;#8217;ve been working full-time at &lt;a href="http://dwaiter.com/"&gt;Dumbwaiter
Design&lt;/a&gt; doing &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; development. I&amp;#8217;ve picked up a bunch of useful tricks along
the way that help me work, and I figured I&amp;#8217;d&amp;nbsp;share&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m sure there are better ways to do some of the things that I mention.  If you know
of any feel free to hit me up on &lt;a href="http://twitter.com/stevelosh"&gt;Twitter&lt;/a&gt; and let&amp;nbsp;me&amp;nbsp;know.&lt;/p&gt;
&lt;p&gt;Also: this entry was written over several months, so if there are inconsistencies let
me know and I&amp;#8217;ll try to&amp;nbsp;fix&amp;nbsp;them.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#vagrant"&gt;Vagrant&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#why-vagrant"&gt;Why&amp;nbsp;Vagrant?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-fabric-to-stay-fast-and-automate-everything"&gt;Using Fabric to Stay Fast and&amp;nbsp;Automate&amp;nbsp;Everything&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#wrangling-databases-with-south"&gt;Wrangling Databases with South&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#useful-fabric-tasks"&gt;Useful&amp;nbsp;Fabric&amp;nbsp;Tasks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#watching-for-changes"&gt;Watching for Changes&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#using-the-werkzeug-debugger-with-gunicorn"&gt;Using the Werkzeug Debugger&amp;nbsp;with&amp;nbsp;Gunicorn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#pulling-uploads"&gt;Pulling&amp;nbsp;Uploads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#preventing-accidents"&gt;Preventing&amp;nbsp;Accidents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#working-with-third-party-apps"&gt;Working with Third-Party Apps&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#installing-apps-from-repositories"&gt;Installing Apps&amp;nbsp;from&amp;nbsp;Repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mirroring-repositories"&gt;Mirroring&amp;nbsp;Repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-bcvi-to-edit-files"&gt;Using &lt;span class="caps"&gt;&lt;span class="caps"&gt;BCVI&lt;/span&gt;&lt;/span&gt; to&amp;nbsp;Edit&amp;nbsp;Files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#improving-the-admin-interface"&gt;Improving the Admin Interface&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#enter-grappelli"&gt;Enter&amp;nbsp;Grappelli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#an-ugly-hack-to-show-usable-foreign-key-fields"&gt;An Ugly Hack to Show Usable Foreign&amp;nbsp;Key&amp;nbsp;Fields&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-django-annoying"&gt;Using Django-Annoying&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-render_to-decorator"&gt;The&amp;nbsp;render_to&amp;nbsp;Decorator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-ajax_request-decorator"&gt;The&amp;nbsp;ajax_request&amp;nbsp;Decorator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#templating-tricks"&gt;Templating Tricks&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#null-checks-and-fallbacks"&gt;Null Checks&amp;nbsp;and&amp;nbsp;Fallbacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#manipulating-query-strings"&gt;Manipulating&amp;nbsp;Query&amp;nbsp;Strings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#satisfying-your-designer-with-typogrify"&gt;Satisfying Your Designer&amp;nbsp;with&amp;nbsp;Typogrify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-flat-page-trainwreck"&gt;The Flat&amp;nbsp;Page&amp;nbsp;Trainwreck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#editing-with-vim"&gt;Editing with Vim&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#vim-for-django"&gt;Vim&amp;nbsp;for&amp;nbsp;Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#filetype-mappings"&gt;Filetype&amp;nbsp;Mappings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#python-sanity-checking"&gt;Python&amp;nbsp;Sanity&amp;nbsp;Checking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#javascript-sanity-checking-and-folding"&gt;Javascript Sanity Checking&amp;nbsp;and&amp;nbsp;Folding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#django-autocommands"&gt;Django&amp;nbsp;Autocommands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="vagrant"&gt;Vagrant&lt;/h2&gt;
&lt;p&gt;I used to develop Django sites by running them on my &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt; X laptop locally and
deploying to a Linode &lt;span class="caps"&gt;&lt;span class="caps"&gt;VPS&lt;/span&gt;&lt;/span&gt;.  I had a whole section of this post written up about
tricks and tips for working with&amp;nbsp;that&amp;nbsp;setup.&lt;/p&gt;
&lt;p&gt;Then I found &lt;a href="http://vagrantup.com/"&gt;Vagrant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I just deleted the entire section of this post&amp;nbsp;I&amp;nbsp;wrote.&lt;/p&gt;
&lt;p&gt;Vagrant gives you a better way of working.  You need to&amp;nbsp;use&amp;nbsp;it.&lt;/p&gt;
&lt;h3 id="why-vagrant"&gt;Why&amp;nbsp;Vagrant?&lt;/h3&gt;
&lt;p&gt;If you haven&amp;#8217;t used it before, Vagrant is basically a tool for managing
&lt;a href="http://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; VMs.  It makes it easy to start, pause, and resume VMs.  Instead of
installing Django in a virtualenv and developing against that, you run a &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt; which
runs your site and develop&amp;nbsp;against&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;This may not sound like much, but it&amp;#8217;s kind of a big deal.  The critical difference
is that you can now develop against the same setup that you&amp;#8217;ll be using&amp;nbsp;in&amp;nbsp;production.&lt;/p&gt;
&lt;p&gt;This cuts out a huge amount of pain that stems from &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt; differences.  Here are a few
examples off the top of&amp;nbsp;my&amp;nbsp;head:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URLField and MacPorts Python 2.5 on &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt; X.  There&amp;#8217;s a &lt;a href="https://trac.macports.org/ticket/24421"&gt;bug&lt;/a&gt; where using
  verify_exists will crash your site every time you save a model, unless you set
  a particular environment variable with no debug information.  Yeah, I spent
  a couple of hours tracking that one down at&amp;nbsp;work.&amp;nbsp;Awesome.&lt;/li&gt;
&lt;li&gt;Installing &lt;span class="caps"&gt;&lt;span class="caps"&gt;PIL&lt;/span&gt;&lt;/span&gt; on &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt; X is no picnic.  &lt;a href="http://mxcl.github.com/homebrew/"&gt;homebrew&lt;/a&gt; makes things better, if you use it,
  so this one isn&amp;#8217;t a&amp;nbsp;huge&amp;nbsp;deal.&lt;/li&gt;
&lt;li&gt;Every time you update Python in-place on your local machines, &lt;span class="caps"&gt;&lt;span class="caps"&gt;ALL&lt;/span&gt;&lt;/span&gt; of your
  virtualenvs break because the Python binaries inside are linked against global
  Python library files.  Have fun recreating them.  I hope you froze your
  &lt;code&gt;requirements.txt&lt;/code&gt; files before&amp;nbsp;you&amp;nbsp;updated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using Vagrant and VMs means you can just worry about &lt;span class="caps"&gt;&lt;span class="caps"&gt;ONE&lt;/span&gt;&lt;/span&gt; operating system and its
quirks.  It saves you a ton&amp;nbsp;of&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;Aside from that, there&amp;#8217;s another benefit to using Vagrant: it strongly encourages you
to learn and use an automated provisioning system.  Support for Puppet and Chef is
built in.  I chose Puppet, but if you prefer Chef that&amp;#8217;s&amp;nbsp;cool&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;You can also use other tools like Fabric or some simple scripts, but I&amp;#8217;d strongly
recommend giving Puppet or Chef a fair shot.  It&amp;#8217;s a lot to learn, but they&amp;#8217;re both
widely tested and&amp;nbsp;very&amp;nbsp;powerful.&lt;/p&gt;
&lt;p&gt;Because you&amp;#8217;re developing against a &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt; and deploying to a &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt;, you can reuse 90% of
the provisioning code across&amp;nbsp;the&amp;nbsp;two.&lt;/p&gt;
&lt;p&gt;When I make a new site, I do the following to initialize a new Vagrant&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;vagrant up&lt;/code&gt; (which runs Puppet to initialize the&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fab dev bootstrap&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When I&amp;#8217;m ready to go live, I do&amp;nbsp;the&amp;nbsp;following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Buy a Linode&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;VPS&lt;/span&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Run Puppet to initialize the&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;VPS&lt;/span&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Enter the Linode info in&amp;nbsp;my&amp;nbsp;fabfile.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fab prod bootstrap&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No more screwing around with different paths, different versions of Nginx, different
versions of Python.  When I&amp;#8217;m developing something I can be pretty confident it will
&amp;#8220;just work&amp;#8221; in production without any&amp;nbsp;major&amp;nbsp;surprises.&lt;/p&gt;
&lt;h3 id="using-fabric-to-stay-fast-and-automate-everything"&gt;Using Fabric to Stay Fast and&amp;nbsp;Automate&amp;nbsp;Everything&lt;/h3&gt;
&lt;p&gt;One of the problems with this setup is that I can&amp;#8217;t just run &lt;code&gt;python manage.py
whatever&lt;/code&gt; any more because I need it to run on the&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;To get around this I&amp;#8217;ve created many simple &lt;a href="http://fabfile.org/"&gt;Fabric&lt;/a&gt; tasks to automate the common
things I need to do.  Fabric is an awesome little Python utility for scripting tasks
(like deployments).  We use it constantly at Dumbwaiter.  Here are a few examples
from&amp;nbsp;our&amp;nbsp;fabfiles.&lt;/p&gt;
&lt;p&gt;This first set is for running abitrary&amp;nbsp;commands&amp;nbsp;easily.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cmd&lt;/code&gt; and &lt;code&gt;vcmd&lt;/code&gt; will &lt;code&gt;cd&lt;/code&gt; into the site directory on the &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt; and run a command of my
choosing.  &lt;code&gt;vcmd&lt;/code&gt; will prefix the command with the path to the virtualenv&amp;#8217;s &lt;code&gt;bin&lt;/code&gt;
directory, so I can do something like &lt;code&gt;fab dev vcmd&lt;/code&gt;, &lt;code&gt;pip install markdown&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;sdo&lt;/code&gt; commands do the same thing, but &lt;code&gt;sudo&lt;/code&gt;&amp;#8216;ed.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run a command in the site directory.  Usable from other commands or the &lt;span class="caps"&gt;&lt;span class="caps"&gt;CLI&lt;/span&gt;&lt;/span&gt;.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cyan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Command to run: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sdo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Sudo a command in the site directory.  Usable from other commands or the &lt;span class="caps"&gt;&lt;span class="caps"&gt;CLI&lt;/span&gt;&lt;/span&gt;.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cyan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Command to run: sudo &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vcmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run a virtualenv-based command in the site directory.  Usable from other commands or the &lt;span class="caps"&gt;&lt;span class="caps"&gt;CLI&lt;/span&gt;&lt;/span&gt;.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cyan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Command to run: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;/bin/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;/bin/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vsdo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Sudo a virtualenv-based command in the site directory.  Usable from other commands or the &lt;span class="caps"&gt;&lt;span class="caps"&gt;CLI&lt;/span&gt;&lt;/span&gt;.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cyan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Command to run: sudo &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;/bin/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;raw_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;/bin/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This next set is just some common commands that I need to&amp;nbsp;run&amp;nbsp;often.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;syncdb&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run syncdb.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py syncdb --noinput&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;collectstatic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Collect static media.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py collectstatic --noinput&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rebuild_index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Rebuild the search index.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;process_owner&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py rebuild_index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;chown -R &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; .xapian&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Update the search index.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;process_owner&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py update_index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;chown -R &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; .xapian&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We also use Fabric to automate some of the more complex things we need&amp;nbsp;to&amp;nbsp;do.&lt;/p&gt;
&lt;p&gt;This task &lt;code&gt;curl&lt;/code&gt;&amp;#8216;s the site&amp;#8217;s home page to make sure we haven&amp;#8217;t completely borked
things.  We use it in lots of other tasks as a&amp;nbsp;sanity&amp;nbsp;check.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Check that the home page of the site returns an &lt;span class="caps"&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt;&lt;/span&gt; 200.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Checking site status...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;200 &lt;span class="caps"&gt;&lt;span class="caps"&gt;OK&lt;/span&gt;&lt;/span&gt;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;curl --silent -I &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;_sad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_happy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;_happy&lt;/code&gt; and &lt;code&gt;_sad&lt;/code&gt; functions just print out some simple messages to get&amp;nbsp;our&amp;nbsp;attention:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fabric.colors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;green&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_happy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Looks good from here!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_sad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;r&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;          ___           ___&lt;/span&gt;
&lt;span class="s"&gt;         /  /\         /__/\&lt;/span&gt;
&lt;span class="s"&gt;        /  /::\        \  \:\&lt;/span&gt;
&lt;span class="s"&gt;       /  /:/\:\        \__\:\&lt;/span&gt;
&lt;span class="s"&gt;      /  /:/  \:\   ___ /  /::\&lt;/span&gt;
&lt;span class="s"&gt;     /__/:/ \__\:\ /__/\  /:/\:\&lt;/span&gt;
&lt;span class="s"&gt;     \  \:\ /  /:/ \  \:\/:/__\/&lt;/span&gt;
&lt;span class="s"&gt;      \  \:\  /:/   \  \::/&lt;/span&gt;
&lt;span class="s"&gt;       \  \:\/:/     \  \:\&lt;/span&gt;
&lt;span class="s"&gt;        \  \::/       \  \:\&lt;/span&gt;
&lt;span class="s"&gt;         \__\/         \__\/&lt;/span&gt;
&lt;span class="s"&gt;          ___           ___           ___           ___&lt;/span&gt;
&lt;span class="s"&gt;         /__/\         /  /\         /  /\         /  /\     ___&lt;/span&gt;
&lt;span class="s"&gt;         \  \:\       /  /::\       /  /:/_       /  /:/_   /__/\&lt;/span&gt;
&lt;span class="s"&gt;          \  \:\     /  /:/\:\     /  /:/ /\     /  /:/ /\  \  \:\&lt;/span&gt;
&lt;span class="s"&gt;      _____\__\:\   /  /:/  \:\   /  /:/ /:/_   /  /:/ /::\  \  \:\&lt;/span&gt;
&lt;span class="s"&gt;     /__/::::::::\ /__/:/ \__\:\ /__/:/ /:/ /\ /__/:/ /:/\:\  \  \:\&lt;/span&gt;
&lt;span class="s"&gt;     \  \:\~~\~~\/ \  \:\ /  /:/ \  \:\/:/ /:/ \  \:\/:/~/:/   \  \:\&lt;/span&gt;
&lt;span class="s"&gt;      \  \:\  ~~~   \  \:\  /:/   \  \::/ /:/   \  \::/ /:/     \__\/&lt;/span&gt;
&lt;span class="s"&gt;       \  \:\        \  \:\/:/     \  \:\/:/     \__\/ /:/          __&lt;/span&gt;
&lt;span class="s"&gt;        \  \:\        \  \::/       \  \::/        /__/:/          /__/\&lt;/span&gt;
&lt;span class="s"&gt;         \__\/         \__\/         \__\/         \__\/           \__\/&lt;/span&gt;

&lt;span class="s"&gt;         Something seems to have gone wrong!&lt;/span&gt;
&lt;span class="s"&gt;         You should probably take a look at that.&lt;/span&gt;
&lt;span class="s"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This one is for when &lt;code&gt;python manage.py reset APP&lt;/code&gt; is broken because you&amp;#8217;ve changed
some &lt;code&gt;db_column&lt;/code&gt; names and Django chokes because of some constraits and you just want
to &lt;strong&gt;reset the fucking app&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s the &amp;#8220;&lt;span class="caps"&gt;&lt;span class="caps"&gt;NUKE&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;IT&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;FROM&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;ORBIT&lt;/span&gt;&lt;/span&gt;!!&amp;#8221;&amp;nbsp;option.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;KILL_IT_WITH_FIRE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c"&gt;# Generate and download the reset &lt;span class="caps"&gt;&lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/span&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py sqlreset &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; &amp;gt; reset.orig.sql&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;reset.orig.sql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;reset.sql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;reset.orig.sql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c"&gt;# Step through the first chunk of the file (the &amp;quot;drop&amp;quot; part).&lt;/span&gt;
                &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CREATE&lt;/span&gt;&lt;/span&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CONSTRAINT&lt;/span&gt;&lt;/span&gt;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c"&gt;# Don&amp;#39;t write out &lt;span class="caps"&gt;&lt;span class="caps"&gt;CONSTRAINT&lt;/span&gt;&lt;/span&gt; lines.&lt;/span&gt;
                        &lt;span class="c"&gt;# They&amp;#39;re a problem when you change db_colum names.&lt;/span&gt;
                        &lt;span class="k"&gt;pass&lt;/span&gt;
                    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;span class="caps"&gt;&lt;span class="caps"&gt;DROP&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;TABLE&lt;/span&gt;&lt;/span&gt;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c"&gt;# Cascade drops.&lt;/span&gt;
                        &lt;span class="c"&gt;# Hence with &amp;quot;with fire&amp;quot; part of this task&amp;#39;s name.&lt;/span&gt;
                        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#39; &lt;span class="caps"&gt;&lt;span class="caps"&gt;CASCADE&lt;/span&gt;&lt;/span&gt;;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;
                        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c"&gt;# Write other lines through untoched.&lt;/span&gt;
                        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="c"&gt;# Write out the rest of the file untouched.&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="c"&gt;# Upload the processed &lt;span class="caps"&gt;&lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/span&gt; file.&lt;/span&gt;
    &lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;reset.sql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;reset.ready.sql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;use_sudo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c"&gt;# Use the &lt;span class="caps"&gt;&lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/span&gt; to reset the app, and fake a migration.&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py dbshell &amp;lt; reset.ready.sql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py migrate --fake --delete-ghost-migrations &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This task uses Mercurial&amp;#8217;s local tags to add a &lt;code&gt;production&lt;/code&gt; or &lt;code&gt;staging&lt;/code&gt; tag in your local
repository, so you can easy see where the production/staging servers are at
compared to your&amp;nbsp;local&amp;nbsp;repo.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retag&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Check which revision the site is at and update the local tag.&lt;/span&gt;

&lt;span class="sd"&gt;    Useful if someone else has deployed (which makes your production/staging local&lt;/span&gt;
&lt;span class="sd"&gt;    tag incorrect.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;prod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;stag&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;env_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;prod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;stag&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;hg id --rev . --quiet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;hg tag --local --force &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; --rev &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This task tails the Gunicorn logs on the server so you can quickly find out what&amp;#8217;s
happening when things&amp;nbsp;blow&amp;nbsp;up.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tailgun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Tail the Gunicorn log file.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;tail -f .gunicorn.log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;tail .gunicorn.log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We&amp;#8217;ve got a lot of other tasks but they&amp;#8217;re pretty specific to&amp;nbsp;our&amp;nbsp;setup.&lt;/p&gt;
&lt;h2 id="wrangling-databases-with-south"&gt;Wrangling Databases&amp;nbsp;with&amp;nbsp;South&lt;/h2&gt;
&lt;p&gt;If you&amp;#8217;re not using &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt;, you need to&amp;nbsp;start.&amp;nbsp;Now.&lt;/p&gt;
&lt;p&gt;No, really, I&amp;#8217;ll wait.  Take 30 minutes, try the &lt;a href="http://south.aeracode.org/docs/tutorial/index.html"&gt;tutorial&lt;/a&gt;, wrap your head
around it and come back.  It&amp;#8217;s far more important than this&amp;nbsp;blog&amp;nbsp;post.&lt;/p&gt;
&lt;h3 id="useful-fabric-tasks"&gt;Useful&amp;nbsp;Fabric&amp;nbsp;Tasks&lt;/h3&gt;
&lt;p&gt;South is awesome but its commands are very long-winded.  Here&amp;#8217;s the set of fabric
tasks I use to save quite a bit&amp;nbsp;of&amp;nbsp;typing:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run any needed migrations.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py migrate &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;migrate_fake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run any needed migrations with --fake.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py migrate --fake &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;migrate_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run any needed migrations with --fake.  No, seriously.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;site_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;venv_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;manage.py migrate --fake --delete-ghost-migrations &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember that running a migration without specifying an app will migrate everything,
so a simple &lt;code&gt;fab dev migrate&lt;/code&gt; will do&amp;nbsp;the&amp;nbsp;trick.&lt;/p&gt;
&lt;h2 id="watching-for-changes"&gt;Watching&amp;nbsp;for&amp;nbsp;Changes&lt;/h2&gt;
&lt;p&gt;When developing locally you&amp;#8217;ll want to make a change to your code and have the server
reload that code automatically.  The Django development server does this, and we can
hack it into our Vagrant/Gunicorn&amp;nbsp;setup&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;First, add a &lt;code&gt;monitor.py&lt;/code&gt; file at the root of your project (I believe I found this
code &lt;a href="http://code.google.com/p/modwsgi/wiki/ReloadingSourceCode"&gt;here&lt;/a&gt;, but I may&amp;nbsp;be&amp;nbsp;wrong):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;signal&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;atexit&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;Queue&lt;/span&gt;

&lt;span class="n"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="n"&gt;_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_restart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;monitor (pid=&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;):&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; Change detected to &lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; Triggering process restart.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;SIGINT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_modified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;# If path doesn&amp;#39;t denote a file and were previously&lt;/span&gt;
        &lt;span class="c"&gt;# tracking it, then it has been removed or the file type&lt;/span&gt;
        &lt;span class="c"&gt;# has changed so force a restart. If not previously&lt;/span&gt;
        &lt;span class="c"&gt;# tracking the file then we can ignore it as probably&lt;/span&gt;
        &lt;span class="c"&gt;# pseudo reference such as when file extracted from a&lt;/span&gt;
        &lt;span class="c"&gt;# collection of modules contained in a zip file.&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_times&lt;/span&gt;

        &lt;span class="c"&gt;# Check for when file last modified.&lt;/span&gt;

        &lt;span class="n"&gt;mtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_mtime&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mtime&lt;/span&gt;

        &lt;span class="c"&gt;# Force restart when modification time has changed, even&lt;/span&gt;
        &lt;span class="c"&gt;# if time now older, as that could indicate older file&lt;/span&gt;
        &lt;span class="c"&gt;# has been restored.&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mtime&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;# If any exception occured, likely that file has been&lt;/span&gt;
        &lt;span class="c"&gt;# been removed just before stat(), so force a restart.&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_monitor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;# Check modification times on all files in sys.modules.&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;__file__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;__file__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;.pyc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;.pyo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;.pyd&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_modified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_restart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;# Check modification times on files which have&lt;/span&gt;
        &lt;span class="c"&gt;# specifically been registered for monitoring.&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_modified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_restart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;# Go to sleep for specified interval.&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_monitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDaemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_exiting&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;atexit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_exiting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;_interval&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;

    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;_running&lt;/span&gt;
    &lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;_running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;monitor (pid=&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;):&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; Starting change monitor.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;
        &lt;span class="n"&gt;_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next add a &lt;code&gt;post_fork&lt;/code&gt; hook to your Gunicorn config file that uses the monitor to
watch&amp;nbsp;for&amp;nbsp;changes:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_fork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;monitor&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;local_settings&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;local_settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;DEBUG&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Starting change monitor.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now the Gunicorn server will automatically restart whenever code is changed.  Use
whatever method for determining debug status that you like.  We use
&lt;code&gt;local_settings.py&lt;/code&gt; files which all have &lt;code&gt;DEBUG&lt;/code&gt; variables, so that works&amp;nbsp;for&amp;nbsp;us.&lt;/p&gt;
&lt;p&gt;It will &lt;em&gt;not&lt;/em&gt; restart when you add new code (e.g. when you install a new app), so
you&amp;#8217;ll need to handle that manually with &lt;code&gt;fab dev restart&lt;/code&gt;, but that&amp;#8217;s not&amp;nbsp;too&amp;nbsp;bad!&lt;/p&gt;
&lt;h3 id="using-the-werkzeug-debugger-with-gunicorn"&gt;Using the Werkzeug Debugger&amp;nbsp;with&amp;nbsp;Gunicorn&lt;/h3&gt;
&lt;p&gt;The final piece of the puzzle is being able to use the fantastic &lt;a href="http://werkzeug.pocoo.org/docs/debug/"&gt;Werkzeug
Debugger&lt;/a&gt; while running on the development &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt;&amp;nbsp;with&amp;nbsp;Gunicorn.&lt;/p&gt;
&lt;p&gt;To do this, create a &lt;code&gt;debug_wsgi.py&lt;/code&gt; file at the root of&amp;nbsp;your&amp;nbsp;project:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;site&lt;/span&gt;

&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;
&lt;span class="n"&gt;site_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;project_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addsitedir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;VIRTUALENV_SITE_PACKAGES&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;setup_environ&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;settings&lt;/span&gt;
&lt;span class="n"&gt;setup_environ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;django.core.handlers.wsgi&lt;/span&gt;
&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wsgi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WSGIHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;werkzeug.debug&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DebuggedApplication&lt;/span&gt;
&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DebuggedApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evalex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;null_technical_500_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;
&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;technical_500_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null_technical_500_response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Have Gunicorn use this file to run your development server with &lt;code&gt;gunicorn
debug_wsgi:application&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make sure to replace &lt;code&gt;'VIRTUALENV_SITE_PACKAGES'&lt;/code&gt; with the &lt;em&gt;full&lt;/em&gt; path to your
virtualenv&amp;#8217;s &lt;code&gt;site_packages&lt;/code&gt; directory.  You might want to make this a setting in
a machine-specific&amp;nbsp;settings&amp;nbsp;file.&lt;/p&gt;
&lt;h3 id="pulling-uploads"&gt;Pulling&amp;nbsp;Uploads&lt;/h3&gt;
&lt;p&gt;Once you give a client access to a site they&amp;#8217;ll probably be uploading images (through
Django&amp;#8217;s built-in file uploading features or with &lt;a href="http://code.google.com/p/django-filebrowser/"&gt;django-filebrowser&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;When you&amp;#8217;re making changes locally it&amp;#8217;s often useful to have these uploaded files on
your local &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt;, otherwise you end up with a bunch of&amp;nbsp;broken&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s a simple Fabric task that will pull down all the uploads from&amp;nbsp;the&amp;nbsp;server:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pull_uploads&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Copy the uploads from the site to your local machine.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;uploads_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;chmod -R a+r &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploads_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rsync_command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;r&amp;quot;&amp;quot;&amp;quot;rsync -av -e &amp;#39;ssh -p &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploads_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;&amp;#39;media/uploads&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rsync_command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You might be wondering about the line that strips &lt;code&gt;/&lt;/code&gt; characters and then adds them
back in.  &lt;code&gt;rsync&lt;/code&gt; does different things depending on whether you end a path with
a &lt;code&gt;/&lt;/code&gt;, so this is actually&amp;nbsp;pretty&amp;nbsp;important.&lt;/p&gt;
&lt;p&gt;In your host task you&amp;#8217;ll need to set the &lt;code&gt;uploads_path&lt;/code&gt; variable to something&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;www&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;myproject&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploads_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;media&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;uploads&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now you can run &lt;code&gt;fab production pull_uploads&lt;/code&gt; to pull down all the files people have
uploaded to the&amp;nbsp;production&amp;nbsp;server.&lt;/p&gt;
&lt;h3 id="preventing-accidents"&gt;Preventing&amp;nbsp;Accidents&lt;/h3&gt;
&lt;p&gt;Deploying to test and staging servers should be quick and easy. Deploying to
production servers should be harder to prevent people from accidentally&amp;nbsp;doing&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve created a little function that I call before deploying to production servers.
It forces me to type in random words from the system word list before proceeding to
make sure I &lt;em&gt;really&lt;/em&gt; know what&amp;nbsp;I&amp;#8217;m&amp;nbsp;doing:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fabric.api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fabric.operations&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fabric.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;abort&lt;/span&gt;

&lt;span class="n"&gt;WORDLIST_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;usr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;share&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;dict&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Are you sure you want to do this?&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;WORD_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;  [&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s"&gt;] Type &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; to continue (^C quits): &amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prevent_horrible_accidents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;horror_rating&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Prompt the user to enter random words to prevent doing something stupid.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;valid_wordlist_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wp&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WORDLIST_PATHS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wp&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;valid_wordlist_paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;No wordlists found!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid_wordlist_paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;wordlist_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wordlist_file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horror_rating&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;p_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORD_PROMPT&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;horror_rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;r&amp;#39;^&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;$&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You may need to adjust &lt;code&gt;WORDLIST_PATHS&lt;/code&gt; if you&amp;#8217;re not on &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt;&amp;nbsp;X.&lt;/p&gt;
&lt;h2 id="working-with-third-party-apps"&gt;Working with&amp;nbsp;Third-Party&amp;nbsp;Apps&lt;/h2&gt;
&lt;p&gt;One of the best parts about working with Django is that many problems have already
been solved and the solutions have been released as&amp;nbsp;open-source&amp;nbsp;applications.&lt;/p&gt;
&lt;p&gt;We use quite a few open-source apps, and there are a couple of tricks I&amp;#8217;ve learned to
make working with&amp;nbsp;them&amp;nbsp;easier.&lt;/p&gt;
&lt;h3 id="installing-apps-from-repositories"&gt;Installing Apps&amp;nbsp;from&amp;nbsp;Repositories&lt;/h3&gt;
&lt;p&gt;If I&amp;#8217;m going to use an open-source Django app in a project I&amp;#8217;ll almost always install
it as an editable repository on the &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt; with &lt;code&gt;pip install -e&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Others may disagree with me on this, but I think it&amp;#8217;s the best way&amp;nbsp;to&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;Often I&amp;#8217;ll find a bug that I think may be in one of the third-party apps I&amp;#8217;m using.
Installing the apps as repositories makes it easy to read their source and figure out
if the bug is really in&amp;nbsp;the&amp;nbsp;app.&lt;/p&gt;
&lt;p&gt;If the bug &lt;em&gt;is&lt;/em&gt; in the third-party app having the app installed as a repository makes
it simple to fix the bug, fork the project on BitBucket or GitHub, send a pull
request, and get back&amp;nbsp;to&amp;nbsp;work.&lt;/p&gt;
&lt;h3 id="mirroring-repositories"&gt;Mirroring&amp;nbsp;Repositories&lt;/h3&gt;
&lt;p&gt;One problem we&amp;#8217;ve run into at Dumbwaiter is that the repos for third-party apps we
use are scattered across GitHub, BitBucket, Google Code, and other servers.  If any
one of these services goes down we&amp;#8217;re stuck waiting for it to come&amp;nbsp;back&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;A while ago I took half a day and consolidated all of these repos onto one of the
servers that we control.  The basic process went&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;a href="http://hg-git.github.com/"&gt;hg-git&lt;/a&gt; and &lt;a href="https://bitbucket.org/durin42/hgsubversion/wiki/Home"&gt;hgsubversion&lt;/a&gt; to convert the git and &lt;span class="caps"&gt;&lt;span class="caps"&gt;SVN&lt;/span&gt;&lt;/span&gt; repos to&amp;nbsp;Mercurial&amp;nbsp;repos.&lt;/li&gt;
&lt;li&gt;Set up a master &lt;code&gt;mirror&lt;/code&gt; Mercurial repo with all the app repos&amp;nbsp;as&amp;nbsp;subrepos.&lt;/li&gt;
&lt;li&gt;Push the master repo and all the subrepos up to one of&amp;nbsp;our&amp;nbsp;Linodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we can use &lt;code&gt;-e ssh://hg@OUR_LINODE/mirror/APP@REV_THAT_WORKS#egg=APP&lt;/code&gt; in our
&lt;code&gt;requirements.txt&lt;/code&gt; files to install apps from our mirror.  When we want to update our
dependencies we can simply pull from the upstream repos and commit in the&amp;nbsp;mirror&amp;nbsp;repo.&lt;/p&gt;
&lt;p&gt;If our mirror goes down it&amp;#8217;s not a big deal, because we have far bigger problems to
worry about than&amp;nbsp;new&amp;nbsp;projects.&lt;/p&gt;
&lt;p&gt;I wrote a few scripts to automate updating apps and such, but they&amp;#8217;re extremely hacky
so I don&amp;#8217;t want to post them here.  Take half a day and write your own set &amp;#8212; it&amp;#8217;s
definitely worth it to have your own mirror of your&amp;nbsp;specific&amp;nbsp;dependencies.&lt;/p&gt;
&lt;h3 id="using-bcvi-to-edit-files"&gt;Using &lt;span class="caps"&gt;&lt;span class="caps"&gt;BCVI&lt;/span&gt;&lt;/span&gt; to&amp;nbsp;Edit&amp;nbsp;Files&lt;/h3&gt;
&lt;p&gt;I said that when I find a bug that I think is in a third-party app I&amp;#8217;ll poke around
with the app and try to figure it out.  But since all the apps are installed in
a virtualenv on the Vagrant &lt;span class="caps"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt;&lt;/span&gt; it might seem like it&amp;#8217;s a pain in the ass to edit&amp;nbsp;those&amp;nbsp;files!&lt;/p&gt;
&lt;p&gt;Luckily &lt;a href="http://sshmenu.sourceforge.net/articles/bcvi/"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;BCVI&lt;/span&gt;&lt;/span&gt;&lt;/a&gt; exists.  It&amp;#8217;s a utility that opens a &amp;#8220;back channel&amp;#8221; to your local
machine when you &lt;span class="caps"&gt;&lt;span class="caps"&gt;SSH&lt;/span&gt;&lt;/span&gt; and lets you run &lt;code&gt;vi FILE&lt;/code&gt; to open that file in
Vim/MacVim/GVim/etc on your &lt;em&gt;local&lt;/em&gt; machine.  When you save the file it uploads it
back to the server automatically&amp;nbsp;for&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;It can be a bit tricky to set up, but it&amp;#8217;s worth it.&amp;nbsp;Trust&amp;nbsp;me.&lt;/p&gt;
&lt;h2 id="improving-the-admin-interface"&gt;Improving the&amp;nbsp;Admin&amp;nbsp;Interface&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m going to be honest: Django&amp;#8217;s admin interface is the main reason I&amp;#8217;m still using
it.  Other frameworks like &lt;a href="http://flask.pocoo.org/"&gt;Flask&lt;/a&gt; are great, but Django&amp;#8217;s admin saves me
&lt;em&gt;ridiculous&lt;/em&gt; amounts of time when I&amp;#8217;m making simple &lt;span class="caps"&gt;&lt;span class="caps"&gt;CRUD&lt;/span&gt;&lt;/span&gt; sites&amp;nbsp;for&amp;nbsp;clients.&lt;/p&gt;
&lt;p&gt;That said, the Django admin isn&amp;#8217;t the prettiest thing around, but we can give it&amp;nbsp;a&amp;nbsp;facelift.&lt;/p&gt;
&lt;h3 id="enter-grappelli"&gt;Enter&amp;nbsp;Grappelli&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://django-grappelli.readthedocs.org/"&gt;Grappelli&lt;/a&gt; is a Django app that reskins the admin interface beautifully.  It also
adds some functionality like drag-and-drop reordering of inlines, and allows you to
customize the dashboard to your liking.  &lt;em&gt;Every&lt;/em&gt; Django site I work on uses Grappelli
&amp;#8212; it&amp;#8217;s just&amp;nbsp;that&amp;nbsp;good.&lt;/p&gt;
&lt;p&gt;The downside of Grappelli is that it changes quite a lot and breaks backwards
compatibility at the drop of&amp;nbsp;a&amp;nbsp;hat.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re going to use Grappelli you &lt;em&gt;must&lt;/em&gt; freeze your requirements.txt files and
work with a single version at a time.  Trying to always work from the trunk will make&amp;nbsp;you&amp;nbsp;drink.&lt;/p&gt;
&lt;h3 id="an-ugly-hack-to-show-usable-foreign-key-fields"&gt;An Ugly Hack to Show Usable Foreign&amp;nbsp;Key&amp;nbsp;Fields&lt;/h3&gt;
&lt;p&gt;A limitation of both Grappelli and the stock Django admin is that it seems like you
can&amp;#8217;t easily show fields from related models in the admin&amp;nbsp;list&amp;nbsp;view.&lt;/p&gt;
&lt;p&gt;For example, if you&amp;#8217;re new to Django you might expect this&amp;nbsp;to&amp;nbsp;work:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogEntryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;author__name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Unfortunately Django chokes on the &lt;code&gt;author__name&lt;/code&gt; lookup.  You can &lt;em&gt;display&lt;/em&gt; the name
without too&amp;nbsp;much&amp;nbsp;fuss:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogEntryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;author_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;author_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That will display the name just fine.  However, it won&amp;#8217;t be a fully-fledged column in
the Django admin because you can&amp;#8217;t sort&amp;nbsp;on&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;It may seem like this is the end &amp;#8212; if it could be a fully-functional field, why
wouldn&amp;#8217;t Django just let you use &lt;code&gt;author__name&lt;/code&gt;?  Luckily we can add one more line to
fix&amp;nbsp;the&amp;nbsp;problem:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogEntryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;author_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;author_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;author_name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_order_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;author__name&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now the author name has all the functionality of a real &lt;code&gt;list_display&lt;/code&gt; entry.&lt;/p&gt;
&lt;h2 id="using-django-annoying"&gt;Using&amp;nbsp;Django-Annoying&lt;/h2&gt;
&lt;p&gt;If you haven&amp;#8217;t heard of &lt;a href="https://bitbucket.org/offline/django-annoying/wiki/Home"&gt;django-annoying&lt;/a&gt; you should definitely check it out.  It&amp;#8217;s
got a bunch of miscellaneous functions that fix some common, annoying parts&amp;nbsp;of&amp;nbsp;Django.&lt;/p&gt;
&lt;p&gt;My two personal favorites from the package are a pair of decorators that help make
your views much,&amp;nbsp;much&amp;nbsp;cleaner.&lt;/p&gt;
&lt;h3 id="the-render_to-decorator"&gt;The&amp;nbsp;render_to&amp;nbsp;Decorator&lt;/h3&gt;
&lt;p&gt;The decorator is called &lt;code&gt;render_to&lt;/code&gt; and it eliminates the ugly &lt;code&gt;render_to_response&lt;/code&gt;
calls that Django normally forces you to use in every&amp;nbsp;single&amp;nbsp;view.&lt;/p&gt;
&lt;p&gt;Normally you&amp;#8217;d use something&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_to_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;video_list.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;videos&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;videos&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                              &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;With &lt;code&gt;render_to&lt;/code&gt; your view gets&amp;nbsp;much&amp;nbsp;cleaner:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="nd"&gt;@render_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;video_list.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;videos&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;videos&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Less typing &lt;code&gt;context_instance=...&lt;/code&gt; over and over, and less syntax&amp;nbsp;to&amp;nbsp;remember.&lt;/p&gt;
&lt;p&gt;Yes, I know about Django 1.3&amp;#8217;s &lt;code&gt;render&lt;/code&gt; shortcut.  You have to type &lt;code&gt;request&lt;/code&gt; every
single time with &lt;code&gt;render&lt;/code&gt;, so the &lt;code&gt;render_to&lt;/code&gt; decorator&amp;nbsp;still&amp;nbsp;wins.&lt;/p&gt;
&lt;h3 id="the-ajax_request-decorator"&gt;The&amp;nbsp;ajax_request&amp;nbsp;Decorator&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;ajax_request&lt;/code&gt; decorator is like &lt;code&gt;render_to&lt;/code&gt; for &lt;span class="caps"&gt;&lt;span class="caps"&gt;AJAX&lt;/span&gt;&lt;/span&gt; requests.  You simply
return a Python dictionary from your view and the decorator handles the &lt;span class="caps"&gt;&lt;span class="caps"&gt;JSON&lt;/span&gt;&lt;/span&gt; encoding&amp;nbsp;and&amp;nbsp;such:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="nd"&gt;@ajax_request&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ajax_get_entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;blog_entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BlogEntry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;entries&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="templating-tricks"&gt;Templating&amp;nbsp;Tricks&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m not a frontend developer, but I&amp;#8217;ve done my share of &lt;span class="caps"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/span&gt; hacking at Dumbwaiter.
Here are a few of the tricks&amp;nbsp;I&amp;#8217;ve&amp;nbsp;learned.&lt;/p&gt;
&lt;h3 id="null-checks-and-fallbacks"&gt;Null Checks&amp;nbsp;and&amp;nbsp;Fallbacks&lt;/h3&gt;
&lt;p&gt;A common pattern I see in Django templates looks&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;business.title&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;business.title&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;business.short_title&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here&amp;#8217;s a simpler way to&amp;nbsp;do&amp;nbsp;that:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;firstof&lt;/span&gt; &lt;span class="nv"&gt;business.title&lt;/span&gt; &lt;span class="nv"&gt;business.short_title&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;firstof&lt;/code&gt; will return the first non-Falsy item in&amp;nbsp;its&amp;nbsp;arguments.&lt;/p&gt;
&lt;h3 id="manipulating-query-strings"&gt;Manipulating&amp;nbsp;Query&amp;nbsp;Strings&lt;/h3&gt;
&lt;p&gt;Query strings are normally not a big deal, but every once in a while you&amp;#8217;ll have
a model listing page where you need to filter by category, and number of spaces, and
tags, etc all&amp;nbsp;at&amp;nbsp;once.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re trying to manage &lt;span class="caps"&gt;&lt;span class="caps"&gt;GET&lt;/span&gt;&lt;/span&gt; queries manually it can get pretty hairy&amp;nbsp;very&amp;nbsp;fast.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://djangosnippets.org/snippets/2237/"&gt;This Django snippet&lt;/a&gt; makes working with query strings in templates&amp;nbsp;a&amp;nbsp;breeze.&lt;/p&gt;
&lt;h3 id="satisfying-your-designer-with-typogrify"&gt;Satisfying Your Designer&amp;nbsp;with&amp;nbsp;Typogrify&lt;/h3&gt;
&lt;p&gt;If you haven&amp;#8217;t heard of &lt;a href="http://code.google.com/p/typogrify/"&gt;Typogrify&lt;/a&gt; you should take a look at it.  It makes it easy
to add all the typographic goodness your designers are&amp;nbsp;looking&amp;nbsp;for.&lt;/p&gt;
&lt;h2 id="the-flat-page-trainwreck"&gt;The Flat&amp;nbsp;Page&amp;nbsp;Trainwreck&lt;/h2&gt;
&lt;p&gt;Creating a site for a client is very different than creating a site for yourself.
For pretty much every client we&amp;#8217;ve dealt with we&amp;#8217;ve heard: &amp;#8220;can&amp;#8217;t we just create
a new page at /drink-special/ for this special deal&amp;nbsp;we&amp;#8217;re&amp;nbsp;running?&amp;#8221;&lt;/p&gt;
&lt;p&gt;Having clients go through you to make new pages is simply too much overhead.  We
needed a way to let clients create new pages (like &lt;code&gt;/drink-special/&lt;/code&gt;) on the fly,
without&amp;nbsp;our&amp;nbsp;intervention.&lt;/p&gt;
&lt;p&gt;Django has a &amp;#8220;flatpages&amp;#8221; app that solves this problem.&amp;nbsp;Kind&amp;nbsp;of.&lt;/p&gt;
&lt;p&gt;When using flat pages clients need to do two things that are often too much for&amp;nbsp;non-technical&amp;nbsp;people:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manage&amp;nbsp;URLs&amp;nbsp;manually.&lt;/li&gt;
&lt;li&gt;Write all content as raw &lt;span class="caps"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/span&gt; in a single&amp;nbsp;text&amp;nbsp;field.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&amp;#8217;ve tried a lot of Django &lt;span class="caps"&gt;&lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/span&gt; apps at Dumbwaiter, and none of them made us happy.
They all seemed to have some or all of the&amp;nbsp;following&amp;nbsp;problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They take over your site and make you write a &amp;#8220;Django-WhateverCMS site&amp;#8221; instead of
  a&amp;nbsp;&amp;#8220;Django&amp;nbsp;site&amp;#8221;.&lt;/li&gt;
&lt;li&gt;They&amp;#8217;re extremely feature-rich and complicated with features like
  internationalization, redirects, versions, and many others.  This is great if you
  need the flexibility, but bad if your clients just need to create a couple&amp;nbsp;of&amp;nbsp;pages.&lt;/li&gt;
&lt;li&gt;They break &lt;code&gt;APPEND_TRAILING_SLASH&lt;/code&gt; and make you clutter your &lt;code&gt;urls.py&lt;/code&gt; files with
  a bunch of extra code ot&amp;nbsp;handle&amp;nbsp;this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I finally got fed up and wrote my own Django &lt;span class="caps"&gt;&lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/span&gt; app: &lt;a href="http://stoat.rtfd.org/"&gt;Stoat&lt;/a&gt;.  Stoat is designed
to be sleek, with only the features that our&amp;nbsp;clients&amp;nbsp;need.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not officially version 1.0 yet, but we&amp;#8217;re using it for a few clients and it&amp;#8217;s
working well.  Check it out if you&amp;#8217;re looking for a more lightweight &lt;span class="caps"&gt;&lt;span class="caps"&gt;CMS&lt;/span&gt;&lt;/span&gt;&amp;nbsp;app.&lt;/p&gt;
&lt;h2 id="editing-with-vim"&gt;Editing&amp;nbsp;with&amp;nbsp;Vim&lt;/h2&gt;
&lt;p&gt;I &lt;a href="/blog/2010/09/coming-home-to-vim/"&gt;use Vim&lt;/a&gt; to edit everything.  Naturally I&amp;#8217;ve found a bunch of plugins,
mappings and other tricks that make it even better when working on&amp;nbsp;Django&amp;nbsp;projects.&lt;/p&gt;
&lt;h3 id="vim-for-django"&gt;Vim&amp;nbsp;for&amp;nbsp;Django&lt;/h3&gt;
&lt;p&gt;There are a lot of ways to make Vim work with Django.  I won&amp;#8217;t go into all of them in
this post, but a good place to start is &lt;a href="https://code.djangoproject.com/wiki/UsingVimWithDjango"&gt;this Django wiki page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="filetype-mappings"&gt;Filetype&amp;nbsp;Mappings&lt;/h3&gt;
&lt;p&gt;Most files in a Django project have one of two extensions: &lt;code&gt;.py&lt;/code&gt; and &lt;code&gt;.html&lt;/code&gt;.
Unfortunately these extensions aren&amp;#8217;t unique to Django, so Vim doesn&amp;#8217;t automatically
set the correct &lt;code&gt;filetype&lt;/code&gt; when you&amp;nbsp;open&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve added a few mappings to my &lt;code&gt;.vimrc&lt;/code&gt; to make it quick and easy to set the correct
&lt;code&gt;filetype&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;nnoremap _dt :&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;htmldjango&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
nnoremap _pd :&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;CR&lt;/span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I also have a few autocommands that set the filetype for me when I&amp;#8217;m editing a file
whose name &amp;#8220;sounds like&amp;#8221; a&amp;nbsp;Django&amp;nbsp;file:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; admin.&lt;span class="k"&gt;py&lt;/span&gt;     &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; urls.&lt;span class="k"&gt;py&lt;/span&gt;      &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; models.&lt;span class="k"&gt;py&lt;/span&gt;    &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; views.&lt;span class="k"&gt;py&lt;/span&gt;     &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; settings.&lt;span class="k"&gt;py&lt;/span&gt;  &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; forms.&lt;span class="k"&gt;py&lt;/span&gt;     &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;python.django
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="python-sanity-checking"&gt;Python&amp;nbsp;Sanity&amp;nbsp;Checking&lt;/h3&gt;
&lt;p&gt;Lets be honest here: it takes a lot of work to turn Vim into an &amp;#8220;&lt;span class="caps"&gt;&lt;span class="caps"&gt;IDE&lt;/span&gt;&lt;/span&gt;&amp;#8221;, and even then
it doesn&amp;#8217;t reach the level of something like Eclipse for Java.  Anyone who claims it
has the same levels of integration and functionality is&amp;nbsp;simply&amp;nbsp;lying.&lt;/p&gt;
&lt;p&gt;With that said I&amp;#8217;ll make an opinionated statement that is going to piss some of&amp;nbsp;you&amp;nbsp;off.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I am a programmer, not an &lt;span class="caps"&gt;&lt;span class="caps"&gt;IDE&lt;/span&gt;&lt;/span&gt;&amp;nbsp;operator.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;nbsp;know&amp;nbsp;Python.&lt;/p&gt;
&lt;p&gt;I&amp;nbsp;know&amp;nbsp;Django.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t need to hit Cmd+Space twice for every line of code&amp;nbsp;I&amp;nbsp;write.&lt;/p&gt;
&lt;p&gt;When someone asks me &amp;#8220;how do you run your site&amp;#8221; I do &lt;strong&gt;not&lt;/strong&gt; answer: &amp;#8220;click the green
triangle&amp;nbsp;in&amp;nbsp;Eclipse&amp;#8221;.&lt;/p&gt;
&lt;p&gt;However, I am human.  I do stupid things like forgetting a colon or forgetting an
import.  To help me with those problems I&amp;#8217;ve turned to &lt;a href="http://www.vim.org/scripts/script.php?script_id=2736"&gt;Syntastic&lt;/a&gt; and Kevin
Watters&amp;#8217; &lt;a href="https://github.com/kevinw/pyflakes"&gt;Pyflakes fork&lt;/a&gt;&amp;nbsp;for&amp;nbsp;Vim.&lt;/p&gt;
&lt;p&gt;Syntastic is a Vim plugin that adds on-the-fly syntax-checking for many different
file formats.  If you have Pyflakes installed it will automatically show you errors
in&amp;nbsp;your&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Pyflakes doesn&amp;#8217;t have &lt;span class="caps"&gt;&lt;span class="caps"&gt;IDE&lt;/span&gt;&lt;/span&gt;-level integration with your code.  It doesn&amp;#8217;t check that
whatever libraries you &lt;code&gt;import&lt;/code&gt; actually exist.  It simply checks that your files are
probably-valid Python, and tells you when&amp;nbsp;they&amp;#8217;re&amp;nbsp;not.&lt;/p&gt;
&lt;p&gt;This is enough for me.  It catches the stupid mistakes I make.  The less-stupid,
more-subtle mistakes slip by it, but to be fair many of them would have slipped by an
&amp;#8220;&lt;span class="caps"&gt;&lt;span class="caps"&gt;IDE&lt;/span&gt;&lt;/span&gt;&amp;#8221;&amp;nbsp;as&amp;nbsp;well.&lt;/p&gt;
&lt;h3 id="javascript-sanity-checking-and-folding"&gt;Javascript Sanity Checking&amp;nbsp;and&amp;nbsp;Folding&lt;/h3&gt;
&lt;p&gt;Syntastic also supports Javascript if you have Javascript Lint installed (&lt;code&gt;brew
install jsl&lt;/code&gt; on &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt; X).  It&amp;#8217;s not perfect but it &lt;em&gt;will&lt;/em&gt; catch things like using
trailing commas in&amp;nbsp;object&amp;nbsp;literals.&lt;/p&gt;
&lt;p&gt;Some people like using CTags to get an overview of their code.  I take a more
low-tech approach and am in love with code folding.  When I fold my code
I automatically get an overview of everything in&amp;nbsp;each&amp;nbsp;file.&lt;/p&gt;
&lt;p&gt;By default Vim doesn&amp;#8217;t fold Javascript files, but you can add some basic, perfectly
serviceable folding with these two lines in&amp;nbsp;your&amp;nbsp;.vimrc:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;au &lt;span class="nb"&gt;FileType&lt;/span&gt; javascript &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="nb"&gt;foldmethod&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;marker
au &lt;span class="nb"&gt;FileType&lt;/span&gt; javascript &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="nb"&gt;foldmarker&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;{&lt;span class="p"&gt;,&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="django-autocommands"&gt;Django&amp;nbsp;Autocommands&lt;/h3&gt;
&lt;p&gt;I &lt;em&gt;rarely&lt;/em&gt; work with raw &lt;span class="caps"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/span&gt; files any more.  Whenever I open a file ending in
&lt;code&gt;.html&lt;/code&gt; it&amp;#8217;s almost always a Django template (or a &lt;a href="http://jinja.pocoo.org/"&gt;Jinja&lt;/a&gt; template, which has
a very similar syntax).  I&amp;#8217;ve added an autocommand to automatically set the correct
filetype whenever I open a &lt;code&gt;.html&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; *.html &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="k"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;htmldjango
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I also have some autocommands that tweak how a few specific files&amp;nbsp;are&amp;nbsp;handled:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; urls.&lt;span class="k"&gt;py&lt;/span&gt;      &lt;span class="k"&gt;setlocal&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; settings.&lt;span class="k"&gt;py&lt;/span&gt;  normal&lt;span class="p"&gt;!&lt;/span&gt; zR
au &lt;span class="nb"&gt;BufNewFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;BufRead&lt;/span&gt; dashboard.&lt;span class="k"&gt;py&lt;/span&gt; normal&lt;span class="p"&gt;!&lt;/span&gt; zR
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This automatically unfolds &lt;code&gt;urls.py&lt;/code&gt;, &lt;code&gt;dashboard.py&lt;/code&gt; and &lt;code&gt;settings.py&lt;/code&gt; (I prefer
seeing those unfolded) and unsets line wrapping for &lt;code&gt;urls.py&lt;/code&gt; (lines in a &lt;code&gt;urls.py&lt;/code&gt;
file can get long and are hard to read&amp;nbsp;when&amp;nbsp;wrapped).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope that this longer-than-expected blog entry has given you at least one or two
things to&amp;nbsp;think&amp;nbsp;about.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve learned a lot while working with Django for Dumbwaiter every day, but I&amp;#8217;m sure
there&amp;#8217;s still a lot I&amp;#8217;ve missed.  If you see something I could be doing better please
let&amp;nbsp;me&amp;nbsp;know!&lt;/p&gt;
                
            
                    
                &lt;img src="http://feeds.feedburner.com/~r/stevelosh/~4/3yBaI0DYSWc" height="1" width="1"/&gt;</content><feedburner:origLink>http://stevelosh.com/blog/2011/06/django-advice/</feedburner:origLink></entry><entry><title type="html">Going Paper-Free for $220</title><author><name>Steve Losh</name></author><link href="http://feedproxy.google.com/~r/stevelosh/~3/ou8uE_a_qao/" /><updated>2011-05-26T13:44:00Z</updated><published>2011-05-26T13:44:00Z</published><id>http://stevelosh.com/blog/2011/05/paper-free/</id><content type="html">
                    
                        
                
                    &lt;p&gt;It&amp;#8217;s 2011. Personal computers have been around and popular for well over a decade
now, and yet we still have to deal with a huge amount of&amp;nbsp;physical&amp;nbsp;paper.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been wanting to go paper-free for a long time now. The advantages&amp;nbsp;are&amp;nbsp;obvious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paper takes up physical space in our homes that digital&amp;nbsp;files&amp;nbsp;don&amp;#8217;t.&lt;/li&gt;
&lt;li&gt;Digital files, if properly encrypted, are far more secure than sheets of paper that
  could&amp;nbsp;be&amp;nbsp;stolen.&lt;/li&gt;
&lt;li&gt;Digital files can be searched in an instant, while papers have to be laboriously&amp;nbsp;sorted&amp;nbsp;through.&lt;/li&gt;
&lt;li&gt;Digital files can be backed up perfectly&amp;nbsp;and&amp;nbsp;easily.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After reading &lt;a href="http://ryanwaggoner.com/2010/11/how-i-filled-two-dumpsters-and-went-paperless-with-the-fujitsu-scansnap-s1500/"&gt;this article&lt;/a&gt; I was psyched to scan and shred all the boxes of paper
sitting in my apartment, but the $420+ price tag was hard to swallow. I started
looking around for&amp;nbsp;other&amp;nbsp;options.&lt;/p&gt;
&lt;p&gt;Here are the requirements I have for any&amp;nbsp;paper-free&amp;nbsp;system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The scanned files need to be &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ed so I can search them easily. I&amp;#8217;m too lazy to
  categorize and tag&amp;nbsp;files&amp;nbsp;manually.&lt;/li&gt;
&lt;li&gt;I need to be able to scan files anywhere.  If I&amp;#8217;m out at dinner I want to be able
  to snap a picture of my receipt and tear it up&amp;nbsp;right&amp;nbsp;there.&lt;/li&gt;
&lt;li&gt;No &amp;#8220;cloud&amp;#8221; services allowed for unencrypted important documents. I simply don&amp;#8217;t
  trust Google/Dropbox/etc enough to put my bank statements and&amp;nbsp;such&amp;nbsp;there.&lt;/li&gt;
&lt;li&gt;Files need to be backed up securely in case my apartment&amp;nbsp;burns&amp;nbsp;down.&lt;/li&gt;
&lt;li&gt;The entire process needs to be automated as much as possible, otherwise I&amp;#8217;ll get
  lazy and not&amp;nbsp;scan&amp;nbsp;things.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#8217;s taken me a while, but I&amp;#8217;ve finally got a system I&amp;#8217;m happy with. This post will
describe each part and how they fit together. The total cost is about $220, $160 of
which is for a&amp;nbsp;physical&amp;nbsp;scanner.&lt;/p&gt;
&lt;p&gt;Note: I use &lt;span class="caps"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/span&gt; X and an iPhone, so this post will focus on that platform. However,
the important pieces of software will run on Windows and I&amp;#8217;m sure there are
Windows/Android equivalents to the&amp;nbsp;other&amp;nbsp;pieces.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#scanning-at-home"&gt;Scanning&amp;nbsp;at&amp;nbsp;Home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#scanning-on-the-go"&gt;Scanning on&amp;nbsp;the&amp;nbsp;Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ocring-scanned-documents"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ing&amp;nbsp;Scanned&amp;nbsp;Documents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#gluing-everything-together"&gt;Gluing&amp;nbsp;Everything&amp;nbsp;Together&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#backing-up"&gt;Backing&amp;nbsp;Up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#destroying-the-originals"&gt;Destroying&amp;nbsp;the&amp;nbsp;Originals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#summary"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="scanning-at-home"&gt;Scanning&amp;nbsp;at&amp;nbsp;Home&lt;/h2&gt;
&lt;p&gt;The first step to becoming paper-free is obviously scanning your documents.  There
are a lot of scanners out there, some more expensive than others. I eventually
settled on a &lt;a href="http://www.getdoxie.com/"&gt;Doxie&lt;/a&gt;&amp;nbsp;for&amp;nbsp;$160.&lt;/p&gt;
&lt;p&gt;I chose the&amp;nbsp;Doxie&amp;nbsp;because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;#8217;s&amp;nbsp;compact.&lt;/li&gt;
&lt;li&gt;It runs with a single &lt;span class="caps"&gt;&lt;span class="caps"&gt;USB&lt;/span&gt;&lt;/span&gt;&amp;nbsp;cable.&lt;/li&gt;
&lt;li&gt;It&amp;#8217;s&amp;nbsp;cross-platform.&lt;/li&gt;
&lt;li&gt;Its software has a great, polished&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;UI&lt;/span&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;It has a &amp;#8220;multiple-function button&amp;#8221; that lets you control it without&amp;nbsp;the&amp;nbsp;mouse/keyboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first, second, and last points mean that (with a &lt;span class="caps"&gt;&lt;span class="caps"&gt;USB&lt;/span&gt;&lt;/span&gt; extension cable) I can scan
documents while sitting on the couch and watching Netflix, which is critical for lazy
people&amp;nbsp;like&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;I set Doxie to save scans on my Desktop. The scanning process is pretty simple so
I won&amp;#8217;t describe it here. Check out Doxie&amp;#8217;s documentation for&amp;nbsp;more&amp;nbsp;information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When I first received my Doxie and tried to calibrate it, it simply made
a grinding noise and wouldn&amp;#8217;t feed the paper.  I emailed their tech support and
within half an hour I got a response back saying they were shipping me a&amp;nbsp;replacement&amp;nbsp;immediately.&lt;/p&gt;
&lt;p&gt;When I got the replacement it worked like a charm.  Their customer service was so
great that I&amp;#8217;d still recommend the Doxie even though my first one was&amp;nbsp;a&amp;nbsp;dud.&lt;/p&gt;
&lt;h2 id="scanning-on-the-go"&gt;Scanning on&amp;nbsp;the&amp;nbsp;Go&lt;/h2&gt;
&lt;p&gt;As I mentioned before, I want to be able to scan things while out and about with my
iPhone. There are a bunch of iPhone document-scanning apps out there. I settled on
&lt;a href="http://itunes.apple.com/us/app/jotnot-scanner-pro/id307868751?mt=8"&gt;JotNot&lt;/a&gt; for $7 because it has a decent &lt;span class="caps"&gt;&lt;span class="caps"&gt;UI&lt;/span&gt;&lt;/span&gt; and supports&amp;nbsp;multiple-page&amp;nbsp;PDFs.&lt;/p&gt;
&lt;p&gt;JotNot&amp;#8217;s &lt;span class="caps"&gt;&lt;span class="caps"&gt;UI&lt;/span&gt;&lt;/span&gt; is pretty easy to get the hang of so I won&amp;#8217;t go over&amp;nbsp;it&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;Once I finish scanning something I send the &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; to a &lt;a href="http://www.dropbox.com/"&gt;Dropbox&lt;/a&gt; folder&amp;nbsp;called&amp;nbsp;&amp;#8220;JotNot&amp;#8221;. &lt;/p&gt;
&lt;p&gt;I know I said in my requirements that &amp;#8220;cloud&amp;#8221; services weren&amp;#8217;t allowed, but I make an
exception for non-critical things that I&amp;#8217;d be scanning with my phone. I don&amp;#8217;t care if
Dropbox knows how much I spent&amp;nbsp;on&amp;nbsp;dinner.&lt;/p&gt;
&lt;h2 id="ocring-scanned-documents"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ing&amp;nbsp;Scanned&amp;nbsp;Documents&lt;/h2&gt;
&lt;p&gt;The next step is to run the scanned PDFs through an &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt; program so they can be
searched&amp;nbsp;with&amp;nbsp;Spotlight.&lt;/p&gt;
&lt;p&gt;I looked at a lot of &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt; software and finally settled on &lt;a href="http://solutions.weblite.ca/pdfocrx/"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt; X&lt;/a&gt; for $30. It
has a simple interface, does a pretty good job at &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ing, has a free version so
I could try it out, and&amp;nbsp;is&amp;nbsp;cross-platform.&lt;/p&gt;
&lt;p&gt;Using it is simple: you drag a &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; onto the app and select your desired settings
(make sure to choose &amp;#8220;searchable &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt;&amp;#8221; as the output format). The app will think for
a while and then create a new &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; next to the old one with the searchable&amp;nbsp;text&amp;nbsp;embedded.&lt;/p&gt;
&lt;p&gt;Once you&amp;#8217;ve done this once you should go into the preferences and change it to
non-interactive mode so that it won&amp;#8217;t prompt you for the settings every time you&amp;nbsp;use&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="gluing-everything-together"&gt;Gluing&amp;nbsp;Everything&amp;nbsp;Together&lt;/h2&gt;
&lt;p&gt;So far we&amp;#8217;ve got two folders with scanned PDFs and a method for &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ing them. The
next step is to automate&amp;nbsp;the&amp;nbsp;process.&lt;/p&gt;
&lt;p&gt;I use an app called &lt;a href="http://www.noodlesoft.com/hazel.php"&gt;Hazel&lt;/a&gt; to do this. It&amp;#8217;s $21 for a license and well worth it.
We&amp;#8217;ll set up four rules to make our&amp;nbsp;lives&amp;nbsp;easier.&lt;/p&gt;
&lt;p&gt;Before we start we need to create two folders somewhere (you can name them whatever&amp;nbsp;you&amp;nbsp;like):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pending &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;: A folder to hold documents that are waiting to be&amp;nbsp;&lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ed.&lt;/li&gt;
&lt;li&gt;Dead Trees: A folder to hold the final, &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ed versions of&amp;nbsp;our&amp;nbsp;documents.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first rule watches the Desktop for scans from Doxie.  Any files placed on the
Desktop whose name starts with &amp;#8220;Doxie Doc&amp;#8221; will be renamed to include the current
date and time, and then moved to the &amp;#8220;Pending &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8221;&amp;nbsp;folder.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rule 1 Screenshot" src="/media/images/blog/2011/05/rules-1-doxie.png" title="Rule 1" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;: you&amp;#8217;ll need to click the &lt;code&gt;date created&lt;/code&gt; bubble and then &amp;#8220;Edit Date&amp;#8221; to get
the time as well as the date into&amp;nbsp;the&amp;nbsp;filename.&lt;/p&gt;
&lt;p&gt;The second rule watches the &amp;#8220;JotNot&amp;#8221; folder for scans from the iPhone app.  Any PDFs
that appear in here (i.e. that are synced down from Dropbox) will be moved to the
&amp;#8220;Pending &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8221; folder.  We don&amp;#8217;t need to rename them like we did with the Doxie scans
because JotNot already includes the date and time of scans in the filenames&amp;nbsp;by&amp;nbsp;default.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rule 2 Screenshot" src="/media/images/blog/2011/05/rules-2-jotnot.png" title="Rule 2" /&gt;&lt;/p&gt;
&lt;p&gt;Now that we&amp;#8217;ve got all of our scans going into the same folder (with unique names) we
can set up a rule to &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt; them.  The third rule watches the &amp;#8220;Pending &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8221; folder for
PDFs.  When a &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; lands in the folder it will be moved to its final destination
folder (&amp;#8220;Dead Trees&amp;#8221; in my case) and then opened in &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt; X.  Because I&amp;#8217;ve put &lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt;
&lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt; X in non-interactive mode the files will automatically be &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ed without any
intervention&amp;nbsp;from&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rule 3 Screenshot" src="/media/images/blog/2011/05/rules-3-ocr.png" title="Rule 3" /&gt;&lt;/p&gt;
&lt;p&gt;The fourth and final rule watches for the &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ed copies of our scans and runs
a script to move the originals to the trash once the searchable versions are ready.
It doesn&amp;#8217;t delete the files completely because I want a safety net in case something&amp;nbsp;goes&amp;nbsp;wrong.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rule 4 Screenshot" src="/media/images/blog/2011/05/rules-4-clean.png" title="Rule 4" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; make sure you change the Shell to &lt;code&gt;/usr/bin/python&lt;/code&gt;.  Here&amp;#8217;s the text of
the script so you can copy and&amp;nbsp;paste&amp;nbsp;it:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;RM_CMD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&amp;quot;osascript -e &amp;#39;tell app &amp;quot;&lt;/span&gt;&lt;span class="n"&gt;Finder&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; to move the &lt;span class="caps"&gt;&lt;span class="caps"&gt;POSIX&lt;/span&gt;&lt;/span&gt; file &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; to trash&amp;#39;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;old_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RM_CMD&lt;/span&gt; &lt;span class="nv"&gt;%&lt;/span&gt; &lt;span class="nv"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Once these four rules are in place we can simply scan a document with Doxie or JotNot
and it will automatically be &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;#8217;ed and placed in our &amp;#8220;Dead Trees&amp;#8221; folder, with no
intervention&amp;nbsp;from&amp;nbsp;us!&lt;/p&gt;
&lt;h2 id="backing-up"&gt;Backing&amp;nbsp;Up&lt;/h2&gt;
&lt;p&gt;A while ago I was using Mozy for full backups. Recently they changed their pricing so
it was no longer unlimited. When that happened I switched to &lt;a href="http://www.backblaze.com/"&gt;Backblaze&lt;/a&gt; and
couldn&amp;#8217;t&amp;nbsp;be&amp;nbsp;happier.&lt;/p&gt;
&lt;p&gt;Backblaze&amp;#8217;s &lt;span class="caps"&gt;&lt;span class="caps"&gt;UI&lt;/span&gt;&lt;/span&gt; is leaps and bounds above Mozy&amp;#8217;s, and they offer an option to generate
a secure encryption key for encrypting your backups. I highly recommend this, but be
sure to have a few copies of your key because you&amp;#8217;ll need it to restore&amp;nbsp;your&amp;nbsp;backups.&lt;/p&gt;
&lt;p&gt;Backblaze is also only $5 per month (less if you pay for a year in advance) for
unlimited backups which is definitely a bargain.  As a bonus, they just released
a &lt;a href="http://blog.backblaze.com/2011/05/23/lost-your-computer-get-it-back-backblaze-launches-locate-my-computer/"&gt;&amp;#8220;find my computer&amp;#8221; feature&lt;/a&gt; that&amp;#8217;s kind of like a lightweight
version of &lt;a href="http://www.orbicule.com/undercover/"&gt;Undercover&lt;/a&gt;, so it&amp;#8217;s an even&amp;nbsp;better&amp;nbsp;deal.&lt;/p&gt;
&lt;h2 id="destroying-the-originals"&gt;Destroying&amp;nbsp;the&amp;nbsp;Originals&lt;/h2&gt;
&lt;p&gt;Once the documents are scanned and backed up it&amp;#8217;s time to destroy the physical paper.
If you live in a rural area you could burn them&amp;nbsp;for&amp;nbsp;free.&lt;/p&gt;
&lt;p&gt;Those of us that can&amp;#8217;t start random fires need a paper shredder. I use a shredder
I picked up a long time ago &amp;#8212; any crosscut shredder will do&amp;nbsp;the&amp;nbsp;job.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;After all of this I&amp;#8217;ve now got a mostly-automated system that lets me go paper-free.
The&amp;nbsp;costs&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doxie&amp;nbsp;Scanner:&amp;nbsp;$160&lt;/li&gt;
&lt;li&gt;JotNot:&amp;nbsp;$7&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;&lt;span class="caps"&gt;OCR&lt;/span&gt;&lt;/span&gt;&amp;nbsp;X:&amp;nbsp;$30&lt;/li&gt;
&lt;li&gt;Hazel:&amp;nbsp;$21&lt;/li&gt;
&lt;li&gt;Backblaze: $5&amp;nbsp;per&amp;nbsp;month&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For me the $218 initial cost is worth it.  Now I can search all of my paper in
a instant and my apartment is much less cluttered. If you have the money to spare I&amp;#8217;d
definitely consider&amp;nbsp;trying&amp;nbsp;it.&lt;/p&gt;
                
            
                    
                &lt;img src="http://feeds.feedburner.com/~r/stevelosh/~4/ou8uE_a_qao" height="1" width="1"/&gt;</content><feedburner:origLink>http://stevelosh.com/blog/2011/05/paper-free/</feedburner:origLink></entry><entry><title type="html">On Learning and Teaching</title><author><name>Steve Losh</name></author><link href="http://feedproxy.google.com/~r/stevelosh/~3/e____HDrAT4/" /><updated>2011-05-22T17:00:00Z</updated><published>2011-05-22T17:00:00Z</published><id>http://stevelosh.com/blog/2011/05/on-learning-and-teaching/</id><content type="html">
                    
                        
                
                    &lt;p&gt;I recently read a blog entry by &lt;a href="http://www.cis.gvsu.edu/~kurmasz/"&gt;Zack Kurmas&lt;/a&gt; called &amp;#8220;&lt;a href="http://spin.atomicobject.com/2011/05/17/the-deep-end-of-the-pool/"&gt;The deep end of the
pool&lt;/a&gt;&amp;#8220;.  In it he talks about why he thinks some students succeed in introductory
Computer Science classes while others fail.  If you&amp;#8217;re a programmer and haven&amp;#8217;t read
it, stop right now and&amp;nbsp;read&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In a nutshell: I think he&amp;#8217;s pretty much correct.  I wanted to write this post because
I think I have a solution to the problem that might work, at least for&amp;nbsp;some&amp;nbsp;people.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#background"&gt;Background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#similarities-in-dancing-and-programming"&gt;Similarities in Dancing and Programming&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#programming-syntax"&gt;Programming&amp;nbsp;Syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dancing-footwork"&gt;Dancing&amp;nbsp;Footwork&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#footwork-is-syntax-and-syntax-is-footwork"&gt;Footwork is Syntax and Syntax&amp;nbsp;is&amp;nbsp;Footwork&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#plateaus-in-learning"&gt;Plateaus&amp;nbsp;in&amp;nbsp;Learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#getting-through-plateaus"&gt;Getting Through Plateaus&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-lie-and-the-solution"&gt;The Lie and&amp;nbsp;the&amp;nbsp;Solution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-problem-with-the-solution"&gt;The Problem with&amp;nbsp;the&amp;nbsp;Solution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-solution-to-the-problem-with-the-solution"&gt;The Solution to the Problem with&amp;nbsp;the&amp;nbsp;Solution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m a computer programmer by trade.  I went to &lt;a href="http://rit.edu/"&gt;&lt;span class="caps"&gt;&lt;span class="caps"&gt;RIT&lt;/span&gt;&lt;/span&gt;&lt;/a&gt; for five years to  get my
degree in Computer Science and before that I was working with toy programs during&amp;nbsp;high&amp;nbsp;school.&lt;/p&gt;
&lt;p&gt;While I was at &lt;span class="caps"&gt;&lt;span class="caps"&gt;RIT&lt;/span&gt;&lt;/span&gt; I started going to &lt;a href="http://www.rit.edu/sg/swing/"&gt;Swing Dance Club&lt;/a&gt;.  I learned to swing dance
there about eight years ago.  A few years later I started I began teaching Swing
Dance Club with some of my friends, and about a year ago I started teaching Blues
Dancing with &lt;a href="http://ladyluckblues.com/"&gt;Lady Luck Blues&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have a couple of other semi-serious hobbies that I&amp;#8217;ve taken classes in as well:
photography and&amp;nbsp;bass&amp;nbsp;playing.&lt;/p&gt;
&lt;p&gt;To summarize: I&amp;#8217;ve had experience in a bunch of different areas, and I&amp;#8217;ve been
teaching one of them for&amp;nbsp;a&amp;nbsp;while.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; one thing I&amp;#8217;ve never studied is teaching itself, so feel free to take
everything I say about it with a large grain of salt.  I haven&amp;#8217;t done any hard
research and I could be&amp;nbsp;completely&amp;nbsp;wrong.&lt;/p&gt;
&lt;h2 id="similarities-in-dancing-and-programming"&gt;Similarities in Dancing&amp;nbsp;and&amp;nbsp;Programming&lt;/h2&gt;
&lt;p&gt;In his article, Zack talks about programming syntax being a stumbling block for
beginners.  I think he&amp;#8217;s very right and I think there&amp;#8217;s a similar concept&amp;nbsp;in&amp;nbsp;dancing.&lt;/p&gt;
&lt;h3 id="programming-syntax"&gt;Programming&amp;nbsp;Syntax&lt;/h3&gt;
&lt;p&gt;If you&amp;#8217;re a programmer you can skip&amp;nbsp;this&amp;nbsp;section.&lt;/p&gt;
&lt;p&gt;For the non-programmers: &amp;#8220;syntax&amp;#8221; is the combination of letters, numbers and symbols
that you write to create computer programs. For example, look at the following two
bits&amp;nbsp;of&amp;nbsp;text:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice the difference?  The first snippet says &lt;code&gt;"Hello"&lt;/code&gt; while the second says
&lt;code&gt;'Hello"&lt;/code&gt;.  Notice how the first uses two double quotes (&lt;code&gt;"&lt;/code&gt;) while the second uses
one double and one single quote (&lt;code&gt;'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The first bit of code will work, but the second one won&amp;#8217;t.  Little differences like
these might not seem like a big deal, but to a computer they&amp;#8217;re as different as
&lt;code&gt;abcdefg&lt;/code&gt; and &lt;code&gt;o34%^8@@.com&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="dancing-footwork"&gt;Dancing&amp;nbsp;Footwork&lt;/h3&gt;
&lt;p&gt;If you&amp;#8217;re a dancer you can skip&amp;nbsp;this&amp;nbsp;section.&lt;/p&gt;
&lt;p&gt;For the non-dancers: &amp;#8220;footwork&amp;#8221; is the pattern of steps you make when you dance.  For
example, in Lindy Hop (a style of swing dancing) the basic footwork looks&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="err"&gt;Beats:&lt;/span&gt;   &lt;span class="err"&gt;1&lt;/span&gt;   &lt;span class="err"&gt;2&lt;/span&gt;   &lt;span class="err"&gt;3&lt;/span&gt;   &lt;span class="err"&gt;4&lt;/span&gt;   &lt;span class="err"&gt;5&lt;/span&gt;   &lt;span class="err"&gt;6&lt;/span&gt;   &lt;span class="err"&gt;7&lt;/span&gt;   &lt;span class="err"&gt;8&lt;/span&gt;
&lt;span class="err"&gt;Steps:&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;  &lt;span class="err"&gt;xx&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;  &lt;span class="err"&gt;xx&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For East Coast Swing, the basic footwork looks&amp;nbsp;like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="err"&gt;Beats:&lt;/span&gt;   &lt;span class="err"&gt;1&lt;/span&gt;   &lt;span class="err"&gt;2&lt;/span&gt;   &lt;span class="err"&gt;3&lt;/span&gt;   &lt;span class="err"&gt;4&lt;/span&gt;   &lt;span class="err"&gt;5&lt;/span&gt;   &lt;span class="err"&gt;6&lt;/span&gt;
&lt;span class="err"&gt;Steps:&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;  &lt;span class="err"&gt;xx&lt;/span&gt;   &lt;span class="err"&gt;x&lt;/span&gt;  &lt;span class="err"&gt;xx&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice the differences?  Lindy Hop footwork is based on an eight beat pattern, while
East Coast footwork is based on six beats.  The patterns also have the &amp;#8220;extra steps&amp;#8221;
in&amp;nbsp;different&amp;nbsp;places.&lt;/p&gt;
&lt;p&gt;When you&amp;#8217;re dancing with a partner you both need to be doing the same footwork,
otherwise you can&amp;#8217;t really&amp;nbsp;dance&amp;nbsp;together.&lt;/p&gt;
&lt;h3 id="footwork-is-syntax-and-syntax-is-footwork"&gt;Footwork is Syntax and Syntax&amp;nbsp;is&amp;nbsp;Footwork&lt;/h3&gt;
&lt;p&gt;When you&amp;#8217;re learning both programming and dancing there are lots of deep, high-level
concepts you can tackle.  For programming you have things like object orientation,
recursion, and &lt;span class="caps"&gt;&lt;span class="caps"&gt;API&lt;/span&gt;&lt;/span&gt; design.  In dancing you have things like musicality, listening to
your follower, and&amp;nbsp;negative&amp;nbsp;space.&lt;/p&gt;
&lt;p&gt;However: in my experience teaching dancing you need to learn and understand the
footwork before you can learn the higher concepts.  If you&amp;#8217;re still thinking about
where to put your feet when you&amp;#8217;re dancing you won&amp;#8217;t have the spare brainpower to
think about those&amp;nbsp;other&amp;nbsp;concepts.&lt;/p&gt;
&lt;p&gt;In Zack&amp;#8217;s post he says the same thing about programming.  If you&amp;#8217;re still struggling
with understanding the syntax of programs you can&amp;#8217;t simultaneously pay attention to
the higher concepts that are being taught. I&amp;#8217;ve never taught programming but my
personal observations of programmers make me think&amp;nbsp;he&amp;#8217;s&amp;nbsp;right.&lt;/p&gt;
&lt;p&gt;To be clear: there aren&amp;#8217;t just two levels (low and high).  There are many
levels/layers, each of which builds on the last.  Also: the layers are not linear &amp;#8212;
learning concept A might allow you to learn either B&amp;nbsp;or&amp;nbsp;C.&lt;/p&gt;
&lt;p&gt;However: the same idea applies most of the time.  You need to learn and internalize
lower levels before moving on to the&amp;nbsp;higher&amp;nbsp;ones.&lt;/p&gt;
&lt;h2 id="plateaus-in-learning"&gt;Plateaus&amp;nbsp;in&amp;nbsp;Learning&lt;/h2&gt;
&lt;p&gt;One thing I&amp;#8217;ve noticed while learning dancing, programming, photography, and other
skills is that my learning seems to progress quickly at times and slowly&amp;nbsp;at&amp;nbsp;others.&lt;/p&gt;
&lt;p&gt;When I&amp;#8217;m learning quickly everything is great.  I feel good about myself and my
abilities and I throw myself into learning as much as&amp;nbsp;I&amp;nbsp;can.&lt;/p&gt;
&lt;p&gt;On the other hand when I&amp;#8217;m learning slowly (hitting a &amp;#8220;plateau&amp;#8221;) I often get
frustrated or discouraged.  Nothing I try seems to speed up my ascent out of these
plateaus.  It seems like the only thing that works is time and practice of what
I&amp;nbsp;already&amp;nbsp;know.&lt;/p&gt;
&lt;p&gt;I think the reason for this is that once I learn a bunch of new concepts my brain
needs some time to process them.  I can learn the concepts and use them if
I concentrate, but it&amp;#8217;s not effortless.  I think the reason I finally start learning
quickly again after some time is that I&amp;#8217;ve internalized the previous concepts and now
I can move on to more difficult ones which build on the&amp;nbsp;previous&amp;nbsp;ones.&lt;/p&gt;
&lt;p&gt;The first plateau of programming is the syntax and the first plateau of dancing is
footwork.  The bad part about this is that dancing when you only know footwork or
programming when you only know syntax isn&amp;#8217;t much fun.  You can&amp;#8217;t do all of the most
interesting things that make these skills&amp;nbsp;so&amp;nbsp;rewarding.&lt;/p&gt;
&lt;h2 id="getting-through-plateaus"&gt;Getting&amp;nbsp;Through&amp;nbsp;Plateaus&lt;/h2&gt;
&lt;p&gt;In my own experience the hardest part about teaching is getting students through the
plateaus.  Because it just takes time and practice to process what they&amp;#8217;ve already
learned there&amp;#8217;s not much I can do to help them.  Students take more advanced classes
but don&amp;#8217;t really grab them until they&amp;#8217;re fluent in the things they&amp;#8217;ve&amp;nbsp;previously&amp;nbsp;learned.&lt;/p&gt;
&lt;p&gt;As a teacher, this is incredibly frustrating.  I want to teach my students what
I know, but I know they won&amp;#8217;t absorb it until they&amp;#8217;ve absorbed the&amp;nbsp;previous&amp;nbsp;lessons.&lt;/p&gt;
&lt;h3 id="the-lie-and-the-solution"&gt;The Lie and&amp;nbsp;the&amp;nbsp;Solution&lt;/h3&gt;
&lt;p&gt;Here&amp;#8217;s the kicker, and what I think might provide the solution to this problem: &lt;strong&gt;I
lied when I said that the only things that help me through plateaus are time&amp;nbsp;and&amp;nbsp;practice.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s one more thing that helps me get through plateaus: &lt;strong&gt;connecting the concepts
I&amp;#8217;m trying to internalize with concepts from other skills that I&amp;nbsp;already&amp;nbsp;know.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An example of this that I&amp;#8217;ve written about before is &lt;a href="http://stevelosh.com/blog/2008/08/negative-space-dancing/"&gt;negative space&lt;/a&gt;.  When I was
learning blues dancing I realized that &amp;#8220;stopping your movement&amp;#8221; was simply
a different expression of the concept of &amp;#8220;negative space&amp;#8221;&amp;nbsp;in&amp;nbsp;photography.&lt;/p&gt;
&lt;p&gt;I had already struggled through internalizing the concept of negative space in
photography, so when I connected this with dancing it helped make things &amp;#8220;click&amp;#8221;
together and made the&amp;nbsp;plateau&amp;nbsp;shorter.&lt;/p&gt;
&lt;h3 id="the-problem-with-the-solution"&gt;The Problem with&amp;nbsp;the&amp;nbsp;Solution&lt;/h3&gt;
&lt;p&gt;In my own life this solution&amp;nbsp;is&amp;nbsp;great.&lt;/p&gt;
&lt;p&gt;I have a bunch of hobbies and all of them mesh together in different ways.  Sometimes
I can connect what I know in one to what I know in another and get through these
plateaus more quickly.  When I can&amp;#8217;t do that I can focus on one of my other hobbies
while practicing another, which reduces the frustration of not&amp;nbsp;progressing&amp;nbsp;faster.&lt;/p&gt;
&lt;p&gt;When teaching students, however, this isn&amp;#8217;t a trick I can&amp;nbsp;use&amp;nbsp;effectively.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t really tell a student: &amp;#8220;you need to find something else to focus on for
a while while you internalize what I&amp;#8217;ve taught you.&amp;#8221;  If they want to learn dancing,
they&amp;#8217;ll feel like taking more dance classes is the way to go.  They&amp;#8217;re paying me to
teach them so I obviously have to try to&amp;nbsp;teach&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;I sometimes try to teach the &amp;#8220;analogies&amp;#8221; between skills that have helped me.
Sometimes this works well &amp;#8212; the &amp;#8220;Negative Space in Dancing&amp;#8221; class that my partner
and I have taught seems to go over well with most of&amp;nbsp;the&amp;nbsp;students.&lt;/p&gt;
&lt;p&gt;The problem is that everyone has different skills.  Trying to connect a concept in
programming to a concept in photography is futile if the student doesn&amp;#8217;t understand
the&amp;nbsp;photographic&amp;nbsp;concept.&lt;/p&gt;
&lt;h3 id="the-solution-to-the-problem-with-the-solution"&gt;The Solution to the Problem with&amp;nbsp;the&amp;nbsp;Solution&lt;/h3&gt;
&lt;p&gt;Disclaimer: once again, I&amp;#8217;ve never studied teaching itself, so this might not be
a new idea and/or I could be completely wrong&amp;nbsp;about&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve thought a long time about a way to fix this.  For students that are simply
taking a class here and there with me, I haven&amp;#8217;t found a&amp;nbsp;good&amp;nbsp;solution.&lt;/p&gt;
&lt;p&gt;However, for students enrolled in a longer series of classes, like college students
studying a major, I think there might be a way to use this idea (linking concepts
from disparate skills) to help&amp;nbsp;them&amp;nbsp;learn.&lt;/p&gt;
&lt;p&gt;When I went to &lt;span class="caps"&gt;&lt;span class="caps"&gt;RIT&lt;/span&gt;&lt;/span&gt; I didn&amp;#8217;t just take Computer Science classes.  I had to take a few
science classes, a few math classes, a few history classes, and so on.  I believe
other colleges work on the same system: you take many classes in your major and one
or two classes in everything else.  The idea is that you become a&amp;nbsp;&amp;#8220;well-rounded&amp;#8221;&amp;nbsp;person.&lt;/p&gt;
&lt;p&gt;The bad part about this process is that one or two classes is not enough to get to
one of these &amp;#8220;plateaus&amp;#8221;, struggle through it, and continue on.  Therefore you don&amp;#8217;t
have time to internalize the skills you learn, which means that you can&amp;#8217;t connect
them to concepts in&amp;nbsp;your&amp;nbsp;major.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m also not sure how effective these random classes are at making students
&amp;#8220;well-rounded&amp;#8221;.  If you don&amp;#8217;t get far enough to internalize the skills you learn in
a class are they really useful to you once you&amp;#8217;re done with&amp;nbsp;those&amp;nbsp;classes?&lt;/p&gt;
&lt;p&gt;I think the way around this is to ditch the &amp;#8220;one major and a tiny bit of everything
else&amp;#8221; process and move to a &amp;#8220;one major and two or three&amp;nbsp;minors&amp;#8221;&amp;nbsp;process.&lt;/p&gt;
&lt;p&gt;This will help students past the plateaus that discourage them, because they&amp;#8217;ll have
learned enough in their &amp;#8220;minors&amp;#8221; to be able to connect concepts to those in their
major.  Teachers might not be able to teach specific analogies because the students
will all have different minors, but each student will be able to connect the concepts
in their majors on their own to whatever they happen to know from&amp;nbsp;their&amp;nbsp;minors.&lt;/p&gt;
&lt;p&gt;This process also has another benefit: students will leave college retaining more
skills that they would have otherwise.  A programming student could leave college
with a real, effective amount of knowledge in music; a writing major could leave with
a real, effective amount of knowledge in biology, and&amp;nbsp;so&amp;nbsp;on.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m not naive enough to think that any college is going to change their entire
curriculum based on some random guy&amp;#8217;s&amp;nbsp;blog&amp;nbsp;post.&lt;/p&gt;
&lt;p&gt;My intended audience for this post is not colleges, but people that want to learn
skills faster and more effectively.  I know that there are a lot of people out there
hungry for knowledge and I hope that some of them can benefit from the ideas I&amp;#8217;ve
given, learn faster, and make the world a&amp;nbsp;better&amp;nbsp;place.&lt;/p&gt;
                
            
                    
                &lt;img src="http://feeds.feedburner.com/~r/stevelosh/~4/e____HDrAT4" height="1" width="1"/&gt;</content><feedburner:origLink>http://stevelosh.com/blog/2011/05/on-learning-and-teaching/</feedburner:origLink></entry></feed>

