<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" xml:lang="en" xml:base="http://blog.bstpierre.org/wp-atom.php">
	<title type="text">The Daily Build</title>
	<subtitle type="text">Software Development, version 3.0</subtitle>

	<updated>2009-08-25T22:46:16Z</updated>
	<generator uri="http://wordpress.org/" version="2.8.5">WordPress</generator>

	<link rel="alternate" type="text/html" href="http://blog.bstpierre.org" />
	<id>http://blog.bstpierre.org/feed/atom</id>
	

			<link rel="self" href="http://feeds.feedburner.com/TheDailyBuild" type="application/atom+xml" /><feedburner:emailServiceId>TheDailyBuild</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[3 Easy Ways to Stick to a Coding Standard]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/xdT-Kk-GTWY/3-easy-ways-to-stick-to-a-coding-standard" />
		<id>http://blog.bstpierre.org/?p=192</id>
		<updated>2009-08-25T19:26:27Z</updated>
		<published>2009-08-25T18:40:58Z</published>
		<category scheme="http://blog.bstpierre.org" term="python" /><category scheme="http://blog.bstpierre.org" term="codereview" /><category scheme="http://blog.bstpierre.org" term="software-engineering" /><category scheme="http://blog.bstpierre.org" term="tool" />		<summary type="html"><![CDATA[
		
		
		
		When you&#8217;re writing python, you don&#8217;t need a lot of debate over the minutiae of most coding standards. PEP 8 does that for you. Even better, there are some tools that make it really easy to stick to the standard.
Why do this? Well, for one thing it makes code reviews easier when everyone follows the [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'>Five Days to a Django Web App: Day Three, Coding</a> <small> Thanks for coming back for...</small></li><li><a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'>Python Exception Handling: Cleanup and Reraise</a> <small> I&#8217;ve had this code around...</small></li><li><a href='http://blog.bstpierre.org/makefiles-are-software-too' rel='bookmark' title='Permanent Link: Makefiles are Software Too'>Makefiles are Software Too</a> <small> This post was inspired by...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "3+Easy+Ways+to+Stick+to+a+Coding+Standard";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;When you&amp;#8217;re writing python, you don&amp;#8217;t need a lot of debate over the minutiae of most coding standards. &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;PEP 8&lt;/a&gt; does that for you. Even better, there are some tools that make it really easy to stick to the standard.&lt;/p&gt;
&lt;p&gt;Why do this? Well, for one thing it makes code reviews easier when everyone follows the same conventions. It also makes maintenance easier.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://github.com/cburroughs/pep8.py/tree/master"&gt;pep8.py&lt;/a&gt; is a style checker that enforces the rules of PEP 8. The &amp;#8220;official home&amp;#8221; (?) &lt;a href="http://svn.browsershots.org/trunk/devtools/pep8/pep8.py"&gt;at browsershots.org &lt;/a&gt;was dead as I was writing this. (Thanks GitHub!) Run pep8.py on your code and it will tell you where you&amp;#8217;ve drifted from the standard.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.logilab.org/857"&gt;pylint&lt;/a&gt; is lint for python. It provides more functionality than pep8.py, but is not a strict superset. (pep8.py is pickier about whitespace issues.) Pylint is also configurable to enforce various naming rules. Like most lints, the SNR is pretty low, but you can turn off most of the noise and get a reasonable signal for the things you want to check.&lt;/li&gt;
&lt;li&gt;Subversion (as well as most other tools) can be configured to run a script every time you check in code. Run one or both of the above tools in the pre-commit hook and bad code will be rejected. (I&amp;#8217;d be wary of doing this with pylint unless you&amp;#8217;ve got the categories of &amp;#8220;noisy&amp;#8221; warnings turned off.) I don&amp;#8217;t have anything cookbook for this yet, but I&amp;#8217;m working on it&amp;#8230;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'&gt;Five Days to a Django Web App: Day Three, Coding&lt;/a&gt; &lt;small&gt; Thanks for coming back for...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'&gt;Python Exception Handling: Cleanup and Reraise&lt;/a&gt; &lt;small&gt; I&amp;#8217;ve had this code around...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/makefiles-are-software-too' rel='bookmark' title='Permanent Link: Makefiles are Software Too'&gt;Makefiles are Software Too&lt;/a&gt; &lt;small&gt; This post was inspired by...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=xdT-Kk-GTWY:oxpMhZokStY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=xdT-Kk-GTWY:oxpMhZokStY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=xdT-Kk-GTWY:oxpMhZokStY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=xdT-Kk-GTWY:oxpMhZokStY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=xdT-Kk-GTWY:oxpMhZokStY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/xdT-Kk-GTWY" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard#comments" thr:count="1" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard/feed/atom" thr:count="1" />
		<thr:total>1</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Hassle Free Way to Kill Sudo&#8217;d Jobs]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/KZLnCjgKZEI/hassle-free-way-to-kill-sudod-jobs" />
		<id>http://blog.bstpierre.org/?p=189</id>
		<updated>2009-08-12T15:53:10Z</updated>
		<published>2009-08-13T00:45:28Z</published>
		<category scheme="http://blog.bstpierre.org" term="news" /><category scheme="http://blog.bstpierre.org" term="bash" /><category scheme="http://blog.bstpierre.org" term="linux" /><category scheme="http://blog.bstpierre.org" term="script" /><category scheme="http://blog.bstpierre.org" term="tip" />		<summary type="html"><![CDATA[
		
		
		
		Every now and then I have to run a foreground job under sudo that doesn&#8217;t want to die when I hit ^C. Then it&#8217;s a hassle to ^Z, get the pid of the sudo job, and sudo kill that pid.
So I wrote a little script (or a template for scripts) that runs the sudo job [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'>Python Exception Handling: Cleanup and Reraise</a> <small> I&#8217;ve had this code around...</small></li><li><a href='http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls' rel='bookmark' title='Permanent Link: Using Python&#8217;s ctypes to Call Into C Libraries'>Using Python&#8217;s ctypes to Call Into C Libraries</a> <small>The ctypes module makes loading and...</small></li><li><a href='http://blog.bstpierre.org/makefiles-are-software-too' rel='bookmark' title='Permanent Link: Makefiles are Software Too'>Makefiles are Software Too</a> <small> This post was inspired by...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/hassle-free-way-to-kill-sudod-jobs">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/hassle-free-way-to-kill-sudod-jobs";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Hassle+Free+Way+to+Kill+Sudo%26%238217%3Bd+Jobs";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;Every now and then I have to run a foreground job under sudo that doesn&amp;#8217;t want to die when I hit ^C. Then it&amp;#8217;s a hassle to ^Z, get the pid of the sudo job, and sudo kill that pid.&lt;/p&gt;
&lt;p&gt;So I wrote a little script (or a template for scripts) that runs the sudo job in the background (but preserves stdout/stderr) and relies on bash to clean up the job when you ^C the script. Only gotcha with this is that you may have to retype your sudo password when you ^C if your authentication has timed out by the time you get around to killing it.&lt;/p&gt;
&lt;pre&gt;#!/bin/bash

function cleanup()
{
    sudo kill $job_pid
    wait $job_pid
    exit 0
}

trap cleanup SIGTERM
trap cleanup SIGINT

sudo long_running_foreground_process &amp;amp;
job_pid=$!
wait&lt;/pre&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'&gt;Python Exception Handling: Cleanup and Reraise&lt;/a&gt; &lt;small&gt; I&amp;#8217;ve had this code around...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls' rel='bookmark' title='Permanent Link: Using Python&amp;#8217;s ctypes to Call Into C Libraries'&gt;Using Python&amp;#8217;s ctypes to Call Into C Libraries&lt;/a&gt; &lt;small&gt;The ctypes module makes loading and...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/makefiles-are-software-too' rel='bookmark' title='Permanent Link: Makefiles are Software Too'&gt;Makefiles are Software Too&lt;/a&gt; &lt;small&gt; This post was inspired by...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=KZLnCjgKZEI:wH4X10wNGMk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=KZLnCjgKZEI:wH4X10wNGMk:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=KZLnCjgKZEI:wH4X10wNGMk:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=KZLnCjgKZEI:wH4X10wNGMk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=KZLnCjgKZEI:wH4X10wNGMk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/KZLnCjgKZEI" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/hassle-free-way-to-kill-sudod-jobs#comments" thr:count="1" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/hassle-free-way-to-kill-sudod-jobs/feed/atom" thr:count="1" />
		<thr:total>1</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/hassle-free-way-to-kill-sudod-jobs</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Using Python&#8217;s ctypes to Call Into C Libraries]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/9uGCx0EhNYc/using-pythons-ctypes-to-make-system-calls" />
		<id>http://blog.bstpierre.org/?p=185</id>
		<updated>2009-08-05T16:23:24Z</updated>
		<published>2009-08-05T10:35:55Z</published>
		<category scheme="http://blog.bstpierre.org" term="python" /><category scheme="http://blog.bstpierre.org" term="howto" /><category scheme="http://blog.bstpierre.org" term="linux" /><category scheme="http://blog.bstpierre.org" term="tutorial" />		<summary type="html"><![CDATA[The ctypes module makes loading and calling into a dynamic library incredibly easy.


Related posts:<ol><li><a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'>Python Exception Handling: Cleanup and Reraise</a> <small> I&#8217;ve had this code around...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'>Five Days to a Django Web App: Day Three, Coding</a> <small> Thanks for coming back for...</small></li><li><a href='http://blog.bstpierre.org/jesse-noller-on-python' rel='bookmark' title='Permanent Link: Jesse Noller on Python'>Jesse Noller on Python</a> <small> Jesse Noller has been republishing...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Using+Python%26%238217%3Bs+ctypes+to+Call+Into+C+Libraries";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;The ctypes module makes loading and calling into a dynamic library incredibly easy:&lt;/p&gt;
&lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; from ctypes import CDLL
&amp;gt;&amp;gt;&amp;gt; libc = CDLL('libc.so.6')
&amp;gt;&amp;gt;&amp;gt; print libc.strlen('abcde')
5&lt;/pre&gt;
&lt;p&gt;As with everything else in python, it gets even better when you scratch the surface. In the example above, CDLL returns an object that represents the dynamic library. You can access the functions in that library by attribute access (&amp;#8221;libc.strlen&amp;#8221;) or item access (&amp;#8221;libc['strlen']&amp;#8220;). Both access mechanisms return a callable object.&lt;/p&gt;
&lt;p&gt;This callable object has an &amp;#8220;errcheck&amp;#8221; attribute that can be assigned a callable. We can use this for error-checking our calls into the library. Let&amp;#8217;s write a simple version of the &amp;#8220;kill&amp;#8221; command that uses the kill(2) system call.&lt;/p&gt;
&lt;pre&gt;import sys
from ctypes import *

# Load the library.
libc = CDLL('libc.so.6')

# Our error checking function. This will receive the
# return value of the library function, the function that
# was called, and the arguments passed to the function as a
# tuple.
def kill_errcheck(retval, func, funcargs):
    '''Check for error -- retval == -1.'''
    if retval &amp;lt; 0:
        raise Exception('kill%s failed' % (funcargs, ))
    return True

# Get the kill function from the standard library.
kill = libc.kill

# Set the error checker for kill().
kill.errcheck = kill_errcheck

# Pass the command line argument as a pid to kill, with
# SIGSEGV (11).
pid = int(sys.argv[1])
kill(pid, 11)&lt;/pre&gt;
&lt;p&gt;Save this as kill.py. Then, in your shell, try something like this:&lt;/p&gt;
&lt;pre&gt;# Notice that the 3401 is the pid of the process
# we're putting into the background. Yours will
# be different.
bash$ sleep 120&amp;amp;
[1] 3401
bash$ python kill.py 3401
[1]+ Segmentation Fault         sleep 120
bash$ python kill.py 3401
Traceback (most recent call last):
  File "kill.py", line 17, in
    kill(pid, 11)
  File "kill.py", line 10, in kill_errcheck
    raise Exception('kill%s failed' % (funcargs, ))
Exception: kill(3401, 11) failed&lt;/pre&gt;
&lt;p&gt;At line 4 of the output we run sleep in the background. At line 5 we learn the pid of this process. At line 6 we run our kill program, giving it the pid we just spawned, and we see the notification from bash that the process was killed (with signal 11, segmentation fault). At line 8 we run our kill program again on pid 3401, but it doesn&amp;#8217;t exist, the kill system call returns -1, and our error checker raises an exception when it detects the system call failure.&lt;/p&gt;
&lt;p&gt;But wait, there&amp;#8217;s more&amp;#8230; I&amp;#8217;m working on a follow up post that combines ctypes.Structure with calls into a linux system call.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'&gt;Python Exception Handling: Cleanup and Reraise&lt;/a&gt; &lt;small&gt; I&amp;#8217;ve had this code around...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'&gt;Five Days to a Django Web App: Day Three, Coding&lt;/a&gt; &lt;small&gt; Thanks for coming back for...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/jesse-noller-on-python' rel='bookmark' title='Permanent Link: Jesse Noller on Python'&gt;Jesse Noller on Python&lt;/a&gt; &lt;small&gt; Jesse Noller has been republishing...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=9uGCx0EhNYc:W2PpM6ALzfk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=9uGCx0EhNYc:W2PpM6ALzfk:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=9uGCx0EhNYc:W2PpM6ALzfk:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=9uGCx0EhNYc:W2PpM6ALzfk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=9uGCx0EhNYc:W2PpM6ALzfk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/9uGCx0EhNYc" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls#comments" thr:count="0" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls/feed/atom" thr:count="0" />
		<thr:total>0</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Yet Another Python Enum Module]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/KZLQaj0jWxM/yet-another-python-enum-module" />
		<id>http://blog.bstpierre.org/yet-another-python-enum-module</id>
		<updated>2009-08-05T03:25:13Z</updated>
		<published>2009-07-20T02:21:36Z</published>
		<category scheme="http://blog.bstpierre.org" term="python" />		<summary type="html"><![CDATA[
		
		
		
		I didn&#8217;t like the existing enum recipes, so I cooked up what I feel is a better way of working with enumerations in python. The result is yapyenum, hosted on github. Rather than come up with something new to say about it, I&#8217;ll just repost the README here:
This module provides named enumerations for python.
Unlike other [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/jesse-noller-on-python' rel='bookmark' title='Permanent Link: Jesse Noller on Python'>Jesse Noller on Python</a> <small> Jesse Noller has been republishing...</small></li><li><a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'>Python Exception Handling: Cleanup and Reraise</a> <small> I&#8217;ve had this code around...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'>Five Days to a Django Web App: Day Three, Coding</a> <small> Thanks for coming back for...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/yet-another-python-enum-module">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/yet-another-python-enum-module";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Yet+Another+Python+Enum+Module";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;I didn&amp;#8217;t like the existing enum recipes, so I cooked up what I feel is a better way of working with enumerations in python. The result is &lt;a href="http://github.com/bstpierre/yapyenum/tree/master"&gt;yapyenum&lt;/a&gt;, hosted on github. Rather than come up with something new to say about it, I&amp;#8217;ll just repost the README here:&lt;/p&gt;
&lt;blockquote class="gmail_quote" style="border-left: 1px solid #cccccc; margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex; font-family: courier new,monospace;"&gt;&lt;p&gt;This module provides named enumerations for python.&lt;/p&gt;
&lt;p&gt;Unlike other implementations that I have seen, this is *not* an anonymous enumeration. To use it derive from the Enumeration class provided in enum.py and list the names of the enum members in the class member _enum_.&lt;/p&gt;
&lt;p&gt;The enum is itself a singleton class. It tries to be immutable via __slots__ and by refusing to allow its members to be changed.&lt;/p&gt;
&lt;p&gt;Enum members are an integer subclass with a &amp;#8220;name&amp;#8221; property and that knows how to pretty print itself. The enum values are interchangeable with integers, which may or may not be what you want.&lt;/p&gt;
&lt;p&gt;The class supports membership (&amp;#8221;FOO in MyEnum&amp;#8221;) and mapping a non-instance-member value to a name. (I.e. if FOO uses the integer value 1, then MyEnum.name(1) returns &amp;#8220;FOO&amp;#8221;.)&lt;/p&gt;
&lt;p&gt;Tested on 2.4.6, 2.5.2, 2.6.2, on a combination of debian sarge, etch, lenny, and ubuntu 9.04.&lt;/p&gt;
&lt;p&gt;There are obvious capabilities that could be added but this does all that I need so far. Patches will be happily accepted.&lt;/p&gt;
&lt;p&gt;Permissive license (MIT). Have fun.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;For other enum recipes, see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.python.org/dev/peps/pep-0354/"&gt;PEP 354&lt;/a&gt; (rejected enum proposal for python)&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://pypi.python.org/pypi/enum/"&gt;enum&lt;/a&gt; module in pypi&lt;/li&gt;
&lt;li&gt;Recipe 413486: &lt;a href="http://code.activestate.com/recipes/413486/"&gt;First Class Enums in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recipe 305271: &lt;a href="http://code.activestate.com/recipes/305271/"&gt;Enumerated values by name or number&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recipe 67107: &lt;a href="http://code.activestate.com/recipes/67107/"&gt;Enums for Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recipe 81098: &lt;a href="http://code.activestate.com/recipes/81098/"&gt;python enum with strings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;At Stack Overflow: &lt;a href="http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python"&gt;What’s the best way to implement an ‘enum’ in Python?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Python Cookbook (at page 607 on &lt;a href="http://books.google.com/books?id=Q0s6Vgb98CQC&amp;amp;lpg=PA607&amp;amp;ots=hc1236Snoz&amp;amp;dq=python%20cookbook%20enum&amp;amp;pg=PA607"&gt;Google Books&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Several of these recipes have comments along the lines of &amp;#8220;if you&amp;#8217;re using one of these too-fancy enum recipes in python, you&amp;#8217;re doing something unpythonic&amp;#8221;. This is likely true, but sometimes unavoidable for social, political, and/or historical reasons.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/jesse-noller-on-python' rel='bookmark' title='Permanent Link: Jesse Noller on Python'&gt;Jesse Noller on Python&lt;/a&gt; &lt;small&gt; Jesse Noller has been republishing...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise' rel='bookmark' title='Permanent Link: Python Exception Handling: Cleanup and Reraise'&gt;Python Exception Handling: Cleanup and Reraise&lt;/a&gt; &lt;small&gt; I&amp;#8217;ve had this code around...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'&gt;Five Days to a Django Web App: Day Three, Coding&lt;/a&gt; &lt;small&gt; Thanks for coming back for...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=KZLQaj0jWxM:2m4ZDuTTLTg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=KZLQaj0jWxM:2m4ZDuTTLTg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=KZLQaj0jWxM:2m4ZDuTTLTg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=KZLQaj0jWxM:2m4ZDuTTLTg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=KZLQaj0jWxM:2m4ZDuTTLTg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/KZLQaj0jWxM" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/yet-another-python-enum-module#comments" thr:count="2" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/yet-another-python-enum-module/feed/atom" thr:count="2" />
		<thr:total>2</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/yet-another-python-enum-module</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Python Exception Handling: Cleanup and Reraise]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/9FBB5QHsClU/python-exception-handling-cleanup-and-reraise" />
		<id>http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise</id>
		<updated>2009-08-05T03:27:15Z</updated>
		<published>2009-07-16T03:35:49Z</published>
		<category scheme="http://blog.bstpierre.org" term="python" /><category scheme="http://blog.bstpierre.org" term="howto" /><category scheme="http://blog.bstpierre.org" term="tutorial" />		<summary type="html"><![CDATA[
		
		
		
		I&#8217;ve had this code around for a while and had an opportunity to drag it out the other day and dust it off. The problem: Every now and again there&#8217;s a situation where you don&#8217;t really want to catch an exception, but you do want to perform some cleanup and let the exception propagate up [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls' rel='bookmark' title='Permanent Link: Using Python&#8217;s ctypes to Call Into C Libraries'>Using Python&#8217;s ctypes to Call Into C Libraries</a> <small>The ctypes module makes loading and...</small></li><li><a href='http://blog.bstpierre.org/jesse-noller-on-python' rel='bookmark' title='Permanent Link: Jesse Noller on Python'>Jesse Noller on Python</a> <small> Jesse Noller has been republishing...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'>Five Days to a Django Web App: Day Three, Coding</a> <small> Thanks for coming back for...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Python+Exception+Handling%3A+Cleanup+and+Reraise";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;I&amp;#8217;ve had &lt;a href="http://gist.github.com/148135"&gt;this code&lt;/a&gt; around for a while and had an opportunity to drag it out the other day and dust it off. The problem: Every now and again there&amp;#8217;s a situation where you don&amp;#8217;t really want to catch an exception, but you do want to perform some cleanup and let the exception propagate up the stack. Sometimes there&amp;#8217;s an extra wrinkle in that the cleanup code may itself throw an exception (that I&amp;#8217;m simply going to assume we can ignore).&lt;/p&gt;
&lt;p&gt;&lt;script src="http://gist.github.com/148135.js"&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;You can run the file to see the behavior. Simply provide an integer 1-5 as a command line argument and you&amp;#8217;ll run the selected scenario and see the output. The goal in this case is for cleanup to occur and an exception to be reported as having occurred at line 10.&lt;/p&gt;
&lt;p&gt;The code in reraiser1 is wrong because this behaves as if a brand new exception were thrown at line 27. That may not seem so bad, but this code is pretty simple. If this happens and the stack trace is deep, it will be almost impossible to diagnose what went wrong.&lt;/p&gt;
&lt;p&gt;The code in reraiser2 shows what happens when a second exception occurs in the except block. A bare raise statement here might be an attempt to re-raise the original exception, but python&amp;#8217;s rules about re-raising specify that the most recent exception in the scope is what is reraised. In this case, that&amp;#8217;s the exception thrown from the cleanup function. Again, this makes troubleshooting difficult.&lt;/p&gt;
&lt;p&gt;In reraiser3 I worked around the problem in reraiser2 by moving the cleanup function&amp;#8217;s exception into a separate scope by defining a local cleanup function and calling it from within the except block. This prevents the cleanup function&amp;#8217;s exception from polluting the scope with an irrelevant exception and we can re-raise the original exception. This results in a stack trace rooted at line 10.&lt;/p&gt;
&lt;p&gt;Reraiser4 takes a different approach. Instead of moving the cleanup function&amp;#8217;s exception into a separate scope, it captures the traceback information from the original exception and then passes it back to the raise statement so that the reported traceback is accurate.&lt;/p&gt;
&lt;p&gt;The cleanest way to handle this is to use a finally block as shown in reraiser5. This situation is what &amp;#8220;finally&amp;#8221; is meant for: it does not trap the exception, it just gives you a chance to clean up before control moves back up the stack to the caller. The presence of finally clues readers in to the fact that you aren&amp;#8217;t messing with the exception, and that the point of the block is to perform cleanup.&lt;/p&gt;
&lt;p&gt;Kindly drop me a note if I&amp;#8217;ve got something wrong above, or if I&amp;#8217;m missing a technique (or a common anti-pattern!). Thanks.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls' rel='bookmark' title='Permanent Link: Using Python&amp;#8217;s ctypes to Call Into C Libraries'&gt;Using Python&amp;#8217;s ctypes to Call Into C Libraries&lt;/a&gt; &lt;small&gt;The ctypes module makes loading and...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/jesse-noller-on-python' rel='bookmark' title='Permanent Link: Jesse Noller on Python'&gt;Jesse Noller on Python&lt;/a&gt; &lt;small&gt; Jesse Noller has been republishing...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'&gt;Five Days to a Django Web App: Day Three, Coding&lt;/a&gt; &lt;small&gt; Thanks for coming back for...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=9FBB5QHsClU:Ah--HQ_JZhA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=9FBB5QHsClU:Ah--HQ_JZhA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=9FBB5QHsClU:Ah--HQ_JZhA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=9FBB5QHsClU:Ah--HQ_JZhA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=9FBB5QHsClU:Ah--HQ_JZhA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/9FBB5QHsClU" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise#comments" thr:count="1" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise/feed/atom" thr:count="1" />
		<thr:total>1</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Makefiles are Software Too]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/CzgnK_qtqKc/makefiles-are-software-too" />
		<id>http://blog.bstpierre.org/?p=173</id>
		<updated>2009-04-14T03:17:48Z</updated>
		<published>2009-04-14T03:17:48Z</published>
		<category scheme="http://blog.bstpierre.org" term="engineering" /><category scheme="http://blog.bstpierre.org" term="software-engineering" /><category scheme="http://blog.bstpierre.org" term="tool" />		<summary type="html"><![CDATA[
		
		
		
		This post was inspired by recent experience with some horrible build scripts from the open source world &#8212; but I&#8217;ve seen enough in-house badness over the years that I wanted to establish some basic parameters for build scripts.
This is a really broad way to divide the world, but I see two important categories of build [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/the-toolkit-of-a-software-engineer' rel='bookmark' title='Permanent Link: The Toolkit of a Software Engineer'>The Toolkit of a Software Engineer</a> <small> This is a rundown of...</small></li><li><a href='http://blog.bstpierre.org/the-cost-of-not-testing-software' rel='bookmark' title='Permanent Link: The cost of (not) testing software'>The cost of (not) testing software</a> <small> Great post on the cost...</small></li><li><a href='http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard' rel='bookmark' title='Permanent Link: 3 Easy Ways to Stick to a Coding Standard'>3 Easy Ways to Stick to a Coding Standard</a> <small> When you&#8217;re writing python, you...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/makefiles-are-software-too">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/makefiles-are-software-too";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Makefiles+are+Software+Too";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;This post was inspired by recent experience with some horrible build scripts from the open source world &amp;mdash; but I&amp;#8217;ve seen enough in-house badness over the years that I wanted to establish some basic parameters for build scripts.&lt;/p&gt;
&lt;p&gt;This is a really broad way to divide the world, but I see two important categories of build scripts. (By &amp;#8220;build script&amp;#8221; I mean Makefiles, SConsfiles, Ant files, and their ilk).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build scripts intended for in-house use, never seen by outsiders.&lt;/li&gt;
&lt;li&gt;Build scripts that you distribute to external users.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both sets of build scripts are important. Let&amp;#8217;s consider the build script (or Makefile system, or whatever you&amp;#8217;re using) as a software system independent of the software that is being built. Its primary requirement is to reliably convert your source code into an executable. Any decent build script has numerous other requirements, most of which are probably implied. I have never seen a written requirement for the numerous build scripts I&amp;#8217;ve written. I have only rarely heard them discussed &amp;mdash; and never actually posed as &amp;#8220;requirements&amp;#8221;.&lt;/p&gt;
&lt;p&gt;I have, always taken the following meta-requirements for granted. Based on my experience with some fairly awful build scripts, I guess these aren&amp;#8217;t universally acknowledged.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The build script should build all the &amp;#8220;normal&amp;#8221; executables with a single command.&lt;/li&gt;
&lt;li&gt;The build script must encapsulate all environment variables within the script. (This is a corollary of the above.)&lt;/li&gt;
&lt;li&gt;The build script should not unnecessarily rebuild source code in a directory tree that has previously been built. (This is &lt;tt&gt;make&lt;/tt&gt;&amp;#8217;s raison d&amp;#8217;etre.)&lt;/li&gt;
&lt;li&gt;The build script should be documented. It doesn&amp;#8217;t have to be elaborate, but a five-line comment at the top of the script describing the available command-line variables would be nice.&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;make clean&lt;/tt&gt; or its equivalent must work reliably.&lt;/li&gt;
&lt;li&gt;It should rarely be necessary to run &lt;tt&gt;make clean&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Bonus: the build should be parallelizable, to be able to take advantage of multicore machines and/or distributed builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Back to those two categories. If you screw up an in-house build script, it&amp;#8217;s primarily your team that&amp;#8217;s going to suffer. I&amp;#8217;m not aware of any bugs from SQA or Customers on projects I&amp;#8217;ve been on that were traced back to a build script, but I can see where it could happen. Mainly what I&amp;#8217;ve seen is developers habitually wasting time rebuilding source that should not need to be rebuilt. The worst offender in this respect was a build script that forced a &amp;#8220;make clean&amp;#8221; at the beginning of every build. In the immortal words of Dave Barry, I&amp;#8217;m not making this up. Really.&lt;/p&gt;
&lt;p&gt;(Actually, I think it was even worse when I was dealing with a build script that required a &amp;#8220;make clean&amp;#8221; every time around but didn&amp;#8217;t have it coded into the script!)&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t say I have a completely clean conscience in this area: I wrote a big pile of Makefiles for a past employer in which I unknowingly used features (bugs?) of a specific version of GNU Make that were &amp;#8220;fixed&amp;#8221; in a subsequent version. As time went on it became (a) a much bigger task to fix the Makefile code that used the &amp;#8220;feature&amp;#8221; and (b) harder to keep the specific version of GNU Make that worked with the Makefiles! I imagine they&amp;#8217;re still stuck with this situation&amp;#8230; sorry guys.&lt;/p&gt;
&lt;p&gt;In the second category, when you toss a slapped-together build script to your unsuspecting users you do all of human society a disservice. Now they are not only wasting their time fighting with a bad build script, but they often have the disadvantage of being unable to contact the author directly for support or commiserate with other experienced users of the system.&lt;/p&gt;
&lt;p&gt;So do us all a favor: if you don&amp;#8217;t know what you&amp;#8217;re doing when you start to write your next build script, please ask for help. Otherwise we&amp;#8217;ll have to resort to some sort of professional licensing scheme for build script authors.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/the-toolkit-of-a-software-engineer' rel='bookmark' title='Permanent Link: The Toolkit of a Software Engineer'&gt;The Toolkit of a Software Engineer&lt;/a&gt; &lt;small&gt; This is a rundown of...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/the-cost-of-not-testing-software' rel='bookmark' title='Permanent Link: The cost of (not) testing software'&gt;The cost of (not) testing software&lt;/a&gt; &lt;small&gt; Great post on the cost...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard' rel='bookmark' title='Permanent Link: 3 Easy Ways to Stick to a Coding Standard'&gt;3 Easy Ways to Stick to a Coding Standard&lt;/a&gt; &lt;small&gt; When you&amp;#8217;re writing python, you...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=CzgnK_qtqKc:7ScDstDzVkY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=CzgnK_qtqKc:7ScDstDzVkY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=CzgnK_qtqKc:7ScDstDzVkY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=CzgnK_qtqKc:7ScDstDzVkY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=CzgnK_qtqKc:7ScDstDzVkY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/CzgnK_qtqKc" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/makefiles-are-software-too#comments" thr:count="0" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/makefiles-are-software-too/feed/atom" thr:count="0" />
		<thr:total>0</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/makefiles-are-software-too</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Five Days to a Django Web App: Day Four, Deployment]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/-xLRzNYwRM8/five-days-to-a-django-web-app-day-four-deployment" />
		<id>http://blog.bstpierre.org/?p=169</id>
		<updated>2009-08-25T22:46:16Z</updated>
		<published>2009-03-02T16:48:20Z</published>
		<category scheme="http://blog.bstpierre.org" term="django" /><category scheme="http://blog.bstpierre.org" term="howto" /><category scheme="http://blog.bstpierre.org" term="python" /><category scheme="http://blog.bstpierre.org" term="tutorial" />		<summary type="html"><![CDATA[
		
		
		
		Thanks for your patience, and for coming back for a discussion of deploying our Django web app.
In case you missed any of the previous posts in this series, here they are:

Day One, Get Ready (Concept and prep)
Day Two, Mockups (Creating a design)
Day Three, Coding (Coding tests, views, templates, and models)

Pre-Deployment
First, we need to make a [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day One, Get Ready'>Five Days to a Django Web App: Day One, Get Ready</a> <small> This is the first in...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'>Five Days to a Django Web App: Day Three, Coding</a> <small> Thanks for coming back for...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Two, Mockups'>Five Days to a Django Web App: Day Two, Mockups</a> <small> Welcome back. Ready for Day...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Five+Days+to+a+Django+Web+App%3A+Day+Four%2C+Deployment";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;Thanks for your patience, and for coming back for a discussion of deploying our Django web app.&lt;/p&gt;
&lt;p&gt;In case you missed any of the previous posts in this series, here they are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/five-days-to-a-django-web-app-day-one-get-ready"&gt;Day One, Get Ready&lt;/a&gt; (Concept and prep)&lt;/li&gt;
&lt;li&gt;&lt;a href="/five-days-to-a-django-web-app-day-two-mockups"&gt;Day Two, Mockups&lt;/a&gt; (Creating a design)&lt;/li&gt;
&lt;li&gt;&lt;a href="/five-days-to-a-django-web-app-day-three-coding"&gt;Day Three, Coding&lt;/a&gt; (Coding tests, views, templates, and models)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Pre-Deployment&lt;/h2&gt;
&lt;p&gt;First, we need to make a couple of decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How are we going to push updates to the live site: FTP, git, svn?&lt;/li&gt;
&lt;li&gt;How are we going to handle backups?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Version Control as Distribution System&lt;/h3&gt;
&lt;p&gt;In my case, I&amp;#8217;m using svn+ssh to push updates. Notice that this does not require special setup on your server &amp;#8212; you do not need to install the svn stuff that DreamHost or your host may provide. Just do svn init to create a repository on the server (&lt;em&gt;outside&lt;/em&gt; the DocumentRoot!). Then point your development pc to svn+ssh://USERNAME@host.example.com/home/USERNAME/svn/PROJECT. (Git works similarly, no support from your host required except the binaries.)&lt;/p&gt;
&lt;p&gt;In the directory on the host where you&amp;#8217;re going to store your project files, point to the same URL. (You could use the file:///&amp;#8230;/ url, but I prefer to avoid accessing the repo directly. Superstition?)&lt;/p&gt;
&lt;p&gt;Now, whenever you make a change on your development system, just &amp;quot;svn ci&amp;quot; and then on the host &amp;#8220;svn up&amp;#8221; and restart your fcgi to pick up the new code. Presto! The live site is updated with your change.&lt;/p&gt;
&lt;h3&gt;Backups&lt;/h3&gt;
&lt;p&gt;You must have a backup strategy: Your app will have users. Your host&amp;#8217;s disk will burp. Your users will hate you when the disk burps and you don&amp;#8217;t have a good backup.&lt;/p&gt;
&lt;p&gt;There are probably 374 different ways of backing up your Django app. The two major things you need to capture are the database and your code. If you are storing objects (e.g. uploaded files) outside the database, you&amp;#8217;ll need to back these up too. The option I&amp;#8217;m using is &lt;a href="http://code.google.com/p/django-backup/"&gt;django-backup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pull the code from subversion into your project. Rename the directory to &amp;quot;django_backup&amp;quot;. Add django_backup to your INSTALLED_APPS. Verify it works by running &lt;code&gt;./manage.py backup -c&lt;/code&gt;. Sanity check the backup by doing &lt;code&gt;zless backups/*.gz&lt;/code&gt;. We&amp;#8217;ll set up a cron job on the host to run this regularly when we deploy.&lt;/p&gt;
&lt;h2&gt;Deploy&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;ve previously written about &lt;a href="/deploying-django-apps-on-dreamhost"&gt;deploying Django apps on DreamHost&lt;/a&gt;, so I&amp;#8217;m not going to duplicate that here. Keep in mind that you want to use the version control strategy outlined above. Read that article, deploy your app and come back here when you&amp;#8217;re done.&lt;/p&gt;
&lt;h2&gt;Post-Deployment&lt;/h2&gt;
&lt;h3&gt;Backup&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s add that cron job we previously mentioned. On the host, run &lt;code&gt;crontab -e&lt;/code&gt;. If you&amp;#8217;re on DreamHost and this if the first time you&amp;#8217;ve used cron, it will prompt you for an email address to send output to. Then it will dump you into an editor. (Side note: &amp;quot;joe&amp;quot; is the default. If you want something different, like vim, be sure that EDITOR=/usr/bin/vim is set in your environment.)&lt;/p&gt;
&lt;p&gt;Set up a job something similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
MAILTO="YOU@EXAMPLE.com"

# m h  dom mon dow   command
4 2 * * * (cd /home/PATH/TO/PROJECT; ./manage.py backup -c --email=YOU@EXAMPLE.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run a backup every day at 02:04 (AM). You will get two emails: one with the output from the job, and one with the compressed backup file. (When you get to the point where your database backups are too big for email, you&amp;#8217;ll need to find another strategy.)&lt;/p&gt;
&lt;p&gt;Now we&amp;#8217;ve got another problem to solve: we&amp;#8217;re going to accumulate a bunch of backups on the disk. Let&amp;#8217;s get rid of the old backups. This is pretty safe, since we&amp;#8217;re receiving backup files via email. Add a cron job like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
14 2 * * * (cd /home/PATH/TO/PROJECT;
    touch --date=`date --iso --date='10 days ago'` .backup.oldest;
    find ./backups/ -mindepth 1 \! -newer .backup.oldest -execdir rm '{}' +)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Formatted here for readability &amp;mdash; you need to put that all on one line.)&lt;/p&gt;
&lt;p&gt;This will remove backup files older than 10 days. You could do this more concisely with a tool like tmpwatch or tmpreaper, but neither is installed on my host and this incantation should work on pretty much any flavor and installation of linux.&lt;/p&gt;
&lt;p&gt;At this point we&amp;#8217;re deployed and the majority of the work is done. Tomorrow we&amp;#8217;ll take a look at some maintenance issues.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day One, Get Ready'&gt;Five Days to a Django Web App: Day One, Get Ready&lt;/a&gt; &lt;small&gt; This is the first in...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'&gt;Five Days to a Django Web App: Day Three, Coding&lt;/a&gt; &lt;small&gt; Thanks for coming back for...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Two, Mockups'&gt;Five Days to a Django Web App: Day Two, Mockups&lt;/a&gt; &lt;small&gt; Welcome back. Ready for Day...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=-xLRzNYwRM8:sM2v9k3-JhY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=-xLRzNYwRM8:sM2v9k3-JhY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=-xLRzNYwRM8:sM2v9k3-JhY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TheDailyBuild?a=-xLRzNYwRM8:sM2v9k3-JhY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TheDailyBuild?i=-xLRzNYwRM8:sM2v9k3-JhY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/-xLRzNYwRM8" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment#comments" thr:count="0" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment/feed/atom" thr:count="0" />
		<thr:total>0</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Firefox Quick Search for Google Maps]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/47Q3bPvcRfc/firefox-quick-search-for-google-maps" />
		<id>http://blog.bstpierre.org/firefox-quick-search-for-google-maps</id>
		<updated>2009-02-18T13:46:14Z</updated>
		<published>2009-02-18T13:43:19Z</published>
		<category scheme="http://blog.bstpierre.org" term="news" /><category scheme="http://blog.bstpierre.org" term="tip" />		<summary type="html"><![CDATA[
		
		
		
		I recently found myself wanting to quickly figure out how far (both miles and driving time) it is to various destinations. At first I was keeping a Firefox tab open to Google Maps and kept keying in the address for the destination. Then I realized that I could make a &#8220;Quick Search&#8221; and get the [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Four, Deployment'>Five Days to a Django Web App: Day Four, Deployment</a> <small> Thanks for your patience, and...</small></li><li><a href='http://blog.bstpierre.org/code-review-tools' rel='bookmark' title='Permanent Link: Code Review Tools'>Code Review Tools</a> <small> Yesterday I posted twenty reasons...</small></li><li><a href='http://blog.bstpierre.org/getting-started-in-c' rel='bookmark' title='Permanent Link: Getting Started in C'>Getting Started in C</a> <small> Some may argue that C...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/firefox-quick-search-for-google-maps">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/firefox-quick-search-for-google-maps";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Firefox+Quick+Search+for+Google+Maps";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;I recently found myself wanting to quickly figure out how far (both miles and driving time) it is to various destinations. At first I was keeping a Firefox tab open to Google Maps and kept keying in the address for the destination. Then I realized that I could make a &amp;#8220;Quick Search&amp;#8221; and get the info faster. Add this link to your Quick Searches folder:&lt;/p&gt;
&lt;p&gt;http://maps.google.com/maps?f=d&amp;amp;saddr=Elm+St,+ Manchester,+NH+03101&amp;amp;daddr=%s&amp;amp;output=html&lt;/p&gt;
&lt;p&gt;[Formatted here for readability. Put it all on one line with no spaces.]&lt;/p&gt;
&lt;p&gt;Edit the properties so that the &amp;#8220;saddr=&amp;#8221; part is your start address. Change the keyword to &amp;#8220;gm&amp;#8221;. This is set to only output simple HTML (no map). If you want the full map, remove the &amp;amp;output=html on the end.&lt;/p&gt;
&lt;p&gt;Now, when you need to get a quick idea of how long it is going to take you to get somewhere, open a new tab and type &amp;#8220;gm city, state&amp;#8221;.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Four, Deployment'&gt;Five Days to a Django Web App: Day Four, Deployment&lt;/a&gt; &lt;small&gt; Thanks for your patience, and...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/code-review-tools' rel='bookmark' title='Permanent Link: Code Review Tools'&gt;Code Review Tools&lt;/a&gt; &lt;small&gt; Yesterday I posted twenty reasons...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/getting-started-in-c' rel='bookmark' title='Permanent Link: Getting Started in C'&gt;Getting Started in C&lt;/a&gt; &lt;small&gt; Some may argue that C...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=clwm7zrX"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=AyM2z6K5"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?i=AyM2z6K5" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=cThkHFMf"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?i=cThkHFMf" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/47Q3bPvcRfc" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/firefox-quick-search-for-google-maps#comments" thr:count="0" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/firefox-quick-search-for-google-maps/feed/atom" thr:count="0" />
		<thr:total>0</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/firefox-quick-search-for-google-maps</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Five Days to a Django Web App: Day Three, Coding]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/AWVjR9Ot1qs/five-days-to-a-django-web-app-day-three-coding" />
		<id>http://blog.bstpierre.org/?p=141</id>
		<updated>2009-02-27T17:55:59Z</updated>
		<published>2009-02-11T15:14:41Z</published>
		<category scheme="http://blog.bstpierre.org" term="django" /><category scheme="http://blog.bstpierre.org" term="python" /><category scheme="http://blog.bstpierre.org" term="tutorial" /><category scheme="http://blog.bstpierre.org" term="howto" />		<summary type="html"><![CDATA[
		
		
		
		Thanks for coming back for Day Three!
[Note: Sorry this post is a day late. It was all ready to go late yesterday, but some of the code included below triggered a bug either in WordPress or ScribeFire and the whole post got mangled. I managed to resurrect it today from drafts, and I think it's [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Two, Mockups'>Five Days to a Django Web App: Day Two, Mockups</a> <small> Welcome back. Ready for Day...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day One, Get Ready'>Five Days to a Django Web App: Day One, Get Ready</a> <small> This is the first in...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Four, Deployment'>Five Days to a Django Web App: Day Four, Deployment</a> <small> Thanks for your patience, and...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Five+Days+to+a+Django+Web+App%3A+Day+Three%2C+Coding";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;Thanks for coming back for Day Three!&lt;/p&gt;
&lt;p&gt;[Note: Sorry this post is a day late. It was all ready to go late yesterday, but some of the code included below triggered a bug either in WordPress or ScribeFire and the whole post got mangled. I managed to resurrect it today from drafts, and I think it's coherent, but if you find some problem with it please drop me a note.]&lt;/p&gt;
&lt;h2&gt;Progress So Far&lt;/h2&gt;
&lt;p&gt;Yesterday we &lt;a href="../five-days-to-a-django-web-app-day-two-mockups"&gt;built some mockups&lt;/a&gt;, just HTML and CSS — nothing active. Hopefully you’ve had a chance to run your mockups past a couple of people for feedback and ideas.&lt;/p&gt;
&lt;p&gt;Armed with these mockups, we’re ready to get started coding. Today’s post is the longest in the series. Take it in chunks if you need to. (I didn’t write it all at once either.)&lt;/p&gt;
&lt;h2&gt;Foundation&lt;/h2&gt;
&lt;h3&gt;MySQL&lt;/h3&gt;
&lt;p&gt;First, go to your web host’s control panel and create two MySQL databases for this project. I created &amp;#8220;resumandb&amp;#8221; and &amp;#8220;test_resumandb&amp;#8221;. This latter database is needed for running the built-in test system.&lt;/p&gt;
&lt;p&gt;You’ll get an error/warning if the test database exists when you first run tests. However, the control panel needs to set user privileges and it seems like the only way it knows how to do this is by creating the database.&lt;/p&gt;
&lt;p&gt;Set up the same databases on your local system:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
bash$ mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1969
Server version: 5.0.32-Debian_7etch8-log Debian etch distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql&amp;gt; CREATE DATABASE resumandb;
Query OK, 1 row affected (0.00 sec)

mysql&amp;gt; GRANT ALL ON resumandb.* TO 'resuman'@'localhost' identified
    by 'reallY_baD_9passworD!';
Query OK, 0 rows affected (0.11 sec)

mysql&amp;gt; GRANT ALL ON test_resumandb.* TO 'resuman'@'localhost' identified
    by 'reallY_baD_9passworD!';
Query OK, 0 rows affected (0.0 sec)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that you don&amp;#8217;t need to create the test database. As mentioned above, this will happen when you run tests.&lt;/p&gt;
&lt;h3&gt;Project Skeleton&lt;/h3&gt;
&lt;p&gt;What follows is a bunch of steps: do-this, do-that. At the end of this section you should have a runnable (but empty) project that is ready to start hanging functionality onto.&lt;/p&gt;
&lt;p&gt;Then generate a skeleton for your project: &lt;code&gt;django-admin.py  startproject YOURPROJECT&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then dive right in to settings.py and change a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set the admin email to your email address.&lt;/li&gt;
&lt;li&gt;Set the MySQL database according to your web host&amp;#8217;s settings.&lt;/li&gt;
&lt;li&gt;Timezone.&lt;/li&gt;
&lt;li&gt;MEDIA_ROOT, MEDIA_URL and variants — see note below.&lt;/li&gt;
&lt;li&gt;TEMPLATE_DIRS — see note below.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make it easier to use the same settings file to test both locally and on your host, I add the following to my settings.py to set MEDIA_ROOT and TEMPLATE_DIRS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import os

def full_path_to(path):
    '''This makes this settings file relocatable.'''
    return os.path.join(os.getcwd(), path)

MEDIA_ROOT = full_path_to('static/')
TEMPLATE_DIRS = (
    full_path_to('templates')
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you just have to make sure to cd to the directory where your settings.py lives whenever the app runs (more on this when we deploy to the host). I find this easier than monkeying with PYTHONPATH.&lt;/p&gt;
&lt;p&gt;Similarly, the following will change your URLs based on whether you&amp;#8217;re running locally or on the host. (Just make sure you test for a directory that only exists locally! My paths are a little different on the host.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
MEDIA_URL = 'http://media.resuman.com/resuman/static/'
if os.path.exists('/home/brian/projects/resuman'):
    MEDIA_URL = 'http://localhost/apache2-default/resuman-static/'

ADMIN_MEDIA_PREFIX = 'http://media.example.com/admin_media/'
if os.path.exists('/home/brian/projects/resuman'):
    ADMIN_MEDIA_PREFIX = 'http://localhost/apache2-default/resuman-admin/'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it&amp;#8217;s time to generate the app: &lt;code&gt;./manage.py startapp YOURAPP&lt;/code&gt;. Notice that the app name should be different from the project name (otherwise it gets too confusing later on).&lt;/p&gt;
&lt;p&gt;Edit your YOURPROJECT/urls.py to include a reference to YOURAPP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    (r'^funnel/$', include('resuman.jobfunnel.urls')),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also uncomment the admin lines so you can use the built-in admin app.&lt;/p&gt;
&lt;p&gt;Now it&amp;#8217;s time to generate the app: &lt;code&gt;./manage.py startapp YOURAPP&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now edit YOURAPP/urls.py &amp;#8212; paste in the needed bits from the existing urls.py, but drop the admin lines and the include().&lt;/p&gt;
&lt;p&gt;In your settings file, add the admin and admindoc apps and &amp;#8220;YOURPROJECT.YOURAPP&amp;#8221; to INSTALLED_APPS.&lt;/p&gt;
&lt;p&gt;Under the YOURPROJECT directory, create a templates directory and a static directory. Copy your HTML files from the mockup to the templates directory. Copy the CSS file to the static directory.&lt;/p&gt;
&lt;p&gt;My system is configured by default to serve from /var/www/apache2-default/, and we specified above to fetch static files from http://localhost/resuman-static/, so we need to do the following to make this possible (substituting your paths, of course):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
bash$ ln -s /home/brian/projects/resuman/static /var/www/apache2-default/resuman-static
bash$ ln -s /home/brian/projects/django/git/django/contrib/admin/media/ \
    /var/www/apache2-default/resuman-admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart apache.&lt;/p&gt;
&lt;p&gt;Change directory to YOURPROJECT and &lt;code&gt;./manage.py syncdb; ./manage.py runserver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Browse to &lt;a href="http://127.0.0.1:8000/admin/"&gt;http://127.0.0.1:8000/admin/&lt;/a&gt;. Log in, and you should see the admin app in all its glory. If the stylesheet didn&amp;#8217;t load (ie. it looks really ugly), View Source on the page. At the top, find the URL to that ends something like &amp;#8230;/resuman-admin/css/base.css. Copy-paste this into the address bar. It probably doesn&amp;#8217;t load. Verify that it is the right URL — if not then change your settings.py to have the right URL base. If the URL is right, then you need to fix your symlink, server config, or permissions (I often get bit by having the wrong permissions).&lt;/p&gt;
&lt;h3&gt;Adding Some Meat&lt;/h3&gt;
&lt;p&gt;This wouldn&amp;#8217;t be a bad time to push a snapshot into your version control system (e.g. &lt;code&gt;git init; git add .; git commit -m'YOURPROJECT skeleton done'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now we&amp;#8217;re finally ready to write the first view for this app. Edit YOURAPP/urls.py. Lay out the URL map that you want to use for your app. We&amp;#8217;re on a tight five day schedule, so don&amp;#8217;t go nuts! There&amp;#8217;s only time to get a couple of pages done. Don&amp;#8217;t worry, you can add more later. Here&amp;#8217;s what mine looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
from django.conf.urls.defaults import *

urlpatterns = patterns(
    '',
    (r'^$', 'resuman.jobfunnel.views.dashboard'),
    (r'^add/$', 'resuman.jobfunnel.views.add_job'),
    (r'^edit/$', 'resuman.jobfunnel.views.edit_job'),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember that my toplevel urls.py includes this based on the &lt;code&gt;^funnel/$&lt;/code&gt; pattern, so each of the patterns above will have …funnel/ as a prefix in the URL.&lt;/p&gt;
&lt;p&gt;Now let’s write our first couple of tests. Edit YOURAPP/tests.py and add something similar to the code below. You’ll have to change URLs and logins. The class ViewTestCase is defined in viewtestcase.py, a convenience TestCase subclass I wrote for testing Django views. Copy that file into YOURAPPNAME directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
from viewtestcase import ViewTestCase
class DashboardViewTestBase(ViewTestCase):
    # Override in subclass to use post/head/etc. Must match a
    # method defined in django.test.TestCase.
    TESTMETHOD = 'get'

    # Override.
    TESTURL = '/funnel/'
    TESTARGS = {}
    TEMPLATE = 'dashboard.html'

class DashboardViewLoginTest(DashboardViewTestBase):
    # We're expecting an error, so set TEMPLATE to None to avoid getting a bogus test failure.
    TEMPLATE = None

    def test_login_required(self):
        """
        Tests that a login is required to view the page.
        """
        self.expect_login_redirect()
        return

class DashboardViewTest(DashboardViewTestBase):
    # This uses TEMPLATE from the parent class.

    # Set username and password and the base class will automagically login the test client.
    USERNAME = 'brian'
    TESTLOGIN = (USERNAME, 'a')

    def test_logged_in_ok(self):
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now run the test: &lt;code&gt;./manage.py test&lt;/code&gt;. You should see exactly two failures. If a bunch of stuff fails (like built-in django tests), fix whatever is wrong before continuing.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
bash$ ./manage.py test
Creating test database...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table jobfunnel_job
Installing index for admin.LogEntry model
Installing index for auth.Permission model
Installing index for auth.Message model
Installing index for jobfunnel.Job model
Installing json fixture 'initial_data' from '/home/brian/projects/resuman/../resuman/jobfunnel/fixtures'.
Installed 1 object(s) from 1 fixture(s)
..........EE......
======================================================================
ERROR: test_login_required (resuman.jobfunnel.tests.DashboardViewLoginTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/brian/projects/resuman/../resuman/jobfunnel/viewtestcase.py", line 98, in setUp
    self.fetch_view(self.TESTMETHOD, self.TESTURL, self.TESTARGS)
  File "/home/brian/projects/resuman/../resuman/jobfunnel/viewtestcase.py", line 79, in fetch_view
    self.response = function(testurl, testargs, **extra)
  File "/usr/lib/python2.5/site-packages/django/test/client.py", line 277, in get
    return self.request(**r)
  File "/usr/lib/python2.5/site-packages/django/core/handlers/base.py", line 77, in get_response
    request.path_info)
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 183, in resolve
    sub_match = pattern.resolve(new_path)
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 183, in resolve
    sub_match = pattern.resolve(new_path)
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 124, in resolve
    return self.callback, args, kwargs
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 136, in _get_callback
    raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
ViewDoesNotExist: Tried dashboard in module resuman.jobfunnel.views. Error was: 'module' object has no attribute 'dashboard'

======================================================================
ERROR: test_logged_in_ok (resuman.jobfunnel.tests.DashboardViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/brian/projects/resuman/../resuman/jobfunnel/viewtestcase.py", line 98, in setUp
    self.fetch_view(self.TESTMETHOD, self.TESTURL, self.TESTARGS)
  File "/home/brian/projects/resuman/../resuman/jobfunnel/viewtestcase.py", line 79, in fetch_view
    self.response = function(testurl, testargs, **extra)
  File "/usr/lib/python2.5/site-packages/django/test/client.py", line 277, in get
    return self.request(**r)
  File "/usr/lib/python2.5/site-packages/django/core/handlers/base.py", line 77, in get_response
    request.path_info)
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 183, in resolve
    sub_match = pattern.resolve(new_path)
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 183, in resolve
    sub_match = pattern.resolve(new_path)
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 124, in resolve
    return self.callback, args, kwargs
  File "/usr/lib/python2.5/site-packages/django/core/urlresolvers.py", line 136, in _get_callback
    raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
ViewDoesNotExist: Tried dashboard in module resuman.jobfunnel.views. Error was: 'module' object has no attribute 'dashboard'

----------------------------------------------------------------------
Ran 18 tests in 4.364s

FAILED (errors=2)
Destroying test database...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s write the view so the test will pass. Edit views.py:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
@login_required
def dashboard(request):
    return render_to_response('dashboard.html',
                              {'title': 'Dashboard'},
                              context_instance=RequestContext(request))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This view uses a template called dashboard.html. Let’s make that template. Go back to the mockup for the dashboard. Copy everything into templates/base.html, then rip out the content so all you have left is the generic skeleton of a page in your app. Something like this (notice that this is using the title variable passed into the context by the view):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;

&amp;lt;!--
# Resuman: keep track of job applications, cover letters, and resumes
#
# Copyright (c) 2009, Blakita Software LLC
# All rights reserved.
--&amp;gt;

&amp;lt;html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta http-equiv="Content-Type" content="text/html;charset=utf-8" /&amp;gt;

  &amp;lt;title&amp;gt;
    {{ title }}
  &amp;lt;/title&amp;gt;

  &amp;lt;style type="text/css"&amp;gt;
    @import "{{ MEDIA_URL }}base.css";
  &amp;lt;/style&amp;gt;

  {% block scripts %}
  {% endblock scripts %}

&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;

&amp;lt;div id="container"&amp;gt;

&amp;lt;div id="menu"&amp;gt;
TBD
&amp;lt;/div&amp;gt; &amp;lt;!-- end div=menu --&amp;gt;

&amp;lt;div id="header"&amp;gt;
&amp;lt;h1&amp;gt;{{ title }}&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt; &amp;lt;!-- end div=header --&amp;gt;

&amp;lt;div id="content"&amp;gt;

{% block content %}
{% endblock content %}

&amp;lt;/div&amp;gt; &amp;lt;!-- end div=content --&amp;gt;

&amp;lt;div id="footer"&amp;gt;
&amp;lt;div class="nav"&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href="/"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href="/blog/about"&amp;gt;About&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href="/privacy.html"&amp;gt;Privacy&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="copyright"&amp;gt;Copyright © 2009, Blakita Software LLC&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt; &amp;lt;!-- end div=footer --&amp;gt;

&amp;lt;/div&amp;gt; &amp;lt;!-- end div=container --&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now take the content you ripped out and put it into dashboard.html:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
{% extends "base.html" %}

{% block content %}
&amp;lt;ul id="funnel"&amp;gt;
  &amp;lt;li class="phase first"&amp;gt;
    &amp;lt;div class="phase"&amp;gt;Applied&amp;lt;/div&amp;gt;
    &amp;lt;div class="companies"&amp;gt;
    &amp;lt;ul class="companies"&amp;gt;
        &amp;lt;li class="company"&amp;gt;AAA&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li class="phase"&amp;gt;
    &amp;lt;div class="phase"&amp;gt;Confirmed&amp;lt;/div&amp;gt;
    &amp;lt;div class="companies"&amp;gt;
    &amp;lt;ul class="companies"&amp;gt;
        &amp;lt;li class="company"&amp;gt;BBB&amp;lt;/li&amp;gt;
        &amp;lt;li class="company"&amp;gt;CCC&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li class="phase"&amp;gt;
    &amp;lt;div class="phase"&amp;gt;Screen&amp;lt;/div&amp;gt;
    &amp;lt;div class="companies"&amp;gt;
    &amp;lt;ul class="companies"&amp;gt;
        &amp;lt;li class="company"&amp;gt;Fubar&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li class="phase"&amp;gt;
    &amp;lt;div class="phase"&amp;gt;Interview&amp;lt;/div&amp;gt;
    &amp;lt;div class="companies"&amp;gt;
    &amp;lt;ul class="companies"&amp;gt;
        &amp;lt;li class="company"&amp;gt;Rabuf&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li class="phase"&amp;gt;
    &amp;lt;div class="phase"&amp;gt;Offer&amp;lt;/div&amp;gt;
    &amp;lt;div class="companies"&amp;gt;
    &amp;lt;ul class="companies"&amp;gt;
        &amp;lt;li class="company"&amp;gt;Oof Rab&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li class="phase last"&amp;gt;
    &amp;lt;div class="phase"&amp;gt;Start!&amp;lt;/div&amp;gt;
    &amp;lt;div class="companies"&amp;gt;
    &amp;lt;ul class="companies"&amp;gt;
        &amp;lt;li class="company"&amp;gt;Rab Oof&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously this still just has dummy static data. We&amp;#8217;ll get some active data in there very soon, but now we&amp;#8217;re ready to rerun the test. It should pass this time. If not, fix the problem. When you get all tests passing, celebrate!&lt;/p&gt;
&lt;p&gt;For real data, we need to write the model(s) used by this app. Let&amp;#8217;s add another test:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
class DashboardViewTest(DashboardViewTestBase):
    USERNAME = 'brian'
    TESTLOGIN = (USERNAME, 'a')

    def test_company_list(self):
        self.expect_div_content('content', 'Foobar Corp')
        return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit models.py to add the model:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
from django.contrib.auth.models import User
from django.db import models

class Job(models.Model):
    applicant = models.ForeignKey(User)
    company = models.CharField(max_length=80)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test depends on having a job in the database. Let&amp;#8217;s set up a test fixture. Edit YOURAPPNAME/fixtures/initial_data.json:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
[{"pk": 1, "model": "auth.user", "fields":
    {"username": "brian", "first_name": "", "last_name": "",
     "is_active": 1, "is_superuser": 0, "is_staff": 0,
     "last_login": "2009-02-06 13:50:02", "groups": [], "user_permissions": [],
     "password": "sha1$0fcc8$c4cf5184f5c005f90165e782fb090e7d75b72986",
     "email": "brian@example.com", "date_joined": "2009-02-06 13:44:04"}},
 {"pk": 2, "model": "auth.user", "fields":
    {"username": "alan", "first_name": "", "last_name": "",
     "is_active": 1, "is_superuser": 0, "is_staff": 0,
     "last_login": "2009-02-06 13:50:02", "groups": [], "user_permissions": [],
     "password": "sha1$0fcc8$c4cf5184f5c005f90165e782fb090e7d75b72986",
     "email": "alan@example.org", "date_joined": "2009-02-06 13:44:04"}},
 {"pk": 1, "model": "jobfunnel.job", "fields":
    {"position_url": "http://example.com/career/", "title": "Foobar Eng",
     "company_url": "http://example.com/", "company": "Foobar Corp", "applicant": 1,
     "phase": "Apply", "date": "2009-02-11", "position": "Engineer",
     "notes": "Applied via website"}}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will populate the database with a couple of users, both with password &amp;#8220;a&amp;#8221;, and a job before each test runs.&lt;/p&gt;
&lt;p&gt;Run this test, expecting exactly one failure — the content div does not contain the expected string. Let&amp;#8217;s grab the list of jobs in the view and pass it into the template:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
@login_required
def dashboard(request):
    jobs = models.Job.objects.all()
    return render_to_response('dashboard.html',
                              {'title': 'Dashboard',
                               'jobs': jobs,
                               },
                              context_instance=RequestContext(request))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I won&amp;#8217;t paste all of the code here again, but we need to edit the template to use the jobs list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    &amp;lt;ul class="companies"&amp;gt;
      {% for job in jobs %}
        &amp;lt;li class="company"&amp;gt;{{ job.company }}&amp;lt;/li&amp;gt;
      {% empty %}
        &amp;lt;li class="company"&amp;gt;No jobs in this phase.&amp;lt;/li&amp;gt;
      {% endfor %}
    &amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now rerun the test and expect it to pass. Hooray!&lt;/p&gt;
&lt;p&gt;One last refinement before we quit for today: users shouldn&amp;#8217;t be able to see each others applications. The way this is coded, all jobs are going to show up on everybody&amp;#8217;s dashboards. Not good. Here&amp;#8217;s another test that checks that the application for Foobar Corp only shows up on Brian&amp;#8217;s dashboard, not on Alan&amp;#8217;s.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
class PrivateDashboardViewTest(DashboardViewTestBase):
    USERNAME = 'alan'
    TESTLOGIN = (USERNAME, 'a')

    def test_private_applications(self):
        assert('Foobar' not in self.get_div_content('content'))
        return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the test, watch it fail, and then change one line in the view:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    jobs = models.Job.objects.filter(applicant=request.user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the all the tests should pass.&lt;/p&gt;
&lt;p&gt;Push a copy of your code into your version control tool. Take a break, you deserve it.&lt;/p&gt;
&lt;p&gt;For &amp;#8220;homework&amp;#8221;, put together your other views in the same way as this one. We’ll look at deployment tomorrow.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Two, Mockups'&gt;Five Days to a Django Web App: Day Two, Mockups&lt;/a&gt; &lt;small&gt; Welcome back. Ready for Day...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day One, Get Ready'&gt;Five Days to a Django Web App: Day One, Get Ready&lt;/a&gt; &lt;small&gt; This is the first in...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Four, Deployment'&gt;Five Days to a Django Web App: Day Four, Deployment&lt;/a&gt; &lt;small&gt; Thanks for your patience, and...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=uTEL7OQO"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=H18GjtgH"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?i=H18GjtgH" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=w3DF7E0r"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?i=w3DF7E0r" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/AWVjR9Ot1qs" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding#comments" thr:count="0" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding/feed/atom" thr:count="0" />
		<thr:total>0</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding</feedburner:origLink></entry>
		<entry>
		<author>
			<name>Brian St. Pierre</name>
						<uri>http://blog.bstpierre.org</uri>
					</author>
		<title type="html"><![CDATA[Five Days to a Django Web App: Day Two, Mockups]]></title>
		<link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TheDailyBuild/~3/HHUbQxbbJ-c/five-days-to-a-django-web-app-day-two-mockups" />
		<id>http://blog.bstpierre.org/?p=139</id>
		<updated>2009-02-10T19:01:42Z</updated>
		<published>2009-02-10T14:40:54Z</published>
		<category scheme="http://blog.bstpierre.org" term="django" /><category scheme="http://blog.bstpierre.org" term="tutorial" /><category scheme="http://blog.bstpierre.org" term="howto" /><category scheme="http://blog.bstpierre.org" term="python" />		<summary type="html"><![CDATA[
		
		
		
		Welcome back.
Ready for Day Two? Did you get your &#8220;hello world&#8221; app running on your host?
Where We Stand
Yesterday we nailed down our concept, bought a domain name and hosting, set up our toolkit, and deployed a practice app on the host.
Here&#8217;s what I&#8217;m going to build: a web app to keep track of job applications, [...]


Related posts:<ol><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day One, Get Ready'>Five Days to a Django Web App: Day One, Get Ready</a> <small> This is the first in...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Four, Deployment'>Five Days to a Django Web App: Day Four, Deployment</a> <small> Thanks for your patience, and...</small></li><li><a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'>Five Days to a Django Web App: Day Three, Coding</a> <small> Thanks for coming back for...</small></li></ol>]]></summary>
		<content type="html" xml:base="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups">&lt;div style="float: right; width: 42px; padding-right: 10px; margin: 0 0 0 10px;"&gt;
		&lt;script type="text/javascript"&gt;
		&lt;!--
		digg_url = "http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups";
		digg_bgcolor = "";
		digg_skin = "";
		digg_window = "";
		digg_title = "Five+Days+to+a+Django+Web+App%3A+Day+Two%2C+Mockups";
		digg_media = "";
		digg_topic = "";
		digg_bodytext = "";
		//--&gt;
		&lt;/script&gt;
		&lt;script src="http://digg.com/tools/diggthis.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/div&gt;&lt;p&gt;Welcome back.&lt;/p&gt;
&lt;p&gt;Ready for Day Two? Did you get your &amp;#8220;hello world&amp;#8221; app running on your host?&lt;/p&gt;
&lt;h3&gt;Where We Stand&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready"&gt;Yesterday&lt;/a&gt; we nailed down our concept, bought a domain name and hosting, set up our toolkit, and deployed a practice app on the host.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s what I&amp;#8217;m going to build: a web app to keep track of job applications, cover letters, and resumes. The working name for the project is &amp;#8220;resuman&amp;#8221;. Unfortunately, that obvious domain name was already registered and I&amp;#8217;m not up for buying from the current owner. So after trying a bunch of more-or-less obvious combinations, I grabbed &amp;#8220;yresu.me&amp;#8221;, and I&amp;#8217;ll deploy the app at &amp;#8220;trackm.yresu.me&amp;#8221;.&lt;/p&gt;
&lt;h3&gt;Notes on Domain Registration&lt;/h3&gt;
&lt;p&gt;It&amp;#8217;s worth making a couple of notes here on the domain registration process. I use &lt;a href="http://godaddy.com"&gt;GoDaddy&lt;/a&gt; for my registrations. In the past I&amp;#8217;ve done registration with my preferred host, but if you end up switching hosts it&amp;#8217;s a hassle to move the domain registrations. Another complication: hosting services like DreamHost only support registrations on the major TLDs (.com, .org, .net, .info), while a full-service registrar like GoDaddy supports alternatives like .me.&lt;/p&gt;
&lt;p&gt;Lastly, I like the bulk domain search service from &lt;a href="http://dotster.com/"&gt;Dotster&lt;/a&gt;. You can search for up to 50 names at a time. So if your first choice is unavailable, use Dotster&amp;#8217;s bulk search tool to enter a whole mess of variations and see what&amp;#8217;s available. This is the process I used to find yresu.me — the search included resu.me, esu.me, su.me, and myresu.me, all of which were taken.&lt;/p&gt;
&lt;h3&gt;Make Some Mockups&lt;/h3&gt;
&lt;p&gt;Before we commit any design ideas to HTML, let&amp;#8217;s take some great advice from 37signals: &lt;a href="http://gettingreal.37signals.com/ch06_From_Idea_to_Implementation.php"&gt;sketch a bunch of drafts on paper first&lt;/a&gt;. To back up this idea, check out this story posted by Jeff Atwood about &lt;a href="http://www.codinghorror.com/blog/archives/001160.html"&gt;quantity vs. quality&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The ceramics teacher announced on opening day that he was dividing the class into two groups. All those on the left side of the studio, he said, would be graded solely on the quantity of work they produced, all those on the right solely on its quality. His procedure was simple: on the final day of class he would bring in his bathroom scales and weigh the work of the &amp;#8220;quantity&amp;#8221; group: fifty pound of pots rated an &amp;#8220;A&amp;#8221;, forty pounds a &amp;#8220;B&amp;#8221;, and so on. Those being graded on &amp;#8220;quality&amp;#8221;, however, needed to produce only one pot &amp;#8211; albeit a perfect one &amp;#8211; to get an &amp;#8220;A&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Well, came grading time and a curious fact emerged: the works of highest quality were all produced by the group being graded for quantity. It seems that while the &amp;#8220;quantity&amp;#8221; group was busily churning out piles of work &amp;#8211; and learning from their mistakes &amp;#8211; the &amp;#8220;quality&amp;#8221; group had sat theorizing about perfection, and in the end had little more to show for their efforts than grandiose theories and a pile of dead clay.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;So my advice to you is to produce no fewer than five different designs on paper. It only takes a few minutes to sketch something out and see what it looks like. I did a handful of drawings on paper with my kids&amp;#8217; crayons, and then some refinements on my whiteboard once I had chosen the general design. So use whatever media you have handy, and don&amp;#8217;t worry about getting it perfect. Run your sketches by a couple of innocent bystanders for some feedback.&lt;/p&gt;
&lt;p&gt;&lt;img class="size-medium wp-image-147" title="Mockups on my whiteboard." src="http://blog.bstpierre.org/wp-content/uploads/2009/02/whiteboard-mockup-300x203.jpg" alt="Mockups on my whiteboard." width="300" height="203" /&gt;&lt;/p&gt;
&lt;p&gt;Once you&amp;#8217;ve got a good design on paper, make mockups in HTML and CSS. Steve Dennis at subcide.com has a great walkthrough on &lt;a href="http://www.subcide.com/tutorials/csslayout/"&gt;creating a CSS layout from scratch&lt;/a&gt;. If you don&amp;#8217;t have an established process for building up a design, follow his tutorial. Putting together the HTML+CSS takes a little more time than sketches on paper, but it&amp;#8217;s still worth doing a couple of different designs to see what grabs you. I worked up two different CSS layouts on top of the same HTML. Run these designs by some people — now that you&amp;#8217;ve got some code, you can upload it to your site and email a link to some friends. Pick the better of the two (or three).&lt;/p&gt;
&lt;p&gt;Better yet, upload the mockups to your host and leave a comment here with a link to them. Traffic here is low enough that I should be able to reply with feedback. (Although feel free to circulate a link to this series to a hundred of your friends, or post it to digg. I&amp;#8217;ll at least reply to the first 50 comments&amp;#8230;)&lt;/p&gt;
&lt;p&gt;Tomorrow we&amp;#8217;ll start writing some code.&lt;/p&gt;


&lt;p&gt;Related posts:&lt;ol&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day One, Get Ready'&gt;Five Days to a Django Web App: Day One, Get Ready&lt;/a&gt; &lt;small&gt; This is the first in...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Four, Deployment'&gt;Five Days to a Django Web App: Day Four, Deployment&lt;/a&gt; &lt;small&gt; Thanks for your patience, and...&lt;/small&gt;&lt;/li&gt;&lt;li&gt;&lt;a href='http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding' rel='bookmark' title='Permanent Link: Five Days to a Django Web App: Day Three, Coding'&gt;Five Days to a Django Web App: Day Three, Coding&lt;/a&gt; &lt;small&gt; Thanks for coming back for...&lt;/small&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=rjcTmeAM"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=fw9zxcjc"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?i=fw9zxcjc" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TheDailyBuild?a=aZmX1i8o"&gt;&lt;img src="http://feeds.feedburner.com/~f/TheDailyBuild?i=aZmX1i8o" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyBuild/~4/HHUbQxbbJ-c" height="1" width="1"/&gt;</content>
		<link rel="replies" type="text/html" href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups#comments" thr:count="5" />
		<link rel="replies" type="application/atom+xml" href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups/feed/atom" thr:count="5" />
		<thr:total>5</thr:total>
	<feedburner:origLink>http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups</feedburner:origLink></entry>
	</feed>
