<?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.simonwillison.net/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-gb"><title>Simon Willison's Weblog Entries</title><link href="http://simonwillison.net/" rel="alternate" /><id>http://simonwillison.net/</id><updated>2009-06-13T17:01:00Z</updated><author><name>Simon Willison</name></author><link rel="self" href="http://feeds.simonwillison.net/swn-entries" type="application/atom+xml" /><entry><title>Facebook Usernames and OpenID</title><link href="http://simonwillison.net/2009/Jun/13/thefacebookdebacle/" rel="alternate" /><updated>2009-06-13T17:01:00Z</updated><id>http://simonwillison.net/2009/Jun/13/thefacebookdebacle/</id><summary type="html">&lt;p&gt;Today's launch of &lt;a href="http://search.twitter.com/search?q=%23fufacebook"&gt;Facebook Usernames&lt;/a&gt; provides an obvious and exciting opportunity for Facebook to become an OpenID provider. Facebook have clearly demonstrated their interest in becoming the key online identity for their users, and the new usernames feature is their acknowledgement that URL-based identities are an important component of that, no doubt driven in part by Twitter making usernames trendy again.&lt;/p&gt;

&lt;p&gt;It's interesting to consider Facebook's history with regards to OpenID and single sign on in general. When I started publicly advocating for OpenID &lt;a href="http://simonwillison.net/2007/talks/"&gt;back in 2007&lt;/a&gt;, my primary worry was that someone would solve the SSO problem in a proprietary way, irreparably damaging the decentralised nature of the Web - just as Microsoft had attempted a few years earlier with Passport.&lt;/p&gt;

&lt;p&gt;When Facebook Connect was announced &lt;a href="http://blog.facebook.com/blog.php?post=24577977130"&gt;a year ago&lt;/a&gt; it seemed like my worst fears had become realised. Facebook Connect's user experience was a huge improvement over OpenID - with only one provider, the sign in UI could be reduced to a single button. Their use of a popup window for the sign in flow was inspired - various usability studies have since shown that users are much more likely to complete a SSO flow if they can see the site they are signing in to in a background window.&lt;/p&gt;

&lt;p&gt;Thankfully, Facebook seem to understand that the industry isn't willing to accept a single SSO provider, no matter how smooth their implementation. Mark Zuckerberg made reassuring noises about OpenID support at both &lt;a href="http://news.cnet.com/8301-13577_3-10063328-36.html"&gt;FOWA 2008&lt;/a&gt; and &lt;a href="http://www.readwriteweb.com/archives/mark_zuckerberg_on_data_portab.php"&gt;SxSW 2009&lt;/a&gt;, but things really stepped up earlier this year when &lt;a href="http://openid.net/2009/02/05/facebook-joins-openid-foundation-board/"&gt;Facebook joined the OpenID Foundation Board&lt;/a&gt; (accompanied by a substantial financial donation). Facebook's board representative, &lt;a href="http://www.sociallipstick.com/"&gt;Luke Shepherd&lt;/a&gt;, is an excellent addition and brings a refreshingly user-centric approach to OpenID. Luke was previously responsible for much of the work on Facebook Connect and has been advocating OpenID inside Facebook for a long time.&lt;/p&gt;

&lt;p&gt;Facebook may not have committed to becoming a provider yet (at least not in public), but their decision to become a consumer first is another interesting data point. They may be trying to avoid the common criticism thrown at companies who provide but don't consume - if they're not willing to eat their own dog food, why should anyone else?&lt;/p&gt;

&lt;p&gt;At any rate, their consumer implementation is fascinating. It's live right now, even though there's no OpenID login box anywhere to be seen on the site. Instead, Facebook take advantage of the little known &lt;a href="http://openid.net/specs/openid-authentication-2_0.html#anchor28"&gt;checkid_immediate mode&lt;/a&gt;. Once you've associated your OpenID with your Facebook account (using the "Linked Accounts" section of the settings pane) Facebook sets a cookie remembering your OpenID provider, which persists even after you log out of Facebook. When you later visit the Facebook homepage, a checkid_immediate request is silently sent to your provider, logging you in automatically if you are already authenticated there.&lt;/p&gt;

&lt;p&gt;While it's great to see innovation with OpenID at such a large scale, I'm not at all convinced that they've got this right. The feature is virtually invisible to users (it took me a bunch of research to figure out how to use it) and not at all intuitive - if I've logged out of Facebook, how come visiting the home page logs me straight back in again? I guess this is why Luke is keen on &lt;a href="http://www.sociallipstick.com/2009/05/logout-the-other-half-of-the-identity-equation/"&gt;exploring single sign out with OpenID&lt;/a&gt;. It sounds like the current OpenID consumer support is principally intended as a developer preview, and I'm looking forward to seeing how they change it based on ongoing user research.&lt;/p&gt;

&lt;p&gt;As OpenID provider implementation is an obvious next step that can't be that far off - I wouldn't be surprised to hear an announcement within a month or two.&lt;/p&gt;

&lt;h3&gt;HTTP redirect codes&lt;/h3&gt;

&lt;p&gt;As an aside, I decided to check that Facebook were using the correct 3xx HTTP status code to redirect from &lt;a href="http://www.facebook.com/profile.php?id=666590500"&gt;my old profile page&lt;/a&gt; to &lt;a href="http://www.facebook.com/swillison"&gt;my new one&lt;/a&gt;. I was horrified to discover that they are using a 200 code, followed by &lt;a href="http://gist.github.com/129240"&gt;a chunk of JavaScript&lt;/a&gt; to implement the redirect! The situation for logged out users is better but still fundamentally flawed: if you enable your public search listing (using an option tucked away on &lt;a href="http://www.facebook.com/privacy/?view=search"&gt;www.facebook.com/privacy/?view=search&lt;/a&gt;) and &lt;samp&gt;curl -i&lt;/samp&gt; your old profile URL you get a 302 Found, when the correct status code is clearly a 301 Moved Permanently.&lt;/p&gt;

&lt;p&gt;One final note: it almost goes without saying, but one of the best things about OpenID is that you can register a real domain name that you can own, instead of just having another URL on Facebook.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/Jun/13/thefacebookdebacle/#comments"&gt;&lt;img src="http://simonwillison.net/2009/Jun/13/thefacebookdebacle/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="facebook" /><category term="fufacebook" /><category term="http" /><category term="openid" /><category term="sso" /><category term="thefacebookdebacle" /></entry><entry><title>djng - a Django powered microframework</title><link href="http://simonwillison.net/2009/May/19/djng/" rel="alternate" /><updated>2009-05-19T00:13:31Z</updated><id>http://simonwillison.net/2009/May/19/djng/</id><summary type="html">&lt;p&gt;&lt;a href="http://github.com/simonw/djng"&gt;djng&lt;/a&gt; is nearly two weeks old now, so it's about time I wrote a bit about the project.&lt;/p&gt;

&lt;p&gt;I presented a keynote at EuroDjangoCon in Prague earlier this month entitled &lt;a href="http://simonwillison.net/2009/talks/eurodjangocon-heresies/"&gt;Django Heresies&lt;/a&gt;. The talk followed the noble DjangoCon tradition (established last year with the help of Mark Ramm and Cal Henderson) of pointing a spotlight at Django's flaws. In my case, it was a chance to apply the benefit of hindsight to some of the design decisions I helped make back at the Lawrence Journal-World in 2004.&lt;/p&gt;

&lt;p&gt;I took a few cheap shots at things like the &lt;code class="django"&gt;{% endifequal %}&lt;/code&gt; tag and error silencing in the template system, but the three substantial topics in my talk were class-based generic views (I'm a fan), my hatred of settings.py and my interest in &lt;a href="http://en.wikipedia.org/wiki/Turtles_all_the_way_down"&gt;turtles all the way down&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;Why I hate settings.py&lt;/h4&gt;

&lt;p&gt;In the talk, I justified my dislike for settings.py by revisiting the problems behind PHP's magic quotes feature (finally going away for good in PHP 6). Magic quotes were one of the main reasons I switched to Python from PHP.&lt;/p&gt;

&lt;p&gt;My main problem with magic quotes was that they made it extremely difficult to write reusable PHP code. The feature was configured globally, which lead to a quandary. What if you have two libraries, one expecting magic quotes on and the other expecting it off? Your library could check &lt;code class="php"&gt;get_magic_quotes_gpc()&lt;/code&gt; and &lt;code class="php"&gt;stripslashes()&lt;/code&gt; from input if the setting was turned on, but this would break in the presence of the common idiom where &lt;code class="php"&gt;stripslashes()&lt;/code&gt; is applied to all incoming &lt;code class="php"&gt;$_GET&lt;/code&gt; and &lt;code class="php"&gt;$_POST&lt;/code&gt; data.&lt;/p&gt;

&lt;p&gt;Unfortunately, global settings configured using settings.py have a similar smell to them. Middleware and context processors are the best example here - a specific setting might be needed by just one installed application, but the effects are felt by everything in the system. While I haven't yet seen two "reusable" Django apps that require conflicting settings, per-application settings are an obvious use case that settings.py fails to cover.&lt;/p&gt;

&lt;p&gt;Global impact aside, my bigger problem with settings.py is that I almost always end up wanting to &lt;em&gt;reconfigure them at run-time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is possible in Django today, but comes at a price:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;Only some settings can actually be changed at run-time - others (such as USE_I18N) are lazily evaluated once and irreversibly reconfigure parts of Django's plumbing. Figuring out which ones can be changed requires exploration of Django's source code.&lt;/li&gt;
    &lt;li&gt;If you change a setting, you need to reliably change it back at the end of a request or your application will behave strangely. Uncaught exceptions could cause problems here, unless you remember to wrap dynamic setting changes in a try/finally block.&lt;/li&gt;
    &lt;li&gt;Changing a setting isn't thread-safe (without doing some extra work).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost every setting in Django has legitimate use-cases for modification at run-time. Here are just a few examples:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;Requests from mobile phones may need a different TEMPLATE_DIRS setting, to load the mobile-specific templates in preference to the site defaults.&lt;/li&gt;
    &lt;li&gt;Some sites offer premium accounts which in turn gain access to more reliable servers. Premium users might get to send e-mail via a separate pool of SMTP servers, for example.&lt;/li&gt;
    &lt;li&gt;Some sections of code may want to use a different cache backend, or talk to a different set of memcache servers - to reduce the chance of one rapidly changing component causing other component's cache entries to expire too early.&lt;/li&gt;
    &lt;li&gt;Errors in one area of a site might need to be sent to a different team of developers.&lt;/li&gt;
    &lt;li&gt;Admin users might want DEBUG=True, while regular site visitors get DEBUG=False.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, settings.py is behind the dreaded "Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined" exception. Yuck.&lt;/p&gt;

&lt;h4&gt;Turtles all the way down&lt;/h4&gt;

&lt;p&gt;The final section of the talk was about turtles. More precisely, it was about their role as an "infinite regression belief about cosmology and the nature of the universe". I want to apply that idea to Django.&lt;/p&gt;

&lt;p&gt;My favourite thing about Django is something I've started to call the "Django Contract": the idea that a Django view is a callable which takes a request object and returns a response object. I want to expand that concept to other parts of Django as well:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;URLconf: takes a request, dispatches based on &lt;code&gt;request.path&lt;/code&gt;, returns a response.&lt;/li&gt;
    &lt;li&gt;Application: takes a request, returns a response&lt;/li&gt;
    &lt;li&gt;Middleware: takes a request, returns a response (conditionally transforming either)&lt;/li&gt;
    &lt;li&gt;Django-powered site: hooked in to mod_wsgi/FastCGI/a Python web server, takes a request, returns a response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of a Django site consisting of a settings.py, urls.py and various applications and middlewares, a site would just be a callable that obeys the Django Contract and composes together dozens of other callables.&lt;/p&gt;

&lt;p&gt;At this point, Django starts to look a lot like WSGI. What if WSGI and the Django Contract were interchangeable? WSGI is a wrapper around HTTP, so what if that could be swapped in and out (through proxies) as well? Django, WSGI and HTTP, three breeds of turtle arranged on top of each other in various configurations. Turtles all the way down.&lt;/p&gt;

&lt;h4&gt;djng&lt;/h4&gt;

&lt;p&gt;djng is my experiment to see what Django would like without settings.py and with a whole lot more turtles. It's Yet Another Python Microframework.&lt;/p&gt;

&lt;p&gt;What's a microframework? The best examples are probably &lt;a href="http://webpy.org/"&gt;web.py&lt;/a&gt; (itself a result of Aaron Swartz's frustrations with Django) and &lt;a href="http://www.sinatrarb.com/"&gt;Sinatra&lt;/a&gt;, my all time favourite example of Ruby DSL design. More recent examples in Python include &lt;a href="http://github.com/breily/juno"&gt;juno&lt;/a&gt;, &lt;a href="http://github.com/JaredKuolt/newf"&gt;newf&lt;/a&gt;, &lt;a href="http://github.com/bradleywright/mnml"&gt;mnml&lt;/a&gt; and &lt;a href="http://github.com/toastdriven/itty"&gt;itty&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Microframeworks let you build an entire web application in a single file, usually with only one import statement. They are becoming increasingly popular for building small, self-contained applications that perform only one task - Service Oriented Architecture reborn as a combination of the Unix development philosophy and RESTful API design. I first saw this idea expressed in code by &lt;a href="http://thraxil.org/users/anders/posts/2005/12/12/tasty/"&gt;Anders Pearson&lt;/a&gt; and &lt;a href="http://blog.ianbicking.org/little-apps-instead-of-little-frameworks.html"&gt;Ian Bicking&lt;/a&gt; back in 2005.&lt;/p&gt;

&lt;p&gt;Unlike most microframeworks, djng has a pretty big dependency: Django itself. The plan is to reuse everything I like about Django (the templates, the ORM, view functions, the form library etc) while replacing just the top level plumbing and removing the requirement for separate settings.py and urls.py files.&lt;/p&gt;

&lt;p&gt;This is what "Hello, world" looks like in in djng:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import djng

def index(request):
    return djng.Response('Hello, world')

if __name__ == '__main__':
    djng.serve(index, '0.0.0.0', 8888)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class="python"&gt;djng.Response&lt;/code&gt; is an alias for Django's &lt;code class="python"&gt;HttpResponse&lt;/code&gt;. &lt;code class="python"&gt;djng.serve&lt;/code&gt; is a utility function which converts up anything fulfilling the Django Contract in to a WSGI application, then exposes it over HTTP.&lt;/p&gt;

&lt;p&gt;Let's add URL routing to the example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;app = djng.Router(
    (r'^hello$', lambda request: djng.Response('Hello, world')),
    (r'^goodbye$', lambda request: djng.Response('Goodbye, world')),
)

if __name__ == '__main__':
    djng.serve(app, '0.0.0.0', 8888)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The implementation of djng.Router is just &lt;a href="http://github.com/simonw/djng/blob/c892dddf064d5542c17119d02920ea4f5e9dd7f5/djng/router.py"&gt;a few lines of glue code&lt;/a&gt; adding a nicer API to Django's internal RegexURLResolver class.&lt;/p&gt;

&lt;h4&gt;Services, not settings&lt;/h4&gt;

&lt;p&gt;The trickiest problem I still need to solve is how to replace settings.py. A group of developers (including &lt;a href="http://www.holovaty.com/"&gt;Adrian&lt;/a&gt;, &lt;a href="http://lucumr.pocoo.org/"&gt;Armin&lt;/a&gt;, &lt;a href="http://lazypython.blogspot.com/"&gt;Alex&lt;/a&gt; and myself) had an excellent brainstorming session at EuroDjangoCon about this. We realised that most of the stuff in settings.py can be recast as configuring &lt;em&gt;services&lt;/em&gt; which Django makes available to the applications it is hosting. Services like the following:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;Caching&lt;/li&gt;
    &lt;li&gt;Templating&lt;/li&gt;
    &lt;li&gt;Sending e-mail&lt;/li&gt;
    &lt;li&gt;Sessions&lt;/li&gt;
    &lt;li&gt;Database connection - &lt;code&gt;django.db.connection&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;Higher level ORM&lt;/li&gt;
    &lt;li&gt;File storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of the above needs to be configured, and each also might need to be reconfigured at runtime. Django already points in this direction by providing hooks for adding custom backends for caching, template loading, file storage and session support. What's missing is an official way of swapping in different backends at runtime.&lt;/p&gt;

&lt;p&gt;I'm currently leaning towards the idea of a "stack" of service implementations, one for each of the service categories listed above. A new implementation could be pushed on to the stack at any time during the Django request/response cycle, and will be automatically popped back off again before the next request is processed (all in a thread-safe manner). Applications would also be able to instantiate and use a particular service implementation directly should they need to do so.&lt;/p&gt;

&lt;p&gt;A few days ago I heard about &lt;a href="http://pypi.python.org/pypi/Contextual"&gt;Contextual&lt;/a&gt;, which appears to be trying to solve a similar problem. Just a few minutes ago I stumbled across &lt;a href="http://pythonpaste.org/modules/registry.html"&gt;paste.registry's StackedObjectProxy&lt;/a&gt; which seems to be exactly what I've been busily reinventing.&lt;/p&gt;

&lt;p&gt;My current rough thoughts on an API for this can be found in &lt;a href="http://github.com/simonw/djng/blob/c892dddf064d5542c17119d02920ea4f5e9dd7f5/services_api_ideas.txt"&gt;services_api_ideas.txt&lt;/a&gt;. I'm eager to hear suggestions on how to tackle this problem.&lt;/p&gt;

&lt;p&gt;djng is very much an experiment at the moment - I wouldn't suggest building anything against it unless you're willing to maintain your own fork. That said, the code is all on GitHub partly because I want people to fork it and experiment with their own API concepts as much as possible.&lt;/p&gt;

&lt;p&gt;If you're interested in exploring these concepts with me, please join me on the brand new &lt;a href="http://groups.google.com/group/djng"&gt;djng mailing list&lt;/a&gt;.&lt;/p&gt;


&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/May/19/djng/#comments"&gt;&lt;img src="http://simonwillison.net/2009/May/19/djng/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="django" /><category term="djangoheresies" /><category term="djng" /><category term="eurodjangocon" /><category term="github" /><category term="php" /><category term="projects" /><category term="python" /><category term="services" /><category term="settingspy" /><category term="talks" /><category term="webframeworks" /></entry><entry><title>rev=canonical bookmarklet and designing shorter URLs</title><link href="http://simonwillison.net/2009/Apr/11/revcanonical/" rel="alternate" /><updated>2009-04-11T17:37:55Z</updated><id>http://simonwillison.net/2009/Apr/11/revcanonical/</id><summary type="html">&lt;p&gt;I've watched the proliferation of URL shortening services over the past year with a certain amount of dismay. I care about the health of the web and try to ensure that URLs I am responsible will last for as long as possible, and I think it's very unlikely that all of these new services will still be around in twenty years time. Last month &lt;a href="http://simonwillison.net/2009/Mar/8/twitter/"&gt;I suggested&lt;/a&gt; that the Internet Archive start mirroring redirect databases, and last week I was &lt;a href="http://simonwillison.net/2009/Apr/3/tinyurl/"&gt;pleased to hear&lt;/a&gt; that Archiveteam, a different organisation, had &lt;a href="http://archiveteam.org/index.php?title=TinyURL"&gt;already started crawling&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The most recent discussion was kicked off by &lt;a href="http://joshua.schachter.org/2009/04/on-url-shorteners.html"&gt;Joshua Schachter&lt;/a&gt; and &lt;a href="http://www.scripting.com/stories/2009/03/07/solvingTheTinyurlCentraliz.html"&gt;Dave Winer&lt;/a&gt;, and &lt;a href="http://laughingmeme.org/2009/04/03/url-shortening-hinting/" title="URL Shortening Hinting"&gt;a solution has emerged&lt;/a&gt; driven by some lightning fast hacking by Kellan Elliott-McCrea. The idea is simple: sites get to chose their preferred source of shortened URLs (including self-hosted solutions) and specify it from individual pages using &lt;code&gt;&amp;lt;link rev="canonical" href="... shorter URL here ..."&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By hosting their own shorteners, the reliability should match that of the host site - and the amount of damage caused by a major shortener going missing can be dramatically reduced.&lt;/p&gt;

&lt;p&gt;I've been experimenting with this new pattern today. Here are a few small contributions to the wider discussion.&lt;/p&gt;

&lt;h4&gt;A URL shortening bookmarklet&lt;/h4&gt;

&lt;p&gt;Kellan's &lt;a href="http://revcanonical.appspot.com/"&gt;rev=canonical service&lt;/a&gt; exposes rev=canonical links using a server-side script running on App Engine. An obvious next step is to distil that logic in to a bookmarklet. I decided to combine the rev=canonical logic with my &lt;a href="http://simonwillison.net/2008/Aug/27/jsontinyurl/"&gt;json-tinyurl&lt;/a&gt; web service (also on App Engine), which allows browsers to lookup or create TinyURLs using a cross-domain JSONP request. The resulting bookmarklet will display the site's rev=canonical link if it exists, or create and display a TinyURL link otherwise:&lt;/p&gt;

&lt;p&gt;Bookmarklet: &lt;a href="javascript:(function(){var url=document.location;var links=document.getElementsByTagName('link');var found=0;for(var i = 0, l; l = links[i]; i++){if(l.getAttribute('rev')=='canonical'||(/alternateshort/).exec(l.getAttribute('rel'))) {found=l.getAttribute('href');break;}}if (!found) {for (var i = 0; l = document.links[i]; i++) {if (l.getAttribute('rev') == 'canonical') {found = l.getAttribute('href');break;}}}if (found) {prompt('URL:', found);} else {window.onTinyUrlGot = function(r) {if (r.ok) {prompt('URL:', r.tinyurl);} else {alert('Could not shorten with tinyurl');}};var s = document.createElement('script');s.type='text/javascript';s.src='http://json-tinyurl.appspot.com/?callback=onTinyUrlGot&amp;amp;url=' +document.location;document.getElementsByTagName('head')[0].appendChild(s);}})();"&gt;Shorten&lt;/a&gt; (drag to your browser toolbar)&lt;/p&gt;

&lt;p&gt;You can also grab the &lt;a href="http://gist.github.com/93591"&gt;uncompressed source code&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;Designing short URLs&lt;/h4&gt;

&lt;p&gt;I've also implemented rev=canonical on this site. I ended up buying a new domain for this, since simonwillison.net is both difficult to spell and 17 characters long. I ended up going with swtiny.eu - 9 characters, and keeping tiny in the domain helps people guess the nature of the site from just the URLs it generates. Be warned: the DNS doesn't appear to have finished resolving yet.&lt;/p&gt;

&lt;p&gt;For the path component, I turned to a variant of base 62 encoding. Decimal integers are represented using 10 digits (0-9), but base 62 uses those digits plus the letters of the alphabet in both lower and upper case. A 13 character integer such as 7250397214971 compresses down to just 8 characters (CDeIPpOD) using base62. My &lt;a href="http://www.djangosnippets.org/snippets/1431/"&gt;baseconv.py module&lt;/a&gt; implements base62, among others. I considered using base 57 by excluding o, O, 0, 1 and l as being too easily confused but decided against it.&lt;/p&gt;

&lt;p&gt;This site has three key types of content: entries, blogmarks and quotations. Each one is a separate Django model, and hence each has its own underlying database table and individual ID sequence. Since the IDs overlap, I need a way of separating out the shortened URLs for each content type.&lt;/p&gt;

&lt;p&gt;I decided to spend a byte on namespacing my shortened URLs. A prefix of E means an entry, Q means a quotation and B means a blogmark. For example:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;samp&gt;http://swtiny.eu/EZ8&lt;/samp&gt;: Entry with ID 1584&lt;/li&gt;
    &lt;li&gt;&lt;samp&gt;http://swtiny.eu/BBEQ&lt;/samp&gt;: Blogmark with ID 4108&lt;/li&gt;
    &lt;li&gt;&lt;samp&gt;http://swtiny.eu/QE5&lt;/samp&gt;: Quotation with ID 279&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By using upper case letters for the prefixes, I can later define custom paths starting with a lower case letter. I also have another 23 upper case prefix letters reserved in case I need them.&lt;/p&gt;

&lt;p&gt;I &lt;a href="http://twitter.com/simonw/status/1496864191"&gt;asked on Twitter&lt;/a&gt; and consensus opinion was that a 301 permanent redirect was the right thing to do (as opposed to a 302), both for SEO reasons and because the content will never exist at the shorter URL.&lt;/p&gt;

&lt;h4&gt;Implementation using Django and nginx&lt;/h4&gt;

&lt;p&gt;I run all of my Django sites using Apache and &lt;a href="http://code.google.com/p/modwsgi/"&gt;mod_wsgi&lt;/a&gt;, proxied behind &lt;a href="http://nginx.net/"&gt;nginx&lt;/a&gt;. Each site gets an Apache running on a high port, and nginx deals with virtual host configuration (proxying each domain to a different Apache backend) and static file serving. I didn't want to set up a full Django site just to run swtiny.eu, especially since my existing blog engine was required in order to resolve the shortened URLs.&lt;/p&gt;

&lt;p&gt;Instead, I implemented the shortened URL direction as just another view within my existing site: &lt;samp&gt;http://simonwillison.net/shorter/EZ8&lt;/samp&gt;. I then configured nginx to invisibly requests to &lt;samp&gt;swtiny.eu&lt;/samp&gt; through to that URL. The correct incantation took a while to figure out, so here's the relevant section of my nginx.conf:&lt;/p&gt;

&lt;pre&gt;&lt;code class="nginx-conf"&gt;server {
    listen 80;
    server_name www.swtiny.eu swtiny.eu;
    location / {
        rewrite (.*) /shorter$1 break;
        proxy_pass http://simonwillison.net;
        proxy_redirect off;
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;proxy_redirect off&lt;/code&gt; is needed to prevent nginx from replacing &lt;samp&gt;simonwillison.net&lt;/samp&gt; in the resulting location header with &lt;samp&gt;swtiny.eu&lt;/samp&gt;. My Django view code is relatively shonky, but if you're interested you can &lt;a href="http://www.djangosnippets.org/snippets/1430/"&gt;find it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The nice thing about this approach is that it makes it trivial to add custom URL shortening domains to other projects - a quick view function and a few lines of nginx configuration are all that is needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; The bookmarklet now supports the rev attribute on A elements as well - &lt;a href="http://simonwillison.net/2009/Apr/11/revcanonical/#c44088"&gt;thanks for the suggestion&lt;/a&gt;, Jeremy.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/Apr/11/revcanonical/#comments"&gt;&lt;img src="http://simonwillison.net/2009/Apr/11/revcanonical/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="bookmarklets" /><category term="davewiner" /><category term="django" /><category term="joshuaschachter" /><category term="kellanelliottmccrea" /><category term="projects" /><category term="python" /><category term="revcanonical" /><category term="tinyurl" /><category term="urls" /></entry><entry><title>List of SxSW 2009 panels with "social" in the title</title><link href="http://simonwillison.net/2009/Mar/14/sxsw/" rel="alternate" /><updated>2009-03-14T23:02:54Z</updated><id>http://simonwillison.net/2009/Mar/14/sxsw/</id><summary type="html">&lt;ul&gt;
	&lt;li&gt;A Hard Sell? Social Media &amp;amp; Your Boss&lt;/li&gt;
	&lt;li&gt;Can Social Media End Racism?&lt;/li&gt;
	&lt;li&gt;Digital Urbanites: How To Become Part of the New Social Capital&lt;/li&gt;
	&lt;li&gt;The Future Of Social Networks&lt;/li&gt;
	&lt;li&gt;How Social Networks Are Killing the Revolution&lt;/li&gt;
	&lt;li&gt;Making Whuffie: Raising Social Capital in Online Communities&lt;/li&gt;
	&lt;li&gt;The Mix at Six Hosted by Social Media Group&lt;/li&gt;
	&lt;li&gt;Mobile Social SXSW BBQ&lt;/li&gt;
	&lt;li&gt;My Boss Doesn't Get It: Championing Social Media to the Man&lt;/li&gt;
	&lt;li&gt;PBS' Interactive Social Media &amp;amp; Online Video Studio&lt;/li&gt;
	&lt;li&gt;The Search for a More Social Web&lt;/li&gt;
	&lt;li&gt;Security for the Social Set&lt;/li&gt;
	&lt;li&gt;Social Engineering: Scam Your Way Into Anything or From Anybody&lt;/li&gt;
	&lt;li&gt;Social Gamers: Away From the Keyboard&lt;/li&gt;
	&lt;li&gt;Social Media For Social Good&lt;/li&gt;
	&lt;li&gt;Social Media Marketing&lt;/li&gt;
	&lt;li&gt;Social Media Marketing: An Hour a Day&lt;/li&gt;
	&lt;li&gt;Social Media Nonprofit ROI Poetry Slam&lt;/li&gt;
	&lt;li&gt;Social Media: If You Liked it, Then You Should Have Put a Digg on it...&lt;/li&gt;
	&lt;li&gt;Social Networking in Health: e-Patients, Data &amp;amp; Privacy&lt;/li&gt;
	&lt;li&gt;Social Patterns and Antipatterns For the Win&lt;/li&gt;
	&lt;li&gt;Suxorz '09: The Ten Worst Social Media Campaigns&lt;/li&gt;
	&lt;li&gt;Twitter for Marketers: Is It Still Social Media?&lt;/li&gt;
	&lt;li&gt;Using GPS &amp;amp; Location to Enhance Social Networking&lt;/li&gt;
	&lt;li&gt;Using the New Digital Social Media to Accelerate Sustainability&lt;/li&gt;
&lt;/ul&gt;


&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/Mar/14/sxsw/#comments"&gt;&lt;img src="http://simonwillison.net/2009/Mar/14/sxsw/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="presentedwithoutcomment" /><category term="social" /><category term="socialmedia" /><category term="sxsw" /></entry><entry><title>A few notes on the Guardian Open Platform</title><link href="http://simonwillison.net/2009/Mar/10/openplatform/" rel="alternate" /><updated>2009-03-10T14:28:39Z</updated><id>http://simonwillison.net/2009/Mar/10/openplatform/</id><summary type="html">&lt;p&gt;This morning we launched the &lt;a href="http://www.guardian.co.uk/open-platform"&gt;Guardian Open Platform&lt;/a&gt; at a well attended event in our new offices in &lt;a href="http://www.kingsplace.co.uk/"&gt;Kings Place&lt;/a&gt;. This is one of the main projects I've been helping out with since joining the Guardian last year, and it's fantastic to finally have it out in the open.&lt;/p&gt;

&lt;p&gt;There are two components to the launch today: the Content API and the Data Store. I'll describe the Data Store first as it deserves not to get buried in the discussion about its larger cousin.&lt;/p&gt;

&lt;h4&gt;The Data Store&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://www.guardian.co.uk/profile/simonrogers"&gt;Simon Rogers&lt;/a&gt; is the Guardian news editor who is principally responsible for gathering data about the world. If you ever see an infographic in the paper, the chances are Simon had a hand in researching the data for it. His delicious feed is a &lt;a href="http://delicious.com/smfrogers"&gt;positive gold mine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As of today, a sizeable portion the data he collects for the newspaper will also be published online. As a starting point, we're publishing over &lt;a href="http://www.guardian.co.uk/data-store"&gt;80 data sets&lt;/a&gt;, all using Google Spreadsheets which means it's all accessible through the &lt;a href="http://code.google.com/apis/spreadsheets/overview.html"&gt;Spreadsheets Data API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's Simon's take on it, from &lt;a href="http://www.guardian.co.uk/news/datablog/2009/mar/10/blogpost1"&gt;Welcome to the Datablog&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote cite="http://www.guardian.co.uk/news/datablog/2009/mar/10/blogpost1"&gt;&lt;p&gt;Everyday we work with datasets from around the world. We have had to check this data and make sure it's the best we can get, from the most credible sources. But then it lives for the moment of the paper's publication and afterward disappears into a hard drive, rarely to emerge again before updating a year later.&lt;/p&gt;

&lt;p&gt;So, together with its companion site, the Data Store – a directory of all the stats we post – we are opening up that data for everyone. Whenever we come across something interesting or relevant or useful, we'll post it up here and let you know what we're planning to do with it.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;It's worth spending quite a while digging around the data. Most sets come with a full description, including where the data was sourced from. New data sets will be announced &lt;a href="http://www.guardian.co.uk/news/datablog"&gt;on the Datablog&lt;/a&gt;, which is cleverly subtitled "Facts are sacred".&lt;/p&gt;

&lt;h4&gt;The Content API&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://api.guardianapis.com/docs/"&gt;The Content API&lt;/a&gt; provides REST-ish access to over a million items of content, mostly from the last decade but with a few gems that are &lt;a href="http://www.guardian.co.uk/world/1944/aug/26/france.secondworldwar"&gt;a little bit older&lt;/a&gt;. Various types of content are available - article is the most common, but you can grab information (though not necessarily content) about audio, video, galleries and more. You can retrieve 50 items at a time, and pagination is unlimited (provided you stay below the API's rate limit).&lt;/p&gt;

&lt;p&gt;Articles are provided with their full body content, though this does not currently include any HTML tags (a known issue). It's a good idea to review &lt;a href="http://www.guardian.co.uk/open-platform/terms-and-conditions"&gt;our terms and conditions&lt;/a&gt;, but you should know that if you opt to republish our article bodies on your site we may ask you to include our ads alongside our content in the future.&lt;/p&gt;

&lt;p&gt;We serve 15 minute HTTP cache headers, but you are allowed to store our content for up to 24 hours. You really, really don't want to store content for longer than that, as in addition to violating our T&amp;amp;Cs you might find yourself inadvertently publishing an article that has been retracted for legal reasons. UK libel laws can be pretty scary.&lt;/p&gt;

&lt;p&gt;In addition to regular search, you can also filter our content using tags. Tags are a core aspect of the Guardian's &lt;a href="http://www.guardian.co.uk/help/insideguardian+series/an-abc-of-r2"&gt;R2 platform&lt;/a&gt;, being used for keywords, contributors, "series" (used to implement blogs), content types and more. Every item returned by the API includes tags, and the tags can be used to further filter the results.&lt;/p&gt;

&lt;p&gt;We also return a list of filters at the bottom of each page of search results showing the tags that could be used to filter that result set, ordered by the number of results (you may have seen this feature referred to as faceted search or guided navigation). Handy tip: you can use ?count=0 in your search API key to turn off results entirely and just get back the filters section. The race is on to be first to release a tag relationship browser based on this feature.&lt;/p&gt;

&lt;p&gt;API responses can be had in custom XML, JSON or Atom. The Atom format is the least mature at the moment, and we'd welcome suggestions for improving it from the community.&lt;/p&gt;

&lt;p&gt;I released &lt;a href="http://code.google.com/p/openplatform-python/"&gt;a Python client library&lt;/a&gt; for the API this morning, and we also have libraries for &lt;a href="http://code.google.com/p/openplatform-ruby/"&gt;Ruby&lt;/a&gt;, &lt;a href="http://code.google.com/p/openplatform-java/"&gt;Java&lt;/a&gt; and &lt;a href="http://code.google.com/p/openplatform-php/"&gt;PHP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also have an API Explorer (written in JavaScript and jQuery, hosted on the same domain as the API so that it can make Ajax requests) but you'll need an API key to try it out.&lt;/p&gt;

&lt;h4&gt;The bad news&lt;/h4&gt;

&lt;p&gt;The response to the API release has been terrific (check out what &lt;a href="http://www.tom-watson.co.uk/2009/03/guardian-open-platform/"&gt;Tom Watson&lt;/a&gt; had to say), but as a result it's likely that API key provisions will be significantly lower than the overall demand for them. Please bear with us while we work towards a more widely accessible release.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/Mar/10/openplatform/#comments"&gt;&lt;img src="http://simonwillison.net/2009/Mar/10/openplatform/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="apis" /><category term="atom" /><category term="contentapi" /><category term="data" /><category term="datastore" /><category term="guardian" /><category term="javascript" /><category term="journalism" /><category term="jquery" /><category term="json" /><category term="openplatform" /><category term="python" /><category term="simonrogers" /><category term="tomwatson" /><category term="xml" /></entry><entry><title>Pragmatism, purity and JSON content types</title><link href="http://simonwillison.net/2009/Feb/6/json/" rel="alternate" /><updated>2009-02-06T10:19:55Z</updated><id>http://simonwillison.net/2009/Feb/6/json/</id><summary type="html">&lt;p&gt;I started a conversation about this on Twitter the other day, but Twitter is a horrible place to have an archived discussion so I'm going to try again here.&lt;/p&gt;

&lt;p&gt;If you're producing a JSON API for other people to use (as opposed to an API that's only really meant for your own local Ajax responses), you need to decide which Content-Type to use. The best option is not entirely obvious.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.ietf.org/rfc/rfc4627.txt"&gt;RFC 4672&lt;/a&gt; defines JSON and reserves &lt;code&gt;application/json&lt;/code&gt; as the preferred media type. The problem is that most browsers will prompt you to download the file rather than displaying it inline (as they would for &lt;code&gt;text/plain&lt;/code&gt; or &lt;code&gt;application/javascript&lt;/code&gt;). One of my favourite qualities of REST-style APIs is that they enable exploration and debugging using just a browser - using &lt;code&gt;application/json&lt;/code&gt; throws a big, frustrating road block in the way. There are ways of telling your browser to treat &lt;code&gt;application/json&lt;/code&gt; in the same way as &lt;code&gt;text/plain&lt;/code&gt; but that doesn't really help you if your aim is to create an API that's easy for other developers to use.&lt;/p&gt;

&lt;p&gt;It's also worth mentioning that if you are returning JSONP (with an extra callback function wrapped around the JSON response to enable the dynamic script tag hack) you HAVE to serve as &lt;code&gt;application/javascript&lt;/code&gt; - otherwise the script you are providing won't be executed by the browser. Don't forget to include &lt;code&gt;charset=UTF8&lt;/code&gt; as well (for both types of response).&lt;/p&gt;

&lt;p&gt;So, it's pragmatism v.s. purity. The &lt;em&gt;correct&lt;/em&gt; thing to do is to return &lt;code&gt;application/json&lt;/code&gt;, but doing so makes your API harder for developers to use.&lt;/p&gt;

&lt;p&gt;In a brief, non-comprehensive review of some existing JSON APIs (FriendFeed, Flickr, Google Social Graph etc) I couldn't find any that were using &lt;code&gt;application/json&lt;/code&gt;, presumably for this exact reason.&lt;/p&gt;

&lt;h4&gt;Using the Accept: header&lt;/h4&gt;

&lt;p&gt;The Accept: header is one of my least favourite parts of HTTP. I like to be confident that if I send a URL to someone, they'll get back exactly the same bytes as I did when I retrieved it myself (I distrust language negotiation for the same reason). However, a number of people suggested it on Twitter and it looks like it could be a useful solution to this problem.&lt;/p&gt;

&lt;p&gt;I'm currently considering the following: ONLY use the &lt;code&gt;application/json&lt;/code&gt; Content-Type in reply to requests that include &lt;code&gt;application/json&lt;/code&gt; in their Accept header - essentially allowing clients that care about the correct content type to opt-in to receiving it. Everyone else (browsers included) gets &lt;code&gt;application/javascript&lt;/code&gt;, which is less correct (though not an all-out lie, since JSON is a subset of JavaScript) but solves the usability problem.&lt;/p&gt;

&lt;p&gt;A couple of things worry me about this. Firstly, is this a reasonable thing to use Accept for? Secondly, is there a chance that browsers might add &lt;code&gt;application/json&lt;/code&gt; to their Accept header at some point in the future? Safari currently sends &lt;code&gt;text/xml,application/xml,application/xhtml+xml,text/html; q=0.9,text/plain; q=0.8,image/png,*/*; q=0.5&lt;/code&gt; while Firefox sends &lt;code&gt;text/html,application/xhtml+xml,application/xml; q=0.9,*/*; q=0.8&lt;/code&gt;. Would it be smarter to look for &lt;code&gt;*/*&lt;/code&gt; and serve the incorrect Content-Type to those requests and the correct one to everything else?&lt;/p&gt;

&lt;p&gt;An alternative is to simply allow people to specify "JSON with a browsable Content-Type" as an alternative format option, or to enable a "pretty=1" query string argument which returns the response as &lt;code&gt;text/plain&lt;/code&gt; and potentially pretty prints it as well. I haven't yet decided if this is better than messing around with the Accept header.&lt;/p&gt;


&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/Feb/6/json/#comments"&gt;&lt;img src="http://simonwillison.net/2009/Feb/6/json/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="accept" /><category term="apis" /><category term="contenttypes" /><category term="http" /><category term="json" /><category term="pragmatism" /><category term="purity" /></entry><entry><title>Rate limiting with memcached</title><link href="http://simonwillison.net/2009/Jan/7/ratelimitcache/" rel="alternate" /><updated>2009-01-07T22:27:08Z</updated><id>http://simonwillison.net/2009/Jan/7/ratelimitcache/</id><summary type="html">&lt;p&gt;On Monday, several high profile "celebrity" Twitter accounts &lt;a href="http://www.techcrunch.com/2009/01/05/twitter-gets-hacked-badly/" title="Twitter Gets Hacked, Badly"&gt;started spouting nonsense&lt;/a&gt;, the victims of stolen passwords. Wired &lt;a href="http://blog.wired.com/27bstroke6/2009/01/professed-twitt.html" title="Weak Password Brings 'Happiness' to Twitter Hacker"&gt;has the full story&lt;/a&gt; - someone ran a dictionary attack against a Twitter staff member, discovered their password and used Twitter's admin tools to reset the passwords on the accounts they wanted to steal.&lt;/p&gt;

&lt;p&gt;The Twitter incident got me thinking about rate limiting again. I've been wanting a good general solution to this problem for quite a while, for API projects as well as security. Django Snippets has &lt;a href="http://www.djangosnippets.org/snippets/1083/" title="Decorator to limit request rates to individual views"&gt;an answer&lt;/a&gt;, but it works by storing access information in the database and requires you to run a periodic purge command to clean up the old records.&lt;/p&gt;

&lt;p&gt;I'm strongly averse to writing to the database for every hit. For most web applications reads scale easily, but writes don't. I also want to avoid filling my database with administrative gunk (I dislike database backed sessions for the same reason). But rate limiting relies on storing state, so there has to be some kind of persistence.&lt;/p&gt;

&lt;h4&gt;Using memcached counters&lt;/h4&gt;

&lt;p&gt;I think I've found a solution, thanks to memcached and in particular the &lt;samp&gt;incr&lt;/samp&gt; command. &lt;samp&gt;incr&lt;/samp&gt; lets you atomically increment an already existing counter, simply by specifying its key. &lt;samp&gt;add&lt;/samp&gt; can be used to create that counter - it will fail silently if the provided key already exists.&lt;/p&gt;

&lt;p&gt;Let's say we want to limit a user to 10 hits every minute. A naive implementation would be to create a memcached counter for hits from that user's IP address in a specific minute. The counter key might look like this:&lt;/p&gt;

&lt;pre&gt;&lt;samp&gt;ratelimit_72.26.203.98_2009-01-07-21:45&lt;/samp&gt;&lt;/pre&gt;

&lt;p&gt;Increment that counter for every hit, and if it exceeds 10 block the request.&lt;/p&gt;

&lt;p&gt;What if the user makes ten requests all in the last second of the minute, then another ten a second later? The rate limiter will let them off. For many cases this is probably acceptable, but we can improve things with a slightly more complex strategy. Let's say we want to allow up to 30 requests every five minutes. Instead of maintaining one counter, we can maintain five - one for each of the past five minutes (older counters than that are allowed to expire). After a few minutes we might end up with counters that look like this:&lt;/p&gt;

&lt;pre&gt;&lt;samp&gt;ratelimit_72.26.203.98_2009-01-07-21:45 = 13
ratelimit_72.26.203.98_2009-01-07-21:46 = 7
ratelimit_72.26.203.98_2009-01-07-21:47 = 11&lt;/samp&gt;&lt;/pre&gt;

&lt;p&gt;Now, on every request we work out the keys for the past five minutes and use &lt;samp&gt;get_multi&lt;/samp&gt; to retrieve them. If the sum of those counters exceeds the maximum allowed for that time period, we block the request.&lt;/p&gt;

&lt;p&gt;Are there any obvious flaws to this approach? I'm pretty happy with it - it cleans up after itself (old counters quietly expire from the cache), it shouldn't use much resources (just five active cache keys per unique IP address at any one time) and if the cache is lost the only snag is that a few clients might go slightly over their rate limit. I don't &lt;em&gt;think&lt;/em&gt; it's possible for an attacker to force the counters to expire early.&lt;/p&gt;

&lt;h4&gt;An implementation for Django&lt;/h4&gt;

&lt;p&gt;I've put together an &lt;a href="http://github.com/simonw/ratelimitcache/tree/master/ratelimitcache.py"&gt;example implementation of this algorithm&lt;/a&gt; using Django, hosted on GitHub. The &lt;a href="http://github.com/simonw/ratelimitcache/tree/master/readme.txt"&gt;readme.txt&lt;/a&gt; file shows how it works - basic usage is via a simple decorator:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;from ratelimitcache import ratelimit

@ratelimit(minutes = 3, requests = 20)
def myview(request):
    # ...
    return HttpResponse('...')&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Python decorators are typically functions, but &lt;code&gt;ratelimit&lt;/code&gt; is actually a class. This means it can be customised by subclassing it, and the class provides a number of methods designed to be over-ridden. I've provided an example of this in the module itself - ratelimit_post, a decorator which only limits on POST requests and can optionally couple the rate limiting to an individual POST field. Here's the complete implementation:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;class ratelimit_post(ratelimit):
    "Rate limit POSTs - can be used to protect a login form"
    key_field = None # If provided, this POST var will affect the rate limit
    
    def should_ratelimit(self, request):
        return request.method == 'POST'
    
    def key_extra(self, request):
        # IP address and key_field (if it is set)
        extra = super(ratelimit_post, self).key_extra(request)
        if self.key_field:
            value = sha.new(request.POST.get(self.key_field, '')).hexdigest()
            extra += '-' + value
        return extra&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here's how you would use it to limit the number of times a specific IP address can attempt to log in as a particular user:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;@ratelimit_post(minutes = 3, requests = 10, key_field = 'username')
def login(request):
    # ...
    return HttpResponse('...')&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;should_ratelimit()&lt;/code&gt; method is called before any other rate limiting logic. The default implementation returns True, but here we only want to apply rate limits to POST requests. The &lt;code&gt;key_extra()&lt;/code&gt; method is used to compose the keys used for the counter - by default this just includes the request's IP address, but in &lt;code&gt;ratelimit_post&lt;/code&gt; we can optionally include the value of a POST field (for example the username). We could include things like the request path here to apply different rate limit counters to different URLs.&lt;/p&gt;

&lt;p&gt;Finally, the readme.txt includes &lt;code&gt;ratelimit_with_logging&lt;/code&gt;, an example that over-rides the &lt;code&gt;disallowed()&lt;/code&gt; view returned when a rate limiting condition fails and writes an audit note to a database (less overhead than writing for every request).&lt;/p&gt;

&lt;p&gt;I've been a fan of customisation via subclassing ever since I got to know the new Django admin system, and I've been using it in a bunch of projects. It's a great way to create reusable pieces of code.&lt;/p&gt;


&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2009/Jan/7/ratelimitcache/#comments"&gt;&lt;img src="http://simonwillison.net/2009/Jan/7/ratelimitcache/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="counters" /><category term="django" /><category term="github" /><category term="memcached" /><category term="projects" /><category term="python" /><category term="ratelimitcache" /><category term="ratelimiting" /><category term="security" /><category term="twitter" /></entry><entry><title>DjangoCon and PyCon UK</title><link href="http://simonwillison.net/2008/Sep/15/conferences/" rel="alternate" /><updated>2008-09-15T15:20:20Z</updated><id>http://simonwillison.net/2008/Sep/15/conferences/</id><summary type="html">&lt;p&gt;September is a big month for conferences. &lt;a href="http://djangocon.org/"&gt;DjangoCon&lt;/a&gt; was a weekend ago in Mountain View (forcing me to miss both &lt;a href="http://2008.dconstruct.org/"&gt;d.Construct&lt;/a&gt; and &lt;a href="http://barcamp.org/BarCampBrighton3"&gt;BarCamp Brighton&lt;/a&gt;), &lt;a href="http://pyconuk.org/"&gt;PyCon UK&lt;/a&gt; was this weekend in Birmingham, I'm writing this from &lt;a href="http://vivabit.com/atmediaAjax/"&gt;@media Ajax&lt;/a&gt; and &lt;a href="http://barcamp.org/BarCampLondon5"&gt;BarCamp London 5&lt;/a&gt; is coming up over another weekend at the end of this month. As always, I've been posting details of upcoming talks and notes and materials from previous ones on &lt;a href="http://simonwillison.net/2008/talks/"&gt;my talks page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DjangoCon went really, really well. Huge thanks to conference chair &lt;a href="http://www.siudesign.co.uk/"&gt;Robert Lofthouse&lt;/a&gt; for pulling it all together in just two months and &lt;a href="http://www.hawthornlandings.org/"&gt;Leslie Hawthorne&lt;/a&gt; for making it all happen from Google's end. Google's facitilies were superb: the AV team were the best I've ever worked with and an army of Google volunteers made sure everything went smoothly. It's hard to see how it could have gone better; the principle complaint we got was that at only two days it was hard to justify the travel, something which future DjangoCons will definitely address.&lt;/p&gt;

&lt;p&gt;Every session was recorded and the videos &lt;del&gt;should be going up on YouTube shortly&lt;/del&gt; &lt;a href="http://www.youtube.com/view_play_list?p=D415FAF806EC47A1"&gt;are now up on YouTube&lt;/a&gt;. For the impatient, you can subscribe to an &lt;a href="http://gdata.youtube.com/feeds/api/videos?vq=DjangoCon"&gt;Atom feed of a YouTube search for "DjangoCon"&lt;/a&gt;. I recommend starting with Cal Henderson's keynote &lt;a href="http://www.youtube.com/watch?v=i6Fr65PFqfk"&gt;"Why I hate Django"&lt;/a&gt; which was both funny and insightful in equal parts. Malcolm's talk on &lt;a href="http://www.youtube.com/watch?v=zlhyp5Ve2qk"&gt;ORM internals&lt;/a&gt; was another personal favourite.&lt;/p&gt;

&lt;p&gt;PyCon UK was the second I've attended, but last year I only stayed for the first day. This time I stuck around and was enormously impressed by the grassroots feel of the conference and the enthusiastic atmosphere. I presented &lt;a href="http://simonwillison.net/2008/talks/pyconuk-admin/"&gt;a tutorial on extending the Django admin&lt;/a&gt; and &lt;a href="http://simonwillison.net/2008/talks/pyconuk-zeppelins/"&gt;a lightning talk on Zeppelins&lt;/a&gt;, prepared two hours in advance after Jacob mentioned that the lightning talks were tending too much towards the technical side. It went down very well; I'm tempted to extend it to a half hour session for BarCamp London.&lt;/p&gt;

&lt;p&gt;Unlike most conferences I attend, PyCon tickets included a sit-down dinner for all attendees complete with a "dramatic lecture" on &lt;a href="http://en.wikipedia.org/wiki/Lunar_Society"&gt;the Lunar Society&lt;/a&gt; presented by &lt;a href="http://www.odyssey.dial.pipex.com/"&gt;Andrew Lound&lt;/a&gt;. This was a great fit for the conference, both for the Birmingham connection and the many analogies to the modern open source community - loose collaboration, patent concerns and what you might call an 18th century equivalent of the modern hacker ethic.&lt;/p&gt;

&lt;p&gt;Next year the PyCon UK team will be hosting EuroPython, and I'm certain they'll do an excellent job of it. Meanwhile, Rob has already started making plans for a Euro DjangoCon in around six months time, probably taking place in Prague.&lt;/p&gt;


&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/Sep/15/conferences/#comments"&gt;&lt;img src="http://simonwillison.net/2008/Sep/15/conferences/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="andrewlound" /><category term="barcamplondon5" /><category term="calhenderson" /><category term="conferences" /><category term="django" /><category term="djangocon" /><category term="djangocon08" /><category term="google" /><category term="lesliehawthorne" /><category term="pyconuk" /><category term="pyconuk08" /><category term="python" /><category term="robertlofthouse" /><category term="speaking" /><category term="zeppelins" /></entry><entry><title>Announcing dmigrations</title><link href="http://simonwillison.net/2008/Sep/3/dmigrations/" rel="alternate" /><updated>2008-09-03T19:17:01Z</updated><id>http://simonwillison.net/2008/Sep/3/dmigrations/</id><summary type="html">&lt;p&gt;The team at &lt;a href="http://www.thisisglobal.com/"&gt;Global Radio&lt;/a&gt; (formerly &lt;a href="http://www.gcapmedia.com/"&gt;GCap Media&lt;/a&gt;) is the largest group of Django developers I've personally worked with, consisting of 14 developers split into two scrum teams, all contributing to the same overall codebase.&lt;/p&gt;

&lt;p&gt;Working with that many developers makes smart tools and processes essential, and in some cases we've had to develop our own. Today, we're releasing one of them as an open source project.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://code.google.com/p/dmigrations/"&gt;dmigrations&lt;/a&gt; is a Django migrations tool. It addresses a common problem in Django development: if you change a model after creating the database tables for it with &lt;code&gt;syncdb&lt;/code&gt;, how do you reflect those changes in your database tables without blowing away your existing data and starting again from scratch?&lt;/p&gt;

&lt;p&gt;&lt;a href="http://code.google.com/p/django-evolution/"&gt;django-evolution&lt;/a&gt; attempts to address this problem the clever way, by detecting changes to models that are not yet reflected in the database schema and figuring out what needs to be done to bring the two back in sync. In contrast, dmigrations takes the stupid approach: it requires you to explicitly state the changes in a sequence of migrations, which will be applied in turn to bring a database up to the most recent state that reflects the underlying models.&lt;/p&gt;

&lt;p&gt;This means extra work for developers who create migrations, but it also makes the whole process completely transparent - for our projects, we decided to go with the simplest system that could possibly work.&lt;/p&gt;

&lt;p&gt;The interface to dmigrations is a pair of custom Django commands. The first, &lt;code&gt;./manage.py dmigrate&lt;/code&gt;, provides a set of command for listing, applying and unapplying (reverting) migrations. This entirely replaces Django's &lt;code&gt;syncdb&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;The second, &lt;code&gt;./manage.py dmigration&lt;/code&gt;, provides commands for code-generating new migrations. It turns out that most migrations fit a set of common patterns: add a new table, add the tables for a new Django application, add a column to an existing table, add an index. These common cases are handled by dmigration; if you want to do something more complex (rename a column while transforming its data for example) you'll need to write a custom migration class.&lt;/p&gt;

&lt;p&gt;The &lt;a href="http://code.google.com/p/dmigrations/wiki/DmigrationsTutorial"&gt;dmigrations tutorial&lt;/a&gt; provides a full introduction to both of these commands, as well as hints on writing your own custom migrations. Since migrations are just classes, one of our hopes is that external developers will write extra migration classes for operations like "rename column" - things that currently require a one-off custom migration.&lt;/p&gt;

&lt;p&gt;dmigrations is actually the third iteration of our in-house migrations system. The first, smigrations, was designed to do the least amount of work possible to give us a controlled way of applying changes to our database schemas. The 's' stood for 'simple'. The second version (dmigrations) written by &lt;a href="http://t-a-w.blogspot.com/"&gt;Tomasz Wegrzanowski&lt;/a&gt; consisted of a major upgrade to smigrations that addressed many of the frustrations we found when using it with branched development, in particular the problem of migrations in two branches conflicting with each other. The 'd' stood for 'distributed'.&lt;/p&gt;

&lt;p&gt;Version three, released today, is my refactoring of dmigrations to de-couple it from the rest of our codebase. I've also stubbed out hooks for adding support for alternative database engines; dmigrations only supports MySQL out of the box, but I'm keen on getting it working with other databases now that it's out in the wild. Patches welcome!&lt;/p&gt;

&lt;p&gt;How does this fit in with &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt; and &lt;a href="http://code.google.com/p/django-evolution/"&gt;django-evolution&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;That's an excellent question. We'll be discussing all three systems on the schema evolution panel at &lt;a href="http://djangocon.org/"&gt;DjangoCon&lt;/a&gt; this weekend. I would love to see co-operation between the projects; at the very least I'd like to see the emergence of a standard Django-style abstraction library for create/alter table statements (something we punted on entirely with dmigrations). You'll certainly be hearing a lot more about migrations in Django after the conference.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/Sep/3/dmigrations/#comments"&gt;&lt;img src="http://simonwillison.net/2008/Sep/3/dmigrations/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="django" /><category term="dmigrations" /><category term="gcap" /><category term="globalradio" /><category term="opensource" /><category term="projects" /><category term="python" /></entry><entry><title>Back to full-time employment</title><link href="http://simonwillison.net/2008/Aug/22/employment/" rel="alternate" /><updated>2008-08-22T17:26:39Z</updated><id>http://simonwillison.net/2008/Aug/22/employment/</id><summary type="html">&lt;p&gt;I've been freelance for a year and a half now, and it's been a great deal of fun. For me, being freelance meant having the freedom to pursue all sorts of different interests - technical writing, public speaking, Django, OpenID, JavaScript - and the opportunity to work with some really fantastic people.&lt;/p&gt;

&lt;p&gt;It was going to take a very special opportunity to pull me back in to full-time employment, but I believe I've found that opportunity at &lt;a href="http://www.guardian.co.uk/"&gt;the Guardian&lt;/a&gt;. I'll be joining them full time (well, four days a week) in mid-October as a software architect, collaborating with their development team on some ambitious API projects. The Guardian have access to a &lt;em&gt;lot&lt;/em&gt; of interesting data and I can't wait to get stuck in to it. Since they're a newspaper, it shouldn't be a surprise that they &lt;a href="http://www.guardian.co.uk/media/2008/aug/22/guardianmediagroup.digitalmedia"&gt;scooped me to the story&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'll be particularly sorry to say good-bye to the outstanding team I've been working with at &lt;a href="http://www.gcapmedia.com/"&gt;GCap&lt;/a&gt;. I'm looking forward to talking about some of the things we've been working together over the next few weeks.&lt;/p&gt;


&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/Aug/22/employment/#comments"&gt;&lt;img src="http://simonwillison.net/2008/Aug/22/employment/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="employment" /><category term="gcap" /><category term="guardian" /><category term="jobs" /><category term="personal" /></entry><entry><title>The point of "Open" in OpenID</title><link href="http://simonwillison.net/2008/Jun/24/openid/" rel="alternate" /><updated>2008-06-24T08:12:23Z</updated><id>http://simonwillison.net/2008/Jun/24/openid/</id><summary type="html">&lt;p&gt;TechCrunch report that &lt;a href="http://www.techcrunch.com/2008/06/23/microsofts-first-step-in-accepting-openid-signons-healthvault/"&gt;Microsoft are accepting OpenID&lt;/a&gt; for their new &lt;a href="http://www.healthvault.com/"&gt;HealthVault site&lt;/a&gt;, but with a catch: you can only use OpenIDs from two providers: &lt;a href="http://www.trustbearer.com/"&gt;Trustbearer&lt;/a&gt; (who offer two-factor authentication using a hardware token) and Verisign. "Whatever happened to the &lt;em&gt;Open&lt;/em&gt; in OpenID?", asks TechCrunch's Jason Kincaid.&lt;/p&gt;

&lt;p&gt;Microsoft's decision is a beautiful example of the Open in action, and I fully support it.&lt;/p&gt;

&lt;p&gt;You have to remember that behind the excitement and marketing OpenID is a protocol, just like SMTP or HTTP. All OpenID actually provides is a mechanism for asserting ownership over a URL and then "proving" that assertion. We can build a pyramid of interesting things on top of this, but that assertion is really all OpenID gives us (well, that and a globally unique identifier). In internet theory terms, it's a &lt;a href="http://en.wikipedia.org/wiki/Dumb_network"&gt;dumb network&lt;/a&gt;: the protocol just concentrates on passing assertions around; it's up to the endpoints to set policies and invent interesting applications.&lt;/p&gt;

&lt;p&gt;Open means that providers and consumers are free to use the protocol in whatever way they wish. If they want to only accept OpenID from a trusted subset of providers, they can go ahead. If they only want to pass OpenID details around behind the corporate firewall (great for gluing together an SSO network from open-source components) they can knock themselves out. Just like SMTP or HTTP, the protocol does not imply any rules about where or how it should be used.&lt;/p&gt;

&lt;p&gt;HealthVault have clearly made this decision due to security concerns - not over the OpenID protocol itself, but the providers that their users might choose to trust. By accepting OpenID on your site you are &lt;em&gt;outsourcing the security of your users&lt;/em&gt; to an unknown third party, and you can't guarantee that your users picked a good home for their OpenID. If you're a bank or a healthcare provider that's not a risk you want to take; whitelisting providers that you have audited for security means you don't have to rule out OpenID entirely.&lt;/p&gt;

&lt;p&gt;I have a very simple rule of thumb for whether or not a site should consider whitelisting OpenID providers: does the site offer a "forgotten password" feature that e-mails the user a login token? If it does, then the owners have already made the decision to outsource the security of their users to whoever they picked as an e-mail provider. If they don't (banks are a good example here) they should continue that policy decision and consider using an OpenID provider whitelist.&lt;/p&gt;

&lt;p&gt;I've been using the example of banks potentially accepting OpenID only from security audited providers in my &lt;a href="http://simonwillison.net/talks/openid/"&gt;talks on OpenID&lt;/a&gt; for at least the past year. Now I can finally provide a real-world example.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/Jun/24/openid/#comments"&gt;&lt;img src="http://simonwillison.net/2008/Jun/24/openid/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="dumbnetworks" /><category term="healthvault" /><category term="microsoft" /><category term="open" /><category term="openid" /><category term="security" /><category term="techcrunch" /><category term="trustbearer" /><category term="verisign" /></entry><entry><title>Debugging Django</title><link href="http://simonwillison.net/2008/May/22/debugging/" rel="alternate" /><updated>2008-05-22T00:35:40Z</updated><id>http://simonwillison.net/2008/May/22/debugging/</id><summary type="html">&lt;p&gt;I gave a talk on Debugging Django applications at &lt;a href="http://upcoming.yahoo.com/event/546918/"&gt;Monday's inaugural meeting&lt;/a&gt; of DJUGL, the London Django Users Group. I wanted to talk about something that wasn't particularly well documented elsewhere, so I pitched the talk as "Bug Driven Development" - what happens when Test Driven Development goes the way of &lt;a href="http://www.shipmentoffail.com/fails/2008/04/horse-vs-car-fail/"&gt;this unfortunate pony&lt;/a&gt;.&lt;/p&gt;

The slides &lt;a href="http://www.slideshare.net/simon/debugging-django/"&gt;are up on SlideShare&lt;/a&gt;, but don't provide quite enough context so I'm going to cover the tips in full here.

&lt;h4&gt;Making the most of the error page&lt;/h4&gt;

&lt;p&gt;Django's default error page is great - it provides a detailed traceback with local variables, lets you expand out the lines of code around the problem, provides a plain text exception suitable for e-mailing to colleagues and even a one-click button to send details to &lt;a href="http://dpaste.com/"&gt;http://dpaste.com/&lt;/a&gt; so you can go and talk about the error on IRC. It also serves the same purpose as &lt;a href="http://www.php.net/phpinfo"&gt;phpinfo()&lt;/a&gt; - it shows you your application's settings, the GET, POST and COOKIE data from the request and the all important META fields assembled from the HTTP environment (great for remembering how to miss-spell HTTP_REFERER).&lt;/p&gt;

&lt;p&gt;Useful tip number one is that you can trigger the error page from any view just by adding the following line:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;assert False&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can serve up an expression with the assertion as well; it will be displayed at the top of the error page:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;assert False, request.GET&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One particularly useful place to use this is when you are building a complex form. If you want to see the data that was submitted, drop an assert False in to the view that the form targets and use the error page to inspect the data.&lt;/p&gt;

&lt;h4&gt;Logging to the development server console&lt;/h4&gt;

&lt;p&gt;If you want to know what's going on inside a view, the quickest way is to drop in a print statement. The development server outputs any print statements directly to the terminal; it's the server-side alternative to a JavaScript alert().&lt;/p&gt;

&lt;p&gt;If you want to be a bit more sophisticated with your logging, it's worth turning to Python's logging module (part of the standard library). You can configure it in your settings.py:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import logging
logging.basicConfig(
    level = logging.DEBUG,
    format = '%(asctime)s %(levelname)s %(message)s',
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then call it from any of your views:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;def my_view(request):
    import logging
    logging.debug("A log message")
    ...&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again, this will log things to the terminal where the development server is running. If you want to log things to a file you can do so by extending the basicConfig call:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;logging.basicConfig(
    level = logging.DEBUG,
    format = '%(asctime)s %(levelname)s %(message)s',
    filename = '/tmp/myapp.log',
    filemode = 'w'
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can then use &lt;samp&gt;tail -f /tmp/myapp.log&lt;/samp&gt; to see log lines being appended to that file in real-time. This can be used in production as well as development.&lt;/p&gt;

&lt;p&gt;The above just scratches the surface of Python's logging module; with a bit of &lt;a href="http://docs.python.org/lib/module-logging.html" title="Loggingi facility for Python"&gt;digging around in the documentation&lt;/a&gt; you can use it to rotate log files, send log messages over the network and even POST log events to an HTTP server somewhere.&lt;/p&gt;

&lt;p&gt;Often you find yourself dealing with an error that only occurs in certain circumstances - a function might be called from dozens of different places in your program but only runs in to trouble in a very specific case. You can use the &lt;a href="http://docs.python.org/lib/module-traceback.html"&gt;traceback module&lt;/a&gt; to log the current stack, which will allow you to tell how a function was called when something went wrong:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import logging, traceback, pprint

def my_buggy_function(arg):
    ...
    if error_condition:
        stack = pprint.pformat(traceback.extract_stack())
        logging.debug('An error occurred: %s' % stack)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The tuple returned by &lt;code class="python"&gt;traceback.extract_stack()&lt;/code&gt; includes line numbers, function names and paths to Python files so you can use it to reconstruct a good amount of information about your program.&lt;/p&gt;

&lt;h4&gt;Using the debugger&lt;/h4&gt;

&lt;p&gt;By far the most powerful weapon in my debugging toolkit is the Python debugger, &lt;a href="http://docs.python.org/lib/module-pdb.html"&gt;pdb&lt;/a&gt;. Again, this ships with the standard library so there's nothing extra to install. pdb is a command line debugger (if you want a GUI options include &lt;a href="http://sourceforge.net/projects/pyeclipse/"&gt;PyEclipse&lt;/a&gt; and &lt;a href="http://www.activestate.com/Products/komodo_ide/index.mhtml"&gt;Komodo&lt;/a&gt;, but I haven't used either myself). There are a bunch of ways to activate pdb, but the most straight forward is to simply drop the following line directly in to a Django view function:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import pdb; pdb.set_trace()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you try to load that page in your browser, the browser will hang - the page will appear to be loading extremely slowly. What's actually happened is the developer server has paused execution and thrown up the pdb interface - you can switch over to your console and start interacting directly with the server mid view.&lt;/p&gt;

&lt;p&gt;Did I mention you should never, ever leave this on in production?&lt;/p&gt;

&lt;p&gt;So, you've got a hung development server and a pdb prompt. What can you do with it? The answer is pretty much anything. I won't provide a full pdb tutorial here (&lt;a href="http://www.onlamp.com/pub/a/python/2005/09/01/debugger.html"&gt;this is a good introduction&lt;/a&gt;), but the commands I find most useful are the following:&lt;/p&gt;

&lt;dl&gt;
    &lt;dt&gt;list&lt;/dt&gt;
    &lt;dd&gt;Shows the lines of source code around your current point of execution. You can run it multiple times to increase the amount of source code displayed.&lt;/dd&gt;
    &lt;dt&gt;n&lt;/dt&gt;
    &lt;dd&gt;Execute the next line&lt;/dd&gt;
    &lt;dt&gt;s&lt;/dt&gt;
    &lt;dd&gt;Same as n, but steps in to any functions that are called. You can quickly get lost in a twisty maze of code with this command, but that's OK because...&lt;/dd&gt;
    &lt;dt&gt;r&lt;/dt&gt;
    &lt;dd&gt;Continues execution until the current function returns&lt;/dd&gt;
    &lt;dt&gt;u&lt;/dt&gt;
    &lt;dd&gt;Goes UP one level in the stack - so you can see the function that called the function you are currently in&lt;/dd&gt;
    &lt;dt&gt;d&lt;/dt&gt;
    &lt;dd&gt;Goes DOWN again&lt;/dd&gt;
    &lt;dt&gt;locals()&lt;/dt&gt;
    &lt;dd&gt;not a pdb command, but handy for seeing what's in your current scope&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;The pdb docs have &lt;a href="http://www.python.org/doc/current/lib/debugger-commands.html"&gt;a full list of commands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The pdb prompt doubles up as a full Python interactive shell, so you can not only access variables but you can modify them, call functions and generally mess around with the internals of your application as much as you like, while it's running. It's kind of a poor man's imitation of being a Smalltalk developer.&lt;/p&gt;

&lt;p&gt;Remember though, the whole time you're messing around in pdb your browser is still stuck there, waiting for the HTTP request to come back. If you hit "c" (for continue) your application will kick in again, the request will be served and your browser will breathe a sigh of relief.&lt;/p&gt;

&lt;p&gt;Thankfully you don't have to use pdb in a way that freezes your development server; it also works great in the interactive shell. If you've got a buggy function, one way to explore it is to run it interactively, then use the following idiom:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; def function_that_raises_an_exception():
...   assert False
... 
&amp;gt;&amp;gt;&amp;gt; function_that_raises_an_exception()
Traceback (most recent call last):
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 2, in function_that_raises_an_exception
AssertionError
&amp;gt;&amp;gt;&amp;gt; import pdb; pdb.pm()
&amp;gt; &amp;lt;stdin&amp;gt;(2)function_that_raises_an_exception()
(Pdb)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;pdb.pm()&lt;/code&gt; stands for post-mortem, and is probably my favourite feature of the debugger - it lets you jump back in to debug the most recently raised exception, even if you hadn't imported pdb at the time the exception was raised.&lt;/p&gt;

&lt;p&gt;One last pdb tip: you can use it to debug Python command line scripts such as Django's custom &lt;samp&gt;./manage.py&lt;/samp&gt; commands. The trick is to run the script like this:&lt;/p&gt;

&lt;p&gt;&lt;samp&gt;python -i manage.py buggy_command&lt;/samp&gt;&lt;/p&gt;

&lt;p&gt;The &lt;samp&gt;-i&lt;/samp&gt; argument causes Python to drop in to the interactive prompt after executing the script. If the script raised an exception, you can then use &lt;code&gt;pdb.pm()&lt;/code&gt; to debug it.&lt;/p&gt;

&lt;h4&gt;Handling errors in production&lt;/h4&gt;

&lt;p&gt;Django's default behaviour in production (that is, when the DEBUG setting is set to False) is to e-mail exception reports to anyone listed in the ADMINS section. You can also turn on e-mail reports on every 404 error with the SEND_BROKEN_LINK_EMAILS setting, which will send them to addresses in the MANAGERS setting. As far as I know these settings don't do anything else - they're a pretty ancient bit of Django.&lt;/p&gt;

&lt;p&gt;On a high traffic site you probably don't want to be e-mailed on every server error. One neat alternative is David Cramer's &lt;a href="http://code.google.com/p/django-db-log/"&gt;django-db-log&lt;/a&gt;, which logs exceptions to a database table. It cleverly uses an MD5 hash of the traceback to aggregate many reports of the same error. More importantly though, it acts as a really straight forward example of how to use Django middleware's process_exception hook to roll your own error reporting. Take a look &lt;a href="http://code.google.com/p/django-db-log/source/browse/trunk/djangodblog/__init__.py"&gt;at the code&lt;/a&gt; to see how simple this is.&lt;/p&gt;

&lt;h4&gt;More useful middleware&lt;/h4&gt;

&lt;p&gt;In the talk I demoed a couple of other handy pieces of middleware. The first was the &lt;a href="http://www.djangosnippets.org/snippets/727/"&gt;ProfilerMiddleware&lt;/a&gt; (one of several profiling tools on &lt;a href="http://www.djangosnippets.org/"&gt;Django Snippets&lt;/a&gt;) which allows you to add &lt;samp&gt;?prof&lt;/samp&gt; to the end of any URL to see the output of Python's &lt;a href="http://docs.python.org/lib/module-profile.html"&gt;cProfile module&lt;/a&gt; run against that request. The second is one that I've just released: &lt;a href="http://www.djangosnippets.org/snippets/766/"&gt;DebugFooter&lt;/a&gt;, which adds a footer showing exactly which templates were loaded from where (handy for debugging complex template paths) as well as every executed SQL query and how long each one took.&lt;/p&gt;

&lt;h4&gt;Abusing the test client&lt;/h4&gt;

&lt;p&gt;A final tip for exploring your application interactively is to learn to use &lt;a href="http://www.djangoproject.com/documentation/testing/"&gt;Django's TestClient&lt;/a&gt;. Although designed for use in unit tests, this tool is equally useful for use at the interactive prompt. It allows you to simulate an in-process request against your application from within your Python code. Here's an example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; from django.test.client import Client
&amp;gt;&amp;gt;&amp;gt; c = Client()
&amp;gt;&amp;gt;&amp;gt; response = c.get(&amp;quot;/&amp;quot;) # The homepage
&amp;gt;&amp;gt;&amp;gt; response
&amp;lt;django.http.HttpResponse object at 0x2300470&amp;gt;
&amp;gt;&amp;gt;&amp;gt; print response
Vary: Cookie
Content-Type: text/html; charset=utf-8

&amp;lt;!DOCTYPE HTML PUBLIC &amp;quot;-//W3C//DTD HTML 4.01//EN&amp;quot;
    &amp;quot;http://www.w3.org/TR/html4/strict.dtd&amp;quot;&amp;gt;
&amp;lt;html&amp;gt;
...&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The response object you get back is the &lt;code class="python"&gt;HttpResponse&lt;/code&gt; returned by the view, ready to be explored interactively.&lt;/p&gt;

&lt;p&gt;There's another function from the unit testing tools that can help with interactively exploring an application: &lt;code class="python"&gt;setup_test_environment()&lt;/code&gt;. This function monkey-patches in some additional hooks used by the unit tests, including one that intercepts template render calls and adds information on them to the request object. Here's an example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; from django.test.utils import setup_test_environment
&amp;gt;&amp;gt;&amp;gt; setup_test_environment()
&amp;gt;&amp;gt;&amp;gt; from django.test.client import Client
&amp;gt;&amp;gt;&amp;gt; c = Client()
&amp;gt;&amp;gt;&amp;gt; response = c.get(&amp;quot;/&amp;quot;)
&amp;gt;&amp;gt;&amp;gt; response.template
[&amp;lt;django.template.Template object at 0x2723dd0&amp;gt;,
 &amp;lt;django.template.Template object at 0x2723f30&amp;gt;,
 &amp;lt;django.template.Template object at 0x273ee10&amp;gt;]
&amp;gt;&amp;gt;&amp;gt; response.context
[ list of Context objects ]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This allows you to explore not just the HTML returned by a view, but also the templates and contexts that were used to render it.&lt;/p&gt;

&lt;h4&gt;Your tips welcome&lt;/h4&gt;

&lt;p&gt;If you have any useful tips on debugging Django applications, please share them in the comments on this entry.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/May/22/debugging/#comments"&gt;&lt;img src="http://simonwillison.net/2008/May/22/debugging/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="debugging" /><category term="django" /><category term="djugl" /><category term="middleware" /><category term="talks" /></entry><entry><title>jQuery style chaining with the Django ORM</title><link href="http://simonwillison.net/2008/May/1/orm/" rel="alternate" /><updated>2008-05-01T12:31:17Z</updated><id>http://simonwillison.net/2008/May/1/orm/</id><summary type="html">&lt;p&gt;Django's ORM is, in my opinion, the unsung gem of the framework. For the subset of SQL that's used in most web applications it's very hard to beat. It's a beautiful piece of API design, and I tip my hat to the people who designed and built it.&lt;/p&gt;

&lt;h4&gt;Lazy evaluation&lt;/h4&gt;

&lt;p&gt;If you haven't spent much time with the ORM, two key features are lazy evaluation and chaining. Consider the following statement:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;entries = Entry.objects.all()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Assuming you have created an Entry model of some sort, the above statement will create a Django QuerySet object representing all of the entries in the database. It will &lt;em&gt;not&lt;/em&gt; result in the execution of any SQL - QuerySets are lazily evaluated, and are only executed at the last possible moment. The most common situation in which SQL will be executed is when the object is used for iteration:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;for entry in entries:
    print entry.title&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This usually happens in a template:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;lt;ul&amp;gt;
{% for entry in entries %}
  &amp;lt;li&amp;gt;{{ entry.title }}&amp;lt;/li&amp;gt;
{% endfor %}
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lazy evaluation works nicely with  &lt;a href="http://www.djangoproject.com/documentation/cache/#template-fragment-caching"&gt;template fragment caching&lt;/a&gt; - even if you pass a QuerySet to a template it won't be executed if the fragment it is used in can be served from the cache.&lt;/p&gt;

&lt;p&gt;You can modify QuerySets as many times as you like before they are executed:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;entries = Entry.objects.all()
today = datetime.date.today()
entries_this_year = entries.filter(
    posted__year = today.year
)
entries_last_year = entries.filter(
    posted__year = today.year - 1
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again, no SQL has been executed, but we now have two QuerySets which, when iterated, will produce the desired result.&lt;/p&gt;

&lt;h4&gt;Chaining&lt;/h4&gt;

&lt;p&gt;Chaining comes in when you want to apply multiple modifications to a QuerySet. Here are blog entries from 2006 that weren't posted in January:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;Entry.objects.filter(
    posted__year = 2006
).exclude(posted__month = 1)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here's entries from that year posted to the category named "Personal", ordered by title:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;Entry.objects.filter(
    posted__year = 2006
).filter(
    category__name = "Personal"
).order_by('title')&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The above can also be expressed like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;Entry.objects.filter(
    posted__year = 2006,
    category__name = "Personal"
).order_by('title')&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Chaining in jQuery&lt;/h4&gt;

&lt;p&gt;The parallels to &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; are pretty clear. The jQuery API is built around chaining, and the jQuery &lt;a href="http://docs.jquery.com/Effects"&gt;animation library&lt;/a&gt; even uses a form of lazy evaluation to automatically queue up effects to run in sequence:&lt;/p&gt;

&lt;pre&gt;&lt;code class="javascript"&gt;jQuery('div#message').addClass(
	'borderfade'
).animate({
   'borderWidth': '+10px'
}, 1000).fadeOut();&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One of the neatest things about jQuery is the plugin model, which takes advantage of JavaScript's prototype inheritance and makes it trivially easy to add new chainable methods. If we wanted to package the above dumb effect up as a plugin, we could do so like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="javascript"&gt;jQuery.fn.dumbBorderFade = function() {
    return this.addClass(
        'borderfade'
    ).animate({
       'borderWidth': '+10px'
    }, 1000).fadeOut();
};&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now we can apply it to an element like so:&lt;/p&gt;

&lt;pre&gt;&lt;code class="javascript"&gt;jQuery('div#message').dumbBorderFade();&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Custom QuerySet methods in Django&lt;/h4&gt;

&lt;p&gt;Django supports adding custom methods for accessing the ORM through the ability to implement a custom &lt;a href="http://www.djangoproject.com/documentation/model-api/#managers"&gt;Manager&lt;/a&gt;. In the above examples, &lt;code class="python"&gt;Entry.objects&lt;/code&gt; is the Manager. The downside of this approach is that methods added to a manager can only be used at the beginning of the chain.&lt;/p&gt;

&lt;p&gt;Luckily, Managers also provide a hook for returning a custom QuerySet. This means we can create our own QuerySet subclass and add new methods to it, in a way that's reminiscent of jQuery:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;from django.db import models
from django.db.models.query import QuerySet
import datetime

class EntryQuerySet(QuerySet):
    def on_date(self, date):
        next = date + datetime.timedelta(days = 1)
        return self.filter(
            posted__gt = date,
            posted__lt = next
        )

class EntryManager(models.Manager):
    def get_query_set(self):
        return EntryQuerySet(self.model)

class Entry(models.Model):
    ...
    objects = EntryManager()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The above gives us a new method on the QuerySets returned by Entry.objects called on_date(), which lets us filter entries down to those posted on a specific date. Now we can run queries like the following:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;Entry.objects.filter(
    category__name = 'Personal'
).on_date(datetime.date(2008, 5, 1))&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Reducing the boilerplate&lt;/h4&gt;

&lt;p&gt;This method works fine, but it requires quite a bit of boilerplate code - a QuerySet subclass and a Manager subclass plus the wiring to pull them all together. Wouldn't it be neat if you could declare the extra QuerySet methods inside the model definition itself?&lt;/p&gt;

&lt;p&gt;It turns out you can, and it's surprisingly easy. Here's the syntax I came up with:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;from django.db.models.query import QuerySet

class Entry(models.Model):
   ...
   objects = QuerySetManager()
   ...
   class QuerySet(QuerySet):
       def on_date(self, date):
           return self.filter(
               ...
           )&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here I've made the custom QuerySet class an inner class of the model definition. I've also replaced the default manager with a QuerySetManager. All this class does is return the QuerySet inner class for the current model from get_query_set. The implementation looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;class QuerySetManager(models.Manager):
    def get_query_set(self):
        return self.model.QuerySet(self.model)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I'm pretty happy with this; it makes it trivial to add custom QuerySet methods and does so without any monkeypatching or deep reliance on Django ORM internals. I think the ease with which this can be achieved is a testament to the quality of the ORM API.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/May/1/orm/#comments"&gt;&lt;img src="http://simonwillison.net/2008/May/1/orm/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="api" /><category term="chaining" /><category term="django" /><category term="jquery" /><category term="orm" /><category term="python" /><category term="queryset" /></entry><entry><title>wikinear.com, OAuth and Fire Eagle</title><link href="http://simonwillison.net/2008/Mar/22/wikinear/" rel="alternate" /><updated>2008-03-22T14:34:53Z</updated><id>http://simonwillison.net/2008/Mar/22/wikinear/</id><summary type="html">&lt;p&gt;I'm pleased to announce &lt;a href="http://wikinear.com/"&gt;wikinear.com&lt;/a&gt;. It's a simple site that does just one thing: show you a list of the five Wikipedia pages that are geographically closest to your current location. It's designed (or not-designed) to be used mainly from mobile phones.&lt;/p&gt;

&lt;p&gt;You'll need a &lt;a href="http://fireeagle.yahoo.net/"&gt;Fire Eagle&lt;/a&gt; invitation code to use the site. &lt;del&gt;I've got four spare; the first four comments to ask for one can have them&lt;/del&gt; my invites are all accounted for. If you don't have a Fire Eagle account you'll have to make do with &lt;a href="http://www.flickr.com/photos/simon/2351435457/"&gt;this screenshot&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;The idea for the site came from living in Oxford for a year. The city is full of beautiful old historic buildings (many of them colleges), but very few of them are labelled or signposted. With wikinear.com and a GPS hooked up to Fire Eagle, I can pull out my phone and see a list of the closest points of interest, plotted on a handy map.&lt;/p&gt;

&lt;p&gt;Under the hood the site combines a number of interesting technologies: &lt;a href="http://oauth.net/"&gt;OAuth&lt;/a&gt;, &lt;a href="http://fireeagle.yahoo.net/"&gt;Fire Eagle&lt;/a&gt;, &lt;a href="http://www.geonames.org/"&gt;GeoNames&lt;/a&gt; and the new &lt;a href="http://code.google.com/apis/maps/documentation/staticmaps/"&gt;Google Static Maps API&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;OAuth&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://oauth.net/"&gt;OAuth&lt;/a&gt; was originally designed to solve a problem with OpenID: in an authentication protocol based on browser redirects, how do you authenticate a desktop or command-line application? As it turned out, the solution to that problem solved a bunch of other problems that are unrelated to OpenID, so OAuth now exists as very much its own thing. In essence, it lets users delegate permission to perform actions on their behalf, without having to hand their regular authentication credentials (e.g. username and password) over to a third-party piece of software.&lt;/p&gt;

&lt;p&gt;If you've ever used a Flickr application that sends you back to Flickr to ask permission to view your private photos you'll understand what OAuth does straight away. Before OAuth, sites had to invent their own solutions to this problem - complete with smart security measures, their own UI flow and libraries for developers wishing to access their protected APIs. OAuth provides a ready-made solution, complete with &lt;a href="http://oauth.net/code/"&gt;tested libraries in a bunch of languages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to securely expose your user's private data via an API, OAuth is a no-brainer. I expect to see a lot more of it over the next year.&lt;/p&gt;

&lt;h3&gt;Fire Eagle&lt;/h3&gt;

&lt;p&gt;Launched at ETech a few weeks ago, &lt;a href="http://fireeagle.yahoo.net/"&gt;Fire Eagle&lt;/a&gt; is a service with enormous potential. You can watch Tom Coates explain it in ten minutes in &lt;a href="http://developer.yahoo.net/blogs/theater/archives/2008/03/fire_eagle_launches.html" title="Fire Eagle Launches"&gt;this video from the conference&lt;/a&gt;, but the short version is that Fire Eagle acts as a &lt;em&gt;location broker&lt;/em&gt;. It consists of two key OAuth-protected APIs: one for setting the geographical location of a user, and another for retrieving that location.&lt;/p&gt;

&lt;p&gt;This leads to a neat separation of concerns. On the one hand are the applications that attempt to figure out your location - GPS receivers, WiFi maps, mobile phones that triangulate nearby cell towers, or even sites that know where you are because you told them (Dopplr and Upcoming, for example, or the Fire Eagle site itself). On the other hand are the applications that do something useful with your location - from restaurant review sites, traffic alert services, friend finders and &lt;a href="http://en.wikipedia.org/wiki/Alternate_reality_game"&gt;ARGs&lt;/a&gt; down to trivial applications like wikinear.com.&lt;/p&gt;

&lt;p&gt;As a developer, this is really exciting. I can build location-based services without having to solve the much bigger problem of figuring out where my users are. Even better, wikinear.com becomes incrementally more useful every time someone builds a new tool for passing location information to Fire Eagle, without me having to do anything at all.&lt;/p&gt;

&lt;p&gt;Obviously privacy is a huge concern when dealing with this kind of data. That's where the Fire Eagle application itself comes in: it provides a simple suite of tools for users to manage the applications that can access their location. Applications can be permitted to access different levels of accuracy or disabled entirely, and there's a "Hide" button for disabling all applications at once.&lt;/p&gt;

&lt;p&gt;Disclaimer: I worked on an early prototype of Fire Eagle as my last project at Yahoo! before leaving in January 2007, but the product that has launched has changed enormously and is entirely the work of the current Fire Eagle team. wikinear.com is inspired by part of that early prototype.&lt;/p&gt;

&lt;h3&gt;Wikipedia and GeoNames&lt;/h3&gt;

&lt;p&gt;Wikipedia has a thriving community of geo-hackers, mainly focused around the &lt;a href="http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Maps"&gt;Maps&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Geographical_coordinates"&gt;Geographical coordinates&lt;/a&gt; and &lt;a href="http://de.wikipedia.org/wiki/Wikipedia:WikiProjekt_Georeferenzierung/Wikipedia-World/en"&gt;Wikipedia-World&lt;/a&gt; wiki projects. Many Wikipedia pages (&lt;a href="http://en.wikipedia.org/wiki/Brighton"&gt;Brighton&lt;/a&gt;, for example) have their co-ordinates in the top-right, added using a bewildering array of macros and markup extensions. You can browse through the huge collection of geotagged pages using &lt;a href="http://maps.google.com/maps?q=http:%2F%2Ftools.wikimedia.de%2F~kolossos%2Fgeoworld%2FWP-world-maps.php%3Flang%3Den&amp;amp;ie=UTF8&amp;amp;om=1"&gt;this KML-powered Google Maps tool&lt;/a&gt; - zoom in and wait a few seconds to load in more markers.&lt;/p&gt;

&lt;p&gt;The wonderful &lt;a href="http://www.geonames.org/"&gt;GeoNames&lt;/a&gt; (also used on &lt;a href="http://djangopeople.net/"&gt;djangopeople.net&lt;/a&gt;) includes &lt;a href="http://www.geonames.org/wikipedia.html"&gt;an API for querying Wikipedia by location&lt;/a&gt;, based on 610,000 articles extracted from a Wikipedia data dump. This was a huge relief when I found it, as "order by distance from X" is actually pretty tricky to do efficiently; I've used expanding bounding box searches in the past but I'd love to hear about more effective solutions.&lt;/p&gt;

&lt;h3&gt;Google Static Maps&lt;/h3&gt;

&lt;p&gt;A long-term criticism of the Google Maps API is that it requires JavaScript to display anything at all - once you've committed to using it, you're going to have trouble implementing unobtrusive scripting (although you can &lt;a href="http://24ways.org/2007/unobtrusively-mapping-microformats-with-jquery" title="Unobtrusively Mapping Microformats with jQuery"&gt;work around the problem&lt;/a&gt; to some extent). Yahoo! Maps has long been better in this regard, but their &lt;a href="http://developer.yahoo.com/maps/rest/V1/mapImage.html"&gt;map image API&lt;/a&gt; is a bit of a pain to use - you have to do an initial call to get back the URL to an image embedded in an XML file, then extract that URL and send it to the browser.&lt;/p&gt;

&lt;p&gt;Launched &lt;a href="http://googlemapsapi.blogspot.com/2008/02/google-maps-without-scripting.html" title="Google Maps Without the Scripting"&gt;last month&lt;/a&gt;, Google's &lt;a href="http://code.google.com/apis/maps/documentation/staticmaps/"&gt;Static Maps API&lt;/a&gt; is a big improvement. As with &lt;a href="http://code.google.com/apis/chart/"&gt;Google Charts&lt;/a&gt;, you need only construct a URL to the image to have it dynamically generated on the fly. You can also specify markers, and optionally omit the initial latitude/longitude/zoom to indicate that you want a best fit for the markers you are displaying. There's even a flag for a "mobile optimised" image which I'm using for wikinear.com.&lt;/p&gt;

&lt;h3&gt;Mixing it all together&lt;/h3&gt;

&lt;p&gt;Excluding templates, the entire application comes in at less than 200 lines of code and took around two hours to build. The only persistence is a couple of cookies for storing Fire Eagle tokens; Django's database layer isn't even configured (and user locations aren't logged anywhere, which is great from a privacy point of view). I suppose it's a classic mashup - Fire Eagle + OAuth + Wikipedia + GeoNames + Google Static Maps = wikinear.com. Despite its simplicity (or maybe because if it), I think it's a neat demonstration of the kind of applications Fire Eagle enables.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/Mar/22/wikinear/#comments"&gt;&lt;img src="http://simonwillison.net/2008/Mar/22/wikinear/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="django" /><category term="fireeagle" /><category term="geonames" /><category term="googlemaps" /><category term="oauth" /><category term="wikinear" /><category term="wikipedia" /></entry><entry><title>Django People: OpenID and microformats</title><link href="http://simonwillison.net/2008/Jan/24/upgrade/" rel="alternate" /><updated>2008-01-24T02:02:19Z</updated><id>http://simonwillison.net/2008/Jan/24/upgrade/</id><summary type="html">&lt;p&gt;In hindsight, it was a mistake to launch &lt;a href="http://djangopeople.net/"&gt;Django People&lt;/a&gt; without support for &lt;a href="http://openid.net/"&gt;OpenID&lt;/a&gt;. It was on the original feature list, but in the end I decided to cut any feature that wasn't completely essential in order to get the site launched before it drowned in an ocean of "wouldn't-it-be-cool-ifs".&lt;/p&gt;

&lt;p&gt;I thought that, once launched, the site would see a small amount of activity from a few interested parties and I'd have plenty of time to catch up on the feature backlog. What I didn't expect was that &lt;a href="http://djangopeople.net/about/"&gt;over 750 people&lt;/a&gt; would create profiles within the first 24 hours!&lt;/p&gt;

&lt;p&gt;So, I spent a few hours this evening integrating my current development version of &lt;a href="http://code.google.com/p/django-openid/"&gt;django-openid&lt;/a&gt;, which thankfully had about 80% of the code needed to integrate with Django's built-in authentication mechanism already written. Sadly the other 20% is either incomplete or a bit of a mess, but I've checked it in to &lt;a href="http://django-openid.googlecode.com/svn/branches/auth-integration/"&gt;a branch on Google Code&lt;/a&gt; for anyone who's interested.&lt;/p&gt;

&lt;p&gt;Anyway, there are a few new features on the site of interest to OpenID users:&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;When &lt;a href="http://djangopeople.net/signup/"&gt;signing up for a new account&lt;/a&gt;, you now have the option to start by signing in with an OpenID. If you do this, you'll be able to complete the signup form without having to pick a password. If your OpenID provider supports simple registration the name, e-mail address and username fields will be filled in for you.&lt;/li&gt;
    &lt;li&gt;If you already have an existing account, you can &lt;a href="http://djangopeople.net/openid/associations/"&gt;associate one or more OpenIDs&lt;/a&gt; with that account. You'll then be able to use any of them to sign in to the account. Why multiple OpenIDs instead of just one? Two reasons: firstly, it opens the potential for doing interesting things with multiple OpenIDs from different providers in the future; secondly, it gives you a fallback for if one of your OpenID providers becomes unavailable.&lt;/li&gt;
    &lt;li&gt;You can freely add and remove OpenIDs from your associations, with one exception: the site won't let you delete your last OpenID if your account doesn't also have a password associated with it, to prevent you from locking yourself out.&lt;/li&gt;
    &lt;li&gt;While I decided that I didn't want Django People to become &lt;em&gt;yet another&lt;/em&gt; OpenID provider, I do want to give people the ability to use their profile page on the site as an OpenID - so that they can prove that they own it (see my &lt;a href="http://simonwillison.net/2008/Jan/7/projection/" title="Yahoo!, Flickr, OpenID and Identity Projection"&gt;recent post on identity projection&lt;/a&gt;). To that end, the new account settings page lets advanced OpenID users set up an &lt;code&gt;openid.server&lt;/code&gt; and &lt;code&gt;openid.delegate&lt;/code&gt; for their profile page, as described in &lt;a href="http://simonwillison.net/2006/Dec/19/openid/" title="How to turn your blog in to an OpenID"&gt;my blog entry&lt;/a&gt; from just over a year ago.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caveat: the site only supports OpenID 1.1, at least for the moment. I had originally planned to go for OpenID 2.0, but demand was such that I decided to get what I had up and running rather than digging in to the OpenID 2.0 libraries.&lt;/p&gt;

&lt;h3&gt;Microformats&lt;/h3&gt;

&lt;p&gt;While I was messing around with OpenID, &lt;a href="http://notes.natbat.net/"&gt;Natalie&lt;/a&gt; was updating the site's templates to clean up the crufty code I'd introduced and add some microformatted goodness. The site now uses &lt;a href="http://microformats.org/wiki/hcard"&gt;hCard&lt;/a&gt; where you would expect it (country listing pages, skill listing pages and the &lt;a href="http://djangopeople.net/search/"&gt;new search interface&lt;/a&gt;) and the profile pages have been updated with a healthy dose of &lt;a href="http://microformats.org/wiki/xfn"&gt;XFN&lt;/a&gt; (just rel="me", since there isn't a relevant microformat for "people who live nearby") and &lt;a href="http://microformats.org/wiki/rel-tag"&gt;Rel-Tag&lt;/a&gt;. On &lt;a href="http://adactio.com/"&gt;Jeremy Keith&lt;/a&gt;'s suggestion, the profile pages also use &lt;a href="http://microformats.org/wiki/hresume"&gt;hResume&lt;/a&gt; - all the more reason to add the Django projects you've worked on to your profile's portfolio.&lt;/p&gt;

&lt;p&gt;As usual, post feedback and bug reports as comments on this entry.&lt;/p&gt;

&lt;!-- &lt;p&gt;&lt;a href="http://simonwillison.net/2008/Jan/24/upgrade/#comments"&gt;&lt;img src="http://simonwillison.net/2008/Jan/24/upgrade/badge.png" alt="Number of comments"&gt;&lt;/a&gt;&lt;/p&gt; --&gt;
</summary><category term="django" /><category term="djangopeople" /><category term="hcard" /><category term="hresume" /><category term="identityprojection" /><category term="microformats" /><category term="openid" /><category term="python" /><category term="reltag" /><category term="xfn" /></entry></feed>
