Index: djangodocs/views.py
===================================================================
--- djangodocs/views.py	(revision 8718)
+++ djangodocs/views.py	(working copy)
@@ -5,6 +5,7 @@
 from django.core import urlresolvers
 from django.http import HttpResponseRedirect, Http404
 from django.shortcuts import render_to_response
+from django.template import RequestContext
 from unipath import FSPath as Path
 
 def index(request):
@@ -26,7 +27,7 @@
     )
 
 def document(request, lang, version, url):
-    if lang != 'en' or version != 'dev' or url == "search/": raise Http404()
+    if lang != 'en' or version != 'dev': raise Http404()
 
     docroot = Path(settings.DOCS_PICKLE_ROOT)
 
@@ -44,12 +45,13 @@
         'docs/%s.html' % '-'.join([b for b in bits if b]), 
         'docs/doc.html'
     ]
-    return render_to_response(template_names, {
-        'doc': pickle.load(open(doc)),
-        'env': pickle.load(open(docroot.child('globalcontext.pickle'))),
+    return render_to_response(template_names, RequestContext(request, {
+        'doc': pickle.load(open(doc, 'rb')),
+        'env': pickle.load(open(docroot.child('globalcontext.pickle'), 'rb')),
         'update_date': datetime.datetime.fromtimestamp(docroot.child('last_build').mtime()),
         'home': urlresolvers.reverse(document, kwargs={'lang':lang, 'version':version, 'url':''}),
-    })
+        'search': urlresolvers.reverse(search, kwargs={'lang':lang, 'version':version}),
+    }))
 
 def images(request, lang, version, path):
     if lang != 'en' or version != 'dev': raise Http404()
@@ -65,4 +67,23 @@
         request,
         document_root = Path(settings.DOCS_PICKLE_ROOT).child('_sources'),
         path = path,
-    )
\ No newline at end of file
+    )
+
+def search(request, lang, version):
+    if lang != 'en' or version != 'dev': raise Http404()
+    
+    docroot = Path(settings.DOCS_PICKLE_ROOT)
+    
+    # Remove the 'cof' GET variable from the query string so that the page
+    # linked to by the Javascript fallback doesn't think its inside an iframe.
+    mutable_get = request.GET.copy()
+    if 'cof' in mutable_get:
+        del mutable_get['cof']
+    
+    return render_to_response('docs/search.html', RequestContext(request, {
+        'query': request.GET.get('q'),
+        'query_string': mutable_get.urlencode(),
+        'env': pickle.load(open(docroot.child('globalcontext.pickle'), 'rb')),
+        'home': urlresolvers.reverse(document, kwargs={'lang':lang, 'version':version, 'url':''}),
+        'search': urlresolvers.reverse(search, kwargs={'lang':lang, 'version':version}),
+    }))
\ No newline at end of file
Index: djangodocs/settings.py
===================================================================
--- djangodocs/settings.py	(revision 8718)
+++ djangodocs/settings.py	(working copy)
@@ -1,8 +1,9 @@
 from django_website.settings import *
 
 PREPEND_WWW = False
-INSTALLED_APPS = []
+INSTALLED_APPS = ['djangodocs']
 TEMPLATE_DIRS = [os.path.join(os.path.dirname(__file__), "templates")] + TEMPLATE_DIRS
+TEMPLATE_CONTEXT_PROCESSORS = ['django.core.context_processors.request']
 ROOT_URLCONF = 'djangodocs.urls'
 CACHE_MIDDLEWARE_KEY_PREFIX = 'djangodocs'
 
Index: djangodocs/templatetags/docs.py
===================================================================
--- djangodocs/templatetags/docs.py	(revision 0)
+++ djangodocs/templatetags/docs.py	(revision 0)
@@ -0,0 +1,16 @@
+from django.template import Library
+
+from djangodocs.forms import SearchForm
+
+
+register = Library()
+
+@register.inclusion_tag('docs/search_form.html', takes_context=True)
+def search_form(context, search_form_id='search'):
+    request = context['request']
+    auto_id = 'id_%s_%%s' % search_form_id
+    return {
+        'form': SearchForm(initial=request.GET, auto_id=auto_id),
+        'search_form_id': search_form_id,
+        'action': context['search'],
+        }
\ No newline at end of file
Index: djangodocs/templatetags/__init__.py
===================================================================
Index: djangodocs/urls.py
===================================================================
--- djangodocs/urls.py	(revision 8718)
+++ djangodocs/urls.py	(working copy)
@@ -11,8 +11,8 @@
         djangodocs.views.language,
     ),
     url(
-        r'^(?P<lang>[a-z-]+)/(?P<version>[\w.-]+)/(?P<url>[\w./-]*)$',
-        djangodocs.views.document,
+        r'^(?P<lang>[a-z-]+)/(?P<version>[\w.-]+)/search/$',
+        djangodocs.views.search,
     ),
     url(
         r'^(?P<lang>[a-z-]+)/(?P<version>[\w.-]+)/_images/(?P<path>.*)$',
@@ -22,4 +22,8 @@
         r'^(?P<lang>[a-z-]+)/(?P<version>[\w.-]+)/_source/(?P<path>.*)$',
         djangodocs.views.source,
     ),
+    url(
+        r'^(?P<lang>[a-z-]+)/(?P<version>[\w.-]+)/(?P<url>[\w./-]*)$',
+        djangodocs.views.document,
+    ),
 )
Index: djangodocs/forms.py
===================================================================
--- djangodocs/forms.py	(revision 0)
+++ djangodocs/forms.py	(revision 0)
@@ -0,0 +1,13 @@
+from django import newforms as forms
+
+AS_Q_CHOICES = (
+    ('more:dev_docs', 'Latest'),
+    ('more:1.0_docs', '1.0'),
+    ('more:0.96_docs', '0.96'),
+    ('more:all_docs', 'All'),
+    )
+
+class SearchForm(forms.Form):
+    q = forms.CharField(widget=forms.TextInput({'class': 'query'}))
+    as_q = forms.ChoiceField(choices=AS_Q_CHOICES, widget=forms.RadioSelect, initial='more:dev_docs')
+    
\ No newline at end of file
Index: djangodocs/templates/docs/doc.html
===================================================================
--- djangodocs/templates/docs/doc.html	(revision 8718)
+++ djangodocs/templates/docs/doc.html	(working copy)
@@ -1,13 +1,46 @@
 {% extends "base_docs.html" %}
 
+{% load docs %}
+
 {% block title %}{{ doc.title }} | Django Documentation{% endblock %}
 
 {% block extrahead %}
   {{ block.super }}
+  <style type="text/css" media="screen">
+    #docs-search {
+      color: #000;
+      float: right;
+    }
+    #docs-search form {
+      font-size: 92%;
+      margin: 0;
+      padding: 1em 1em 0;
+      white-space: nowrap;
+    }
+    form.search ul {
+      list-style: none;
+      margin: 0;
+      padding: 0;
+    }
+    form.search li {
+      display: inline;
+      padding-right: 1em;
+    }
+    form.search .query {
+      width: 18em;
+    }
+  </style>
   <link rel="stylesheet" href="http://media.djangoproject.com/css/pygments.css" type="text/css" />
 {% endblock %}
 
-{% block billboard %}<h2><a href="{{ home }}">Django documentation</a></h2>{% endblock %}
+{% block billboard %}
+  {% block search %}
+    <div id="docs-search">
+      {% search_form %}
+    </div>
+  {% endblock %}
+  <h2><a href="{{ home }}">Django documentation</a></h2>
+{% endblock %}
 
 {% block content %}
 
@@ -73,6 +106,8 @@
     </ul>
   {% endblock %}
   
-  <h3>Last update:</h3>
-  <div>{{ update_date|date:"F j, Y, P" }} (<a href="http://www.timeanddate.com/worldclock/city.html?n=64">CDT</a>)</div>
+  {% block last-update-wrapper %}
+    <h3>Last update:</h3>
+    <div>{{ update_date|date:"F j, Y, P" }} (<a href="http://www.timeanddate.com/worldclock/city.html?n=64">CDT</a>)</div>
+  {% endblock %}
 {% endblock %}
Index: djangodocs/templates/docs/search.html
===================================================================
--- djangodocs/templates/docs/search.html	(revision 0)
+++ djangodocs/templates/docs/search.html	(revision 0)
@@ -0,0 +1,46 @@
+{% extends "docs/doc.html" %}
+
+{% load docs %}
+
+{% block title %}Search | Django Docuemtation{% endblock %}
+
+{% block extrahead %}
+  {{ block.super }}
+  <style type="text/css" media="screen">
+    #cse-search-results iframe {
+      width: 100%;
+    }
+  </style>
+{% endblock %}
+
+{% block toc-wrapper %}{% endblock %}
+
+{% block breadcrumbs-wrapper %}{% endblock %}
+
+{% block last-update-wrapper %}{% endblock %}
+
+{% block body %}
+  {% if query %}
+    <h1>Search Results</h1>
+    <div id="cse-search-results">
+      <div id="cse-search-results-js-required">
+        The Django Documentation search uses a Google Custom Search, which
+        requires Javascript. If your results don't appear here shortly, please
+        <a href="http://www.google.com/cse?{{ query_string|escape }}">view the results on Google's site</a>. 
+      </div>
+    </div>
+    <script type="text/javascript">
+      var googleSearchMessage = document.getElementById("cse-search-results-js-required");
+      googleSearchMessage.parentNode.removeChild(googleSearchMessage);
+    
+      var googleSearchIframeName = "cse-search-results";
+      var googleSearchFormName = "cse-search-box";
+      var googleSearchDomain = "www.google.com";
+      var googleSearchPath = "/cse";
+    </script>
+    <script type="text/javascript" src="http://www.google.com/afsonline/show_afs_search.js"></script>
+  {% else %}
+    <h1>Search</h1>
+    {% search_form "search_page" %}
+  {% endif %}
+{% endblock %}
Index: djangodocs/templates/docs/search_form.html
===================================================================
--- djangodocs/templates/docs/search_form.html	(revision 0)
+++ djangodocs/templates/docs/search_form.html	(revision 0)
@@ -0,0 +1,12 @@
+<form action="{{ action|escape }}" id="{{ search_form_id|escape }}" class="search">
+  <div>
+    <input type="hidden" name="cx" value="014182425414053223532:sfhzyefydv8" />
+    <input type="hidden" name="cof" value="FORID:11" />
+    <input type="hidden" name="ie" value="UTF-8" />
+    <input type="hidden" name="hl" value="{{ lang|escape }}" />
+    {{ form.q }}
+    <input type="submit" name="sa" class="submit" value="Search" />
+    {{ form.as_q }}
+  </div>
+</form>
+<script type="text/javascript" src="http://www.google.com/coop/cse/brand?form={{ search_form_id|escape }}&lang={{ lang|escape }}"></script>
