replace pocketfilms occurence by blinkster
authorymh <ymh.work@gmail.com>
Wed, 20 Jan 2010 12:37:40 +0100
changeset 3 526ebd3988b0
parent 1 ebaad720f88b
child 4 b77683731f25
replace pocketfilms occurence by blinkster
.pydevproject
web/blinkster/ldt/contentindexer.py
web/blinkster/ldt/fileimport.py
web/blinkster/ldt/forms.py
web/blinkster/ldt/models.py
web/blinkster/ldt/utils.py
web/blinkster/ldt/views.py
web/blinkster/settings.py
web/blinkster/utils/context_processors.py
web/lib/django_extensions/__init__.py
web/lib/django_extensions/admin/__init__.py
web/lib/django_extensions/admin/widgets.py
web/lib/django_extensions/conf/app_template/__init__.py.tmpl
web/lib/django_extensions/conf/app_template/forms.py.tmpl
web/lib/django_extensions/conf/app_template/models.py.tmpl
web/lib/django_extensions/conf/app_template/urls.py.tmpl
web/lib/django_extensions/conf/app_template/views.py.tmpl
web/lib/django_extensions/conf/command_template/management/__init__.py.tmpl
web/lib/django_extensions/conf/command_template/management/commands/__init__.py.tmpl
web/lib/django_extensions/conf/command_template/management/commands/sample.py.tmpl
web/lib/django_extensions/conf/jobs_template/jobs/__init__.py.tmpl
web/lib/django_extensions/conf/jobs_template/jobs/daily/__init__.py.tmpl
web/lib/django_extensions/conf/jobs_template/jobs/hourly/__init__.py.tmpl
web/lib/django_extensions/conf/jobs_template/jobs/monthly/__init__.py.tmpl
web/lib/django_extensions/conf/jobs_template/jobs/sample.py.tmpl
web/lib/django_extensions/conf/jobs_template/jobs/weekly/__init__.py.tmpl
web/lib/django_extensions/db/__init__.py
web/lib/django_extensions/db/fields/__init__.py
web/lib/django_extensions/db/models.py
web/lib/django_extensions/jobs/__init__.py
web/lib/django_extensions/jobs/daily/__init__.py
web/lib/django_extensions/jobs/daily/cache_cleanup.py
web/lib/django_extensions/jobs/daily/daily_cleanup.py
web/lib/django_extensions/jobs/hourly/__init__.py
web/lib/django_extensions/jobs/monthly/__init__.py
web/lib/django_extensions/jobs/weekly/__init__.py
web/lib/django_extensions/management/__init__.py
web/lib/django_extensions/management/color.py
web/lib/django_extensions/management/commands/__init__.py
web/lib/django_extensions/management/commands/clean_pyc.py
web/lib/django_extensions/management/commands/compile_pyc.py
web/lib/django_extensions/management/commands/create_app.py
web/lib/django_extensions/management/commands/create_command.py
web/lib/django_extensions/management/commands/create_jobs.py
web/lib/django_extensions/management/commands/describe_form.py
web/lib/django_extensions/management/commands/dumpscript.py
web/lib/django_extensions/management/commands/export_emails.py
web/lib/django_extensions/management/commands/generate_secret_key.py
web/lib/django_extensions/management/commands/graph_models.py
web/lib/django_extensions/management/commands/mail_debug.py
web/lib/django_extensions/management/commands/passwd.py
web/lib/django_extensions/management/commands/print_user_for_session.py
web/lib/django_extensions/management/commands/reset_db.py
web/lib/django_extensions/management/commands/runjob.py
web/lib/django_extensions/management/commands/runjobs.py
web/lib/django_extensions/management/commands/runprofileserver.py
web/lib/django_extensions/management/commands/runscript.py
web/lib/django_extensions/management/commands/runserver_plus.py
web/lib/django_extensions/management/commands/set_fake_emails.py
web/lib/django_extensions/management/commands/set_fake_passwords.py
web/lib/django_extensions/management/commands/shell_plus.py
web/lib/django_extensions/management/commands/show_urls.py
web/lib/django_extensions/management/commands/sqldiff.py
web/lib/django_extensions/management/commands/sync_media_s3.py
web/lib/django_extensions/management/commands/syncdata.py
web/lib/django_extensions/management/jobs.py
web/lib/django_extensions/management/modelviz.py
web/lib/django_extensions/management/signals.py
web/lib/django_extensions/management/utils.py
web/lib/django_extensions/media/django_extensions/css/jquery.autocomplete.css
web/lib/django_extensions/media/django_extensions/img/indicator.gif
web/lib/django_extensions/media/django_extensions/js/jquery.ajaxQueue.js
web/lib/django_extensions/media/django_extensions/js/jquery.autocomplete.js
web/lib/django_extensions/media/django_extensions/js/jquery.bgiframe.min.js
web/lib/django_extensions/media/django_extensions/js/jquery.js
web/lib/django_extensions/templates/django_extensions/widgets/foreignkey_searchinput.html
web/lib/django_extensions/templatetags/__init__.py
web/lib/django_extensions/templatetags/syntax_color.py
web/lib/django_extensions/templatetags/truncate_letters.py
web/lib/django_extensions/utils/__init__.py
web/lib/django_extensions/utils/text.py
web/lib/django_extensions/utils/uuid.py
--- a/.pydevproject	Wed Jan 20 01:06:59 2010 +0100
+++ b/.pydevproject	Wed Jan 20 12:37:40 2010 +0100
@@ -5,7 +5,7 @@
 <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
 <pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
 <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
-<path>/pocketfilms/web/pocketfilms</path>
+<path>/blinkster/web/pocketfilms</path>
 </pydev_pathproperty>
 <pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
 <path>/Library/Python/2.5/site-packages</path>
--- a/web/blinkster/ldt/contentindexer.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/ldt/contentindexer.py	Wed Jan 20 12:37:40 2010 +0100
@@ -2,11 +2,11 @@
 import os
 import os.path
 import shutil
-from pocketfilms.utils import zipfileext
-import pocketfilms.utils.log
-import pocketfilms.utils.xml
-from pocketfilms import settings
-from pocketfilms.ldt.models import Content
+from blinkster.utils import zipfileext
+import blinkster.utils.log
+import blinkster.utils.xml
+from blinkster import settings
+from blinkster.ldt.models import Content
 import xml
 import xml.dom
 import xml.dom.minidom
@@ -16,8 +16,8 @@
 import uuid
 import shutil
 import lucene
-from pocketfilms.ldt import STORE
-from pocketfilms.ldt import ANALYZER
+from blinkster.ldt import STORE
+from blinkster.ldt import ANALYZER
 
 def Property(func):
     return property(**func()) 
@@ -53,7 +53,7 @@
                 
         def index_content(self, content):
             
-            pocketfilms.utils.log.debug("Indexing content : "+str(content.iri_id))
+            blinkster.utils.log.debug("Indexing content : "+str(content.iri_id))
             filepath = content.iri_file_path()
             doc = xml.dom.minidom.parse(filepath)
                                     
@@ -66,7 +66,7 @@
                 ensembleId = ensemble.getAttribute("id")
                 
                 for decoupageNode in ensemble.childNodes:
-                    pocketfilms.utils.log.debug("Indexing content decoupage : "+ repr(decoupageNode.nodeType) + " in " + repr(self.decoupage_blacklist))
+                    blinkster.utils.log.debug("Indexing content decoupage : "+ repr(decoupageNode.nodeType) + " in " + repr(self.decoupage_blacklist))
                     if decoupageNode.nodeType != xml.dom.Node.ELEMENT_NODE or decoupageNode.tagName != "decoupage"  or decoupageNode.getAttribute("id") in self.decoupage_blacklist:
                         continue
                     
@@ -144,7 +144,7 @@
  
         def index_project(self, project):
             
-            pocketfilms.utils.log.debug("Indexing project : "+str(project.iri_id))
+            blinkster.utils.log.debug("Indexing project : "+str(project.iri_id))
             doc = xml.dom.minidom.parseString(project.ldt)
 
             self.__writer.deleteDocuments(lucene.Term("iri_id", project.iri_id))
@@ -158,7 +158,7 @@
                 ensembleId = "ens_perso"
                 
                 for decoupageNode in content.childNodes:
-                    pocketfilms.utils.log.debug("Indexing content decoupage : "+ repr(decoupageNode.nodeType) + " in " + repr(self.decoupage_blacklist))
+                    blinkster.utils.log.debug("Indexing content decoupage : "+ repr(decoupageNode.nodeType) + " in " + repr(self.decoupage_blacklist))
                     if decoupageNode.nodeType != xml.dom.Node.ELEMENT_NODE or decoupageNode.tagName != "decoupage"  or decoupageNode.getAttribute("id") in self.decoupage_blacklist:
                         continue
                     
@@ -204,4 +204,4 @@
             
                         self.__writer.addDocument(doc)
             
-            self.__writer.flush()
\ No newline at end of file
+            self.__writer.flush()
--- a/web/blinkster/ldt/fileimport.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/ldt/fileimport.py	Wed Jan 20 12:37:40 2010 +0100
@@ -2,11 +2,11 @@
 import os
 import os.path
 import shutil
-from pocketfilms.utils import zipfileext
-import pocketfilms.utils.log
-import pocketfilms.utils.xml
-from pocketfilms import settings
-from pocketfilms.ldt.models import Content, LdtProject
+from blinkster.utils import zipfileext
+import blinkster.utils.log
+import blinkster.utils.xml
+from blinkster import settings
+from blinkster.ldt.models import Content, LdtProject
 import xml
 import xml.dom
 import xml.dom.minidom
@@ -17,9 +17,9 @@
 import shutil
 import lucene
 import uuid
-from pocketfilms.ldt import STORE
-from pocketfilms.ldt import ANALYZER
-from pocketfilms.ldt.contentindexer import ContentIndexer
+from blinkster.ldt import STORE
+from blinkster.ldt import ANALYZER
+from blinkster.ldt.contentindexer import ContentIndexer
 
 def Property(func):
     return property(**func()) 
@@ -111,7 +111,7 @@
             f.close()
         
         
-        destPath = os.path.join(os.path.join(os.path.join(pocketfilms.settings.MEDIA_ROOT, "media"), "ldt"), self.id);
+        destPath = os.path.join(os.path.join(os.path.join(blinkster.settings.MEDIA_ROOT, "media"), "ldt"), self.id);
         if not os.path.exists(destPath):
             os.makedirs(destPath)
         shutil.move(os.path.join(self.basepath, self.src), os.path.join(destPath, os.path.basename(self.src)))
@@ -119,7 +119,7 @@
 
 
     def saveContent(self):
-        pocketfilms.utils.log.debug("ID of object to create : "+str(self.id))
+        blinkster.utils.log.debug("ID of object to create : "+str(self.id))
         content, self.created = Content.objects.get_or_create(iri_id=self.id, defaults = { 'iri' : self.id + u"/" + os.path.basename(self.src), 'title':self.title, 'description':self.desc, })#'decoupage_id': self.decoupageId, 'ensemble_id': self.ensembleId});
         if not self.created:
             content.iri = self.id + u"/" + os.path.basename(self.src)
@@ -242,7 +242,7 @@
         
         for i, medianode in  enumerate(result):
             id = medianode.attributes['id'].value
-            pocketfilms.utils.log.debug("FIRST ID : " + str(id))
+            blinkster.utils.log.debug("FIRST ID : " + str(id))
             if self.checkExistingMedia:
                 try:
                     Content.objects.get(iri_id=id)
@@ -253,16 +253,16 @@
                     do_pass = False
             if not do_pass:
                 if not (contents.has_key(id)):
-                    pocketfilms.utils.log.debug("CREATE IRI INFO ID : " + str(id))
+                    blinkster.utils.log.debug("CREATE IRI INFO ID : " + str(id))
                     contents[id] = IriInfo(id, os.path.dirname(ldtpath), i, "")
                 contents[id].src = medianode.attributes['src'].value
         
         result = xml.xpath.Evaluate("/iri/annotations/content", context=con)
 
-        pocketfilms.utils.log.debug("content xpath result : " + str(len(result)))        
+        blinkster.utils.log.debug("content xpath result : " + str(len(result)))        
         for contentnode in result:
             id = contentnode.attributes['id'].value
-            pocketfilms.utils.log.debug("ID : " + str(id))
+            blinkster.utils.log.debug("ID : " + str(id))
             if contents.has_key(id):
                 if self.author:
                     contentnode.setAttribute("author", unicode(self.author))
@@ -290,21 +290,21 @@
             zipfile = zipfileext.ZipFileExt(self.filepath)
             zipfile.unzip_into_dir(self.__tempdir)
             #load ldt
-            pocketfilms.utils.log.debug("Processing files : ")
-            pocketfilms.utils.log.debug(os.listdir(self.__tempdir))
+            blinkster.utils.log.debug("Processing files : ")
+            blinkster.utils.log.debug(os.listdir(self.__tempdir))
             foldersToProcess = [self.__tempdir]
             while len(foldersToProcess):
-                pocketfilms.utils.log.debug("folder stack length : "+ str(len(foldersToProcess)))
+                blinkster.utils.log.debug("folder stack length : "+ str(len(foldersToProcess)))
                 currentFolder = foldersToProcess.pop()
                 for entry in os.listdir(currentFolder):
                     if entry in settings.ZIP_BLACKLIST:
                         continue
                     entryPath = os.path.join(currentFolder, entry)
                     if(os.path.isdir(entryPath)):
-                        pocketfilms.utils.log.debug("Push folder : " + entryPath)
+                        blinkster.utils.log.debug("Push folder : " + entryPath)
                         foldersToProcess.append(entryPath)
                     elif fnmatch.fnmatch(entry, "*.ldt"):
-                        pocketfilms.utils.log.debug("Process file : " + entryPath)
+                        blinkster.utils.log.debug("Process file : " + entryPath)
                         ldtid = self.processLdt(entryPath)
                         processedids.append(ldtid)
                     elif fnmatch.fnmatch(entry, "*.flv"):
@@ -341,7 +341,7 @@
         
         for i, medianode in  enumerate(result):
             id = medianode.attributes['id'].value
-            pocketfilms.utils.log.debug("FIRST ID : " + str(id))
+            blinkster.utils.log.debug("FIRST ID : " + str(id))
             content = Content.objects.get(iri_id = id)
             self.contents.append(content)
             
@@ -360,7 +360,7 @@
         ldtproject = LdtProject(ldt_id = ldt_id, ldt=ldt)
         ldtproject.save()
         idldt = ldtproject.id
-        pocketfilms.utils.log.debug("ProjectFileImport.processFile ID : " + str(idldt))
+        blinkster.utils.log.debug("ProjectFileImport.processFile ID : " + str(idldt))
         for author in authors:
             ldtproject.authors.add(author)
         for content in contents:
@@ -381,9 +381,9 @@
         fi = ProjectFileImport(ldtPath, self.author)
         FileImport.processLdt(self, ldtPath)
         idproject = fi.processFile()
-        pocketfilms.utils.log.debug("ZipProjectFileImport.processLdt ID : " + str(idproject))
+        blinkster.utils.log.debug("ZipProjectFileImport.processLdt ID : " + str(idproject))
         fi.close()
         
         return idproject
                 
-    
\ No newline at end of file
+    
--- a/web/blinkster/ldt/forms.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/ldt/forms.py	Wed Jan 20 12:37:40 2010 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 from django import forms
-from pocketfilms.ldt.models import *
+from blinkster.ldt.models import *
 
 class LdtImportForm(forms.Form):
     importFile = forms.FileField()
--- a/web/blinkster/ldt/models.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/ldt/models.py	Wed Jan 20 12:37:40 2010 +0100
@@ -3,9 +3,9 @@
 from django.db import models
 from django.contrib import admin
 import lucene
-from pocketfilms.ldt import STORE
-from pocketfilms.ldt import ANALYZER
-from pocketfilms import settings
+from blinkster.ldt import STORE
+from blinkster.ldt import ANALYZER
+from blinkster import settings
 
 class Author(models.Model):
 
--- a/web/blinkster/ldt/utils.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/ldt/utils.py	Wed Jan 20 12:37:40 2010 +0100
@@ -1,9 +1,9 @@
 import lucene
-from pocketfilms.ldt import STORE
-from pocketfilms.ldt import ANALYZER
+from blinkster.ldt import STORE
+from blinkster.ldt import ANALYZER
 import uuid
 import django.core.urlresolvers
-import pocketfilms.settings
+import blinkster.settings
 from Ft.Xml import MarkupWriter
 import xml.dom
 import xml.dom.minidom
@@ -43,7 +43,7 @@
         writer.simpleElement(u"project", attributes={u"id":unicode(str(uuid.uuid1())), u"title":unicode(title) , u"user":author, u"abstract":u""})
         writer.startElement(u"medias")
         for content in contentList:
-            writer.simpleElement(u"media", attributes={u"id":content.iri_id,u"src":content.iri_url(web_url),u"video":unicode(media_url)+unicode(pocketfilms.settings.STREAM_URL),u"pict":u"",u"extra":u""})
+            writer.simpleElement(u"media", attributes={u"id":content.iri_id,u"src":content.iri_url(web_url),u"video":unicode(media_url)+unicode(blinkster.settings.STREAM_URL),u"pict":u"",u"extra":u""})
         writer.endElement(u"medias")
         writer.simpleElement(u"annotations")
         writer.startElement(u"displays")
@@ -80,14 +80,14 @@
     
         elementfile = doc.createElement('file')
             
-        elementfile.setAttribute('src',pocketfilms.settings.WEB_URL + django.core.urlresolvers.reverse(method, args=url))
+        elementfile.setAttribute('src',blinkster.settings.WEB_URL + django.core.urlresolvers.reverse(method, args=url))
         elementfile.setAttribute('display', '1')
         if(search):
-            elementfile.setAttribute("segsel",pocketfilms.settings.WEB_URL + django.core.urlresolvers.reverse(search, args=url))
+            elementfile.setAttribute("segsel",blinkster.settings.WEB_URL + django.core.urlresolvers.reverse(search, args=url))
     
     
         #/*chemin video : tant que le serveur de media n'est pas up, */
-        elementfile.setAttribute('video', pocketfilms.settings.STREAM_URL)
+        elementfile.setAttribute('video', blinkster.settings.STREAM_URL)
         elementfile.setAttribute('pict', "")
         elementfile.setAttribute('extra', "")
     
--- a/web/blinkster/ldt/views.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/ldt/views.py	Wed Jan 20 12:37:40 2010 +0100
@@ -2,39 +2,39 @@
 from django.http import HttpResponse
 from django.shortcuts import render_to_response
 from django.template import RequestContext
-from pocketfilms import settings
-from pocketfilms import version
-from pocketfilms.ldt.forms import LdtImportForm
-from pocketfilms.ldt.forms import LdtProjectImportForm
-from pocketfilms.ldt.forms import SearchForm, LdtForm, reindexForm 
-from pocketfilms.ldt.fileimport import *
-from pocketfilms.ldt.models import Author, LdtProject, Content
-from pocketfilms.ldt.utils import *
+from blinkster import settings
+from blinkster import version
+from blinkster.ldt.forms import LdtImportForm
+from blinkster.ldt.forms import LdtProjectImportForm
+from blinkster.ldt.forms import SearchForm, LdtForm, reindexForm 
+from blinkster.ldt.fileimport import *
+from blinkster.ldt.models import Author, LdtProject, Content
+from blinkster.ldt.utils import *
 import django.core.urlresolvers
 import django.utils.http
-import pocketfilms.ldt.models
-import pocketfilms.utils.zipfileext
-import pocketfilms.utils.log
+import blinkster.ldt.models
+import blinkster.utils.zipfileext
+import blinkster.utils.log
 import xml.dom.minidom
 import xml.dom.ext
 import urllib
 import base64
 import cgi
-from pocketfilms.ldt import STORE
-from pocketfilms.ldt import ANALYZER
+from blinkster.ldt import STORE
+from blinkster.ldt import ANALYZER
 from string import Template
 from Ft.Xml import MarkupWriter
 
 def index(request, url):
     
-    urlStr = settings.WEB_URL + django.core.urlresolvers.reverse("pocketfilms.ldt.views.init", args=['ldt', url])
+    urlStr = settings.WEB_URL + django.core.urlresolvers.reverse("blinkster.ldt.views.init", args=['ldt', url])
     language_code = request.LANGUAGE_CODE[:2]
     
     return render_to_response('ldt/init_ldt.html', {'MEDIA_URL': settings.MEDIA_URL, 'colorurl': settings.MEDIA_URL+'swf/ldt/pkg/color.xml', 'i18nurl': settings.MEDIA_URL+'swf/ldt/pkg/i18n', 'language': language_code, 'baseurl': settings.MEDIA_URL+'swf/ldt/', 'url': urlStr}, context_instance=RequestContext(request))
 
 def indexProject(request, id):
 
-    urlStr = settings.WEB_URL + django.core.urlresolvers.reverse("pocketfilms.ldt.views.init", args=['ldtProject', id])
+    urlStr = settings.WEB_URL + django.core.urlresolvers.reverse("blinkster.ldt.views.init", args=['ldtProject', id])
     language_code = request.LANGUAGE_CODE[:2]
     
     return render_to_response('ldt/init_ldt.html', {'MEDIA_URL': settings.MEDIA_URL, 'colorurl': settings.MEDIA_URL+'swf/ldt/pkg/color.xml', 'i18nurl': settings.MEDIA_URL+'swf/ldt/pkg/i18n', 'language': language_code, 'baseurl': settings.MEDIA_URL+'swf/ldt/', 'url': urlStr}, context_instance=RequestContext(request))
@@ -49,24 +49,24 @@
     queryStr = base64.urlsafe_b64encode(request.POST["search"].encode('utf8'))
     field = request.POST["field"]
     language_code = request.LANGUAGE_CODE[:2]
-    #pocketfilms.utils.log.debug(str(form))
-    #pocketfilms.utils.log.debug(str(sform.cleaned_data))
+    #blinkster.utils.log.debug(str(form))
+    #blinkster.utils.log.debug(str(sform.cleaned_data))
     #queryStr = django.utils.http.urlquote_plus(form.search)
     
-    url = settings.WEB_URL + django.core.urlresolvers.reverse("pocketfilms.ldt.views.searchInit", args=[field, queryStr])
+    url = settings.WEB_URL + django.core.urlresolvers.reverse("blinkster.ldt.views.searchInit", args=[field, queryStr])
     return render_to_response('ldt/init_ldt.html', {'MEDIA_URL': settings.MEDIA_URL, 'colorurl': settings.MEDIA_URL+'swf/ldt/pkg/color.xml', 'i18nurl': settings.MEDIA_URL+'swf/ldt/pkg/i18n', 'language': language_code, 'baseurl': settings.MEDIA_URL+'swf/ldt/', 'url': url}, context_instance=RequestContext(request))
 
 def searchIndexGet(request, field, query):
 
     language_code = request.LANGUAGE_CODE[:2]
-    url = settings.WEB_URL + django.core.urlresolvers.reverse("pocketfilms.ldt.views.searchInit", args=[field, query])
+    url = settings.WEB_URL + django.core.urlresolvers.reverse("blinkster.ldt.views.searchInit", args=[field, query])
     return render_to_response('ldt/init_ldt.html', {'MEDIA_URL': settings.MEDIA_URL, 'colorurl': settings.MEDIA_URL+'swf/ldt/pkg/color.xml', 'i18nurl': settings.MEDIA_URL+'swf/ldt/pkg/i18n', 'language': language_code, 'baseurl': settings.MEDIA_URL+'swf/ldt/', 'url': url}, context_instance=RequestContext(request))
 
 def searchInit(request, field, query):
     
-    ldtgen = pocketfilms.ldt.utils.LdtUtils()
+    ldtgen = blinkster.ldt.utils.LdtUtils()
     
-    doc = ldtgen.generateInit([field,query], 'pocketfilms.ldt.views.searchLdt', 'pocketfilms.ldt.views.searchSegments')
+    doc = ldtgen.generateInit([field,query], 'blinkster.ldt.views.searchLdt', 'blinkster.ldt.views.searchSegments')
     
     resp = HttpResponse(mimetype="text/xml")
     xml.dom.ext.PrettyPrint(doc, resp)
@@ -75,9 +75,9 @@
 
 def init(request, method, url):
 
-    ldtgen = pocketfilms.ldt.utils.LdtUtils()
+    ldtgen = blinkster.ldt.utils.LdtUtils()
     
-    doc = ldtgen.generateInit([url], 'pocketfilms.ldt.views.'+method, None)
+    doc = ldtgen.generateInit([url], 'blinkster.ldt.views.'+method, None)
     
     resp = HttpResponse(mimetype="text/xml")
     xml.dom.ext.PrettyPrint(doc, resp)
@@ -92,9 +92,9 @@
     
     resp = HttpResponse(mimetype="text/xml")
     
-    contentList = pocketfilms.ldt.models.Content.objects.filter(iri_id=url)
+    contentList = blinkster.ldt.models.Content.objects.filter(iri_id=url)
     
-    ldtgen = pocketfilms.ldt.utils.LdtUtils()
+    ldtgen = blinkster.ldt.utils.LdtUtils()
     ldtgen.generateLdt(contentList, file=resp, title = contentList[0].title)
     
     return resp
@@ -102,7 +102,7 @@
 def ldtProject(request, id):
     resp = HttpResponse(mimetype="text/xml")
     
-    project = pocketfilms.ldt.models.LdtProject.objects.get(id=id)
+    project = blinkster.ldt.models.LdtProject.objects.get(id=id)
     template = Template(project.ldt)
     resp.write(template.safe_substitute({'web_url':settings.WEB_URL,'media_url':settings.MEDIA_URL, 'stream_url': settings.STREAM_URL}))
     
@@ -127,8 +127,8 @@
 def uploadFile(request):
     importform = LdtImportForm(request.POST, request.FILES)
     if importform.is_valid():
-        pocketfilms.utils.log.debug(request.FILES)
-        pocketfilms.utils.log.debug(importform.cleaned_data['author'])
+        blinkster.utils.log.debug(request.FILES)
+        blinkster.utils.log.debug(importform.cleaned_data['author'])
         fi = FileImport(request.FILES['importFile'], importform.cleaned_data['author'])
         fi.processFile()
         fi.close()
@@ -140,8 +140,8 @@
     importForm = LdtProjectImportForm(request.POST, request.FILES)
     args = {}
     if importForm.is_valid():
-        pocketfilms.utils.log.debug(request.FILES)
-        pocketfilms.utils.log.debug(importForm.cleaned_data['author'])
+        blinkster.utils.log.debug(request.FILES)
+        blinkster.utils.log.debug(importForm.cleaned_data['author'])
         
         filetoprocess = request.FILES['importFile']
         fi = None
@@ -178,7 +178,7 @@
     if query and len(query)>0:        
         queryStr = base64.urlsafe_b64decode(query.encode("ascii")).decode("utf8")
         #queryStr = base64.urlsafe_b64decode(query.trim("="))
-        searcher = pocketfilms.ldt.utils.LdtSearch()
+        searcher = blinkster.ldt.utils.LdtSearch()
         ids = {}
         
         for result in searcher.query(field, queryStr):
@@ -190,10 +190,10 @@
             ids_editions = map(lambda t:t[0], filter(lambda id: id[0] is not None, Speak.objects.filter(session__day__edition=edition).order_by("session__start_ts", "order").values_list("content__iri_id")))            
             id_list = filter(lambda id: id in id_list, ids_editions)
             
-        contentList = pocketfilms.ldt.models.Content.objects.filter(iri_id__in=id_list)        
+        contentList = blinkster.ldt.models.Content.objects.filter(iri_id__in=id_list)        
 
             
-    ldtgen = pocketfilms.ldt.utils.LdtUtils()
+    ldtgen = blinkster.ldt.utils.LdtUtils()
     ldtgen.generateLdt(contentList, file=resp, title = u"Recherche : " + queryStr)
     
     return resp
@@ -202,7 +202,7 @@
 def searchSegments(request, field, query, edition=None):
     
     if query and len(query)>0:
-        searcher = pocketfilms.ldt.utils.LdtSearch()
+        searcher = blinkster.ldt.utils.LdtSearch()
         
         queryStr = base64.urlsafe_b64decode(query.encode("ascii")).decode("utf8")
         res = searcher.query(field, queryStr)
@@ -243,7 +243,7 @@
         rawContentList = Content.objects.in_bulk(ids)
         contentList = map(lambda id: rawContentList[id], ids)
         
-        ldtgen = pocketfilms.ldt.utils.LdtUtils()
+        ldtgen = blinkster.ldt.utils.LdtUtils()
         ldtgen.generateLdt(contentList, file=resp, author=user, title = "ENMI %s" % edition, web_url=settings.WEB_URL, media_url=settings.MEDIA_BASE_URL)
     
         return resp
@@ -289,7 +289,7 @@
     writer.startElement(u"projects")
     
     for project in projects:
-            writer.startElement(u"project", attributes={"id":unicode(project.id), "url":unicode(settings.WEB_URL + django.core.urlresolvers.reverse('pocketfilms.ldt.views.indexProject',args=[project.id]))})
+            writer.startElement(u"project", attributes={"id":unicode(project.id), "url":unicode(settings.WEB_URL + django.core.urlresolvers.reverse('blinkster.ldt.views.indexProject',args=[project.id]))})
             writer.startElement(u"authors")
             for author in project.authors.all():
                 writer.startElement(u"author")
--- a/web/blinkster/settings.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/settings.py	Wed Jan 20 12:37:40 2010 +0100
@@ -51,7 +51,7 @@
 ADMIN_MEDIA_PREFIX = '/media/'
 
 # Make this unique, and don't share it with anybody.
-SECRET_KEY = '-)1l)+v@raqnw8-olez3qtr_cwy37+ok@#$ppcu4ip@)i1v5s-'
+SECRET_KEY = '8cj2-b_tt4h0j9!n%75-n=#ng#^37euw45)v)uvva2-sb0$xx6'
 
 # List of callables that know how to import templates from various sources.
 TEMPLATE_LOADERS = (
@@ -77,6 +77,7 @@
 )
 
 INSTALLED_APPS = (
+    'django_extensions',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
--- a/web/blinkster/utils/context_processors.py	Wed Jan 20 01:06:59 2010 +0100
+++ b/web/blinkster/utils/context_processors.py	Wed Jan 20 12:37:40 2010 +0100
@@ -1,11 +1,11 @@
-import pocketfilms.settings;
-import pocketfilms.version;
+import blinkster.settings;
+import blinkster.version;
 
 def version(request):
-    return {'VERSION': pocketfilms.version.VERSION }
+    return {'VERSION': blinkster.version.VERSION }
 
 def base(request):
-    return {'BASE_URL': pocketfilms.settings.BASE_URL }
+    return {'BASE_URL': blinkster.settings.BASE_URL }
 
 def web(request):
     return {'WEB_URL': enmi.settings.WEB_URL }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/__init__.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,10 @@
+
+VERSION = (0, "4.1")
+
+# Dynamically calculate the version based on VERSION tuple
+if len(VERSION)>2 and VERSION[2] is not None:
+    str_version = "%s.%s_%s" % VERSION[:3]
+else:
+    str_version = "%s.%s" % VERSION[:2]
+
+__version__ = str_version
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/admin/__init__.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,125 @@
+#
+#    Autocomplete feature for admin panel
+#
+#    Most of the code has been written by Jannis Leidel and was updated a bit
+#    for django_extensions.
+#    http://jannisleidel.com/2008/11/autocomplete-form-widget-foreignkey-model-fields/
+#
+#    to_string_function, Satchmo adaptation and some comments added by emes
+#    (Michal Salaban)
+#
+import operator
+from django.http import HttpResponse, HttpResponseNotFound
+from django.contrib import admin
+from django.db import models
+from django.db.models.query import QuerySet
+from django.utils.encoding import smart_str
+from django.utils.translation import ugettext as _
+from django.utils.text import get_text_list
+
+from django_extensions.admin.widgets import ForeignKeySearchInput
+
+class ForeignKeyAutocompleteAdmin(admin.ModelAdmin):
+    """Admin class for models using the autocomplete feature.
+
+    There are two additional fields:
+       - related_search_fields: defines fields of managed model that
+         have to be represented by autocomplete input, together with
+         a list of target model fields that are searched for
+         input string, e.g.:
+         
+         related_search_fields = {
+            'author': ('first_name', 'email'),
+         }
+
+       - related_string_functions: contains optional functions which
+         take target model instance as only argument and return string
+         representation. By default __unicode__() method of target
+         object is used.
+    """
+
+    related_search_fields = {}
+    related_string_functions = {}
+
+    def __call__(self, request, url):
+        if url is None:
+            pass
+        elif url == 'foreignkey_autocomplete':
+            return self.foreignkey_autocomplete(request)
+        return super(ForeignKeyAutocompleteAdmin, self).__call__(request, url)
+
+    def foreignkey_autocomplete(self, request):
+        """
+        Searches in the fields of the given related model and returns the 
+        result as a simple string to be used by the jQuery Autocomplete plugin
+        """
+        query = request.GET.get('q', None)
+        app_label = request.GET.get('app_label', None)
+        model_name = request.GET.get('model_name', None)
+        search_fields = request.GET.get('search_fields', None)
+        object_pk = request.GET.get('object_pk', None)
+        try:
+            to_string_function = self.related_string_functions[model_name]
+        except KeyError:
+            to_string_function = lambda x: x.__unicode__()
+        if search_fields and app_label and model_name and (query or object_pk):
+            def construct_search(field_name):
+                # use different lookup methods depending on the notation
+                if field_name.startswith('^'):
+                    return "%s__istartswith" % field_name[1:]
+                elif field_name.startswith('='):
+                    return "%s__iexact" % field_name[1:]
+                elif field_name.startswith('@'):
+                    return "%s__search" % field_name[1:]
+                else:
+                    return "%s__icontains" % field_name
+            model = models.get_model(app_label, model_name)
+            queryset = model._default_manager.all()
+            data = ''
+            if query:
+                for bit in query.split():
+                    or_queries = [models.Q(**{construct_search(
+                        smart_str(field_name)): smart_str(bit)})
+                            for field_name in search_fields.split(',')]
+                    other_qs = QuerySet(model)
+                    other_qs.dup_select_related(queryset)
+                    other_qs = other_qs.filter(reduce(operator.or_, or_queries))
+                    queryset = queryset & other_qs
+                data = ''.join([u'%s|%s\n' % (
+                    to_string_function(f), f.pk) for f in queryset])
+            elif object_pk:
+                try:
+                    obj = queryset.get(pk=object_pk)
+                except:
+                    pass
+                else:
+                    data = to_string_function(obj)
+            return HttpResponse(data)
+        return HttpResponseNotFound()
+
+    def get_help_text(self, field_name, model_name):
+        searchable_fields = self.related_search_fields.get(field_name, None)
+        if searchable_fields:
+            help_kwargs = {
+                'model_name': model_name,
+                'field_list': get_text_list(searchable_fields, _('and')),
+            }
+            return _('Use the left field to do %(model_name)s lookups in the fields %(field_list)s.') % help_kwargs
+        return ''
+
+    def formfield_for_dbfield(self, db_field, **kwargs):
+        """
+        Overrides the default widget for Foreignkey fields if they are
+        specified in the related_search_fields class attribute.
+        """
+        if (isinstance(db_field, models.ForeignKey) and 
+            db_field.name in self.related_search_fields):
+            model_name = db_field.rel.to._meta.object_name
+            help_text = self.get_help_text(db_field.name, model_name)
+            if kwargs.get('help_text'):
+                help_text = u'%s %s' % (kwargs['help_text'], help_text)
+            kwargs['widget'] = ForeignKeySearchInput(db_field.rel,
+                                    self.related_search_fields[db_field.name])
+            kwargs['help_text'] = help_text
+        return super(ForeignKeyAutocompleteAdmin,
+            self).formfield_for_dbfield(db_field, **kwargs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/admin/widgets.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,76 @@
+from django import forms
+from django.conf import settings
+from django.utils.safestring import mark_safe
+from django.utils.text import truncate_words
+from django.template.loader import render_to_string
+from django.contrib.admin.widgets import ForeignKeyRawIdWidget
+
+class ForeignKeySearchInput(ForeignKeyRawIdWidget):
+    """
+    A Widget for displaying ForeignKeys in an autocomplete search input 
+    instead in a <select> box.
+    """
+    # Set in subclass to render the widget with a different template
+    widget_template = None
+    # Set this to the patch of the search view
+    search_path = '../foreignkey_autocomplete/'
+
+    class Media:
+        css = {
+            'all': ('django_extensions/css/jquery.autocomplete.css',)
+        }
+        js = (
+            'django_extensions/js/jquery.js',
+            'django_extensions/js/jquery.bgiframe.min.js',
+            'django_extensions/js/jquery.ajaxQueue.js',
+            'django_extensions/js/jquery.autocomplete.js',
+        )
+
+    def label_for_value(self, value):
+        key = self.rel.get_related_field().name
+        obj = self.rel.to._default_manager.get(**{key: value})
+        return truncate_words(obj, 14)
+
+    def __init__(self, rel, search_fields, attrs=None):
+        self.search_fields = search_fields
+        super(ForeignKeySearchInput, self).__init__(rel, attrs)
+
+    def render(self, name, value, attrs=None):
+        if attrs is None:
+            attrs = {}
+        output = [super(ForeignKeySearchInput, self).render(name, value, attrs)]
+        opts = self.rel.to._meta
+        app_label = opts.app_label
+        model_name = opts.object_name.lower()
+        related_url = '../../../%s/%s/' % (app_label, model_name)
+        params = self.url_parameters()
+        if params:
+            url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
+        else:
+            url = ''
+        if not attrs.has_key('class'):
+            attrs['class'] = 'vForeignKeyRawIdAdminField'
+        # Call the TextInput render method directly to have more control
+        output = [forms.TextInput.render(self, name, value, attrs)]
+        if value:
+            label = self.label_for_value(value)
+        else:
+            label = u''
+        context = {
+            'url': url,
+            'related_url': related_url,
+            'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX,
+            'search_path': self.search_path,
+            'search_fields': ','.join(self.search_fields),
+            'model_name': model_name,
+            'app_label': app_label,
+            'label': label,
+            'name': name,
+        }
+        output.append(render_to_string(self.widget_template or (
+            'django_extensions/widgets/%s/%s/foreignkey_searchinput.html' % (app_label, model_name),
+            'django_extensions/widgets/%s/foreignkey_searchinput.html' % app_label,
+            'django_extensions/widgets/foreignkey_searchinput.html',
+        ), context))
+        output.reverse()
+        return mark_safe(u''.join(output))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/conf/app_template/forms.py.tmpl	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,3 @@
+from django import forms
+
+# place form definition here
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/conf/app_template/models.py.tmpl	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/conf/app_template/urls.py.tmpl	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,3 @@
+from django.conf.urls.defaults import *
+
+# place app url patterns here
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/conf/app_template/views.py.tmpl	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,1 @@
+# Create your views here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/conf/command_template/management/commands/sample.py.tmpl	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,7 @@
+from django.core.management.base import {{ base_command }}
+
+class Command({{ base_command }}):
+    help = "My shiny new management command."
+
+    def {{ handle_method }}:
+        raise NotImplementedError()
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/conf/jobs_template/jobs/sample.py.tmpl	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,8 @@
+from django_extensions.management.jobs import BaseJob
+
+class Job(BaseJob):
+    help = "My sample job."
+
+    def execute(self):
+        # executing empty sample job
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/db/fields/__init__.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,213 @@
+"""
+Django Extensions additional model fields
+"""
+
+from django.template.defaultfilters import slugify
+from django.db.models import DateTimeField, CharField, SlugField
+import datetime
+import re
+
+try:
+    import uuid
+except ImportError:
+    from django_extensions.utils import uuid
+
+class AutoSlugField(SlugField):
+    """ AutoSlugField
+
+    By default, sets editable=False, blank=True.
+
+    Required arguments:
+
+    populate_from
+        Specifies which field or list of fields the slug is populated from.
+
+    Optional arguments:
+
+    separator
+        Defines the used separator (default: '-')
+
+    overwrite
+        If set to True, overwrites the slug on every save (default: False)
+
+    Inspired by SmileyChris' Unique Slugify snippet:
+    http://www.djangosnippets.org/snippets/690/
+    """
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault('blank', True)
+        kwargs.setdefault('editable', False)
+
+        populate_from = kwargs.pop('populate_from', None)
+        if populate_from is None:
+            raise ValueError("missing 'populate_from' argument")
+        else:
+            self._populate_from = populate_from
+        self.separator = kwargs.pop('separator',  u'-')
+        self.overwrite = kwargs.pop('overwrite', False)
+        super(AutoSlugField, self).__init__(*args, **kwargs)
+
+    def _slug_strip(self, value):
+        """
+        Cleans up a slug by removing slug separator characters that occur at
+        the beginning or end of a slug.
+
+        If an alternate separator is used, it will also replace any instances
+        of the default '-' separator with the new separator.
+        """
+        re_sep = '(?:-|%s)' % re.escape(self.separator)
+        value = re.sub('%s+' % re_sep, self.separator, value)
+        return re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
+
+    def slugify_func(self, content):
+        return slugify(content)
+
+    def create_slug(self, model_instance, add):
+        # get fields to populate from and slug field to set
+        if not isinstance(self._populate_from, (list, tuple)):
+            self._populate_from = (self._populate_from, )
+        slug_field = model_instance._meta.get_field(self.attname)
+
+        if add or self.overwrite:
+            # slugify the original field content and set next step to 2
+            slug_for_field = lambda field: self.slugify_func(getattr(model_instance, field))
+            slug = self.separator.join(map(slug_for_field, self._populate_from))
+            next = 2
+        else:
+            # get slug from the current model instance and calculate next
+            # step from its number, clean-up
+            slug = self._slug_strip(getattr(model_instance, self.attname))
+            next = slug.split(self.separator)[-1]
+            if next.isdigit():
+                slug = self.separator.join(slug.split(self.separator)[:-1])
+                next = int(next)
+            else:
+                next = 2
+
+        # strip slug depending on max_length attribute of the slug field
+        # and clean-up
+        slug_len = slug_field.max_length
+        if slug_len:
+            slug = slug[:slug_len]
+        slug = self._slug_strip(slug)
+        original_slug = slug
+
+        # exclude the current model instance from the queryset used in finding
+        # the next valid slug
+        queryset = model_instance.__class__._default_manager.all()
+        if model_instance.pk:
+            queryset = queryset.exclude(pk=model_instance.pk)
+
+        # form a kwarg dict used to impliment any unique_together contraints
+        kwargs = {}
+        for params in model_instance._meta.unique_together:
+            if self.attname in params:
+                for param in params:
+                    kwargs[param] = getattr(model_instance, param, None)
+        kwargs[self.attname] = slug
+
+        # increases the number while searching for the next valid slug
+        # depending on the given slug, clean-up
+        while not slug or queryset.filter(**kwargs):
+            slug = original_slug
+            end = '%s%s' % (self.separator, next)
+            end_len = len(end)
+            if slug_len and len(slug)+end_len > slug_len:
+                slug = slug[:slug_len-end_len]
+                slug = self._slug_strip(slug)
+            slug = '%s%s' % (slug, end)
+            kwargs[self.attname] = slug
+            next += 1
+        return slug
+
+    def pre_save(self, model_instance, add):
+        value = unicode(self.create_slug(model_instance, add))
+        setattr(model_instance, self.attname, value)
+        return value
+
+    def get_internal_type(self):
+        return "SlugField"
+
+class CreationDateTimeField(DateTimeField):
+    """ CreationDateTimeField
+
+    By default, sets editable=False, blank=True, default=datetime.now
+    """
+
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault('editable', False)
+        kwargs.setdefault('blank', True)
+        kwargs.setdefault('default', datetime.datetime.now)
+        DateTimeField.__init__(self, *args, **kwargs)
+
+    def get_internal_type(self):
+        return "DateTimeField"
+
+class ModificationDateTimeField(CreationDateTimeField):
+    """ ModificationDateTimeField
+
+    By default, sets editable=False, blank=True, default=datetime.now
+
+    Sets value to datetime.now() on each save of the model.
+    """
+
+    def pre_save(self, model, add):
+        value = datetime.datetime.now()
+        setattr(model, self.attname, value)
+        return value
+
+    def get_internal_type(self):
+        return "DateTimeField"
+
+class UUIDVersionError(Exception):
+    pass
+
+class UUIDField(CharField):
+    """ UUIDField
+
+    By default uses UUID version 1 (generate from host ID, sequence number and current time)
+
+    The field support all uuid versions which are natively supported by the uuid python module.
+    For more information see: http://docs.python.org/lib/module-uuid.html
+    """
+
+    def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
+        kwargs['max_length'] = 36
+        if auto:
+            kwargs['blank'] = True
+            kwargs.setdefault('editable', False)
+        self.auto = auto
+        self.version = version
+        if version==1:
+            self.node, self.clock_seq = node, clock_seq
+        elif version==3 or version==5:
+            self.namespace, self.name = namespace, name
+        CharField.__init__(self, verbose_name, name, **kwargs)
+
+    def get_internal_type(self):
+        return CharField.__name__
+
+    def create_uuid(self):
+        if not self.version or self.version==4:
+            return uuid.uuid4()
+        elif self.version==1:
+            return uuid.uuid1(self.node, self.clock_seq)
+        elif self.version==2:
+            raise UUIDVersionError("UUID version 2 is not supported.")
+        elif self.version==3:
+            return uuid.uuid3(self.namespace, self.name)
+        elif self.version==5:
+            return uuid.uuid5(self.namespace, self.name)
+        else:
+            raise UUIDVersionError("UUID version %s is not valid." % self.version)
+
+    def pre_save(self, model_instance, add):
+        if self.auto and add:
+            value = unicode(self.create_uuid())
+            setattr(model_instance, self.attname, value)
+            return value
+        else:
+            value = super(UUIDField, self).pre_save(model_instance, add)
+            if self.auto and not value:
+                value = unicode(self.create_uuid())
+                setattr(model_instance, self.attname, value)
+        return value
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/db/models.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,31 @@
+"""
+Django Extensions abstract base model classes.
+"""
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from django_extensions.db.fields import (ModificationDateTimeField,
+                                         CreationDateTimeField, AutoSlugField)
+
+class TimeStampedModel(models.Model):
+    """ TimeStampedModel
+    An abstract base class model that provides self-managed "created" and
+    "modified" fields.
+    """
+    created = CreationDateTimeField(_('created'))
+    modified = ModificationDateTimeField(_('modified'))
+
+    class Meta:
+        abstract = True
+
+class TitleSlugDescriptionModel(models.Model):
+    """ TitleSlugDescriptionModel
+    An abstract base class model that provides title and description fields
+    and a self-managed "slug" field that populates from the title.
+    """
+    title = models.CharField(_('title'), max_length=255)
+    slug = AutoSlugField(_('slug'), populate_from='title')
+    description = models.TextField(_('description'), blank=True, null=True)
+
+    class Meta:
+        abstract = True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/jobs/daily/cache_cleanup.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,23 @@
+"""
+Daily cleanup job.
+
+Can be run as a cronjob to clean out old data from the database (only expired
+sessions at the moment).
+"""
+
+from django_extensions.management.jobs import DailyJob
+
+class Job(DailyJob):
+    help = "Cache (db) cleanup Job"
+
+    def execute(self):
+        from django.conf import settings
+        import os
+
+        if settings.CACHE_BACKEND.startswith('db://'):
+            os.environ['TZ'] = settings.TIME_ZONE
+            table_name = settings.CACHE_BACKEND[5:]
+            cursor = connection.cursor()
+            cursor.execute("DELETE FROM %s WHERE %s < UTC_TIMESTAMP()" % \
+                (backend.quote_name(table_name), backend.quote_name('expires')))
+            transaction.commit_unless_managed()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/jobs/daily/daily_cleanup.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,15 @@
+"""
+Daily cleanup job.
+
+Can be run as a cronjob to clean out old data from the database (only expired
+sessions at the moment).
+"""
+
+from django_extensions.management.jobs import DailyJob
+
+class Job(DailyJob):
+    help = "Django Daily Cleanup Job"
+
+    def execute(self):
+        from django.core import management
+        management.call_command("cleanup")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/color.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,13 @@
+"""
+Sets up the terminal color scheme.
+"""
+
+from django.core.management import color
+from django.utils import termcolors
+
+def color_style():
+    style = color.color_style()
+    style.URL = termcolors.make_style(fg='green', opts=('bold',))
+    style.MODULE = termcolors.make_style(fg='yellow')
+    style.MODULE_NAME = termcolors.make_style(opts=('bold',))
+    return style
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/clean_pyc.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,41 @@
+from django.core.management.base import NoArgsCommand
+from django_extensions.management.utils import get_project_root
+from random import choice
+from optparse import make_option
+from os.path import join as _j
+import os
+
+class Command(NoArgsCommand):
+    option_list = NoArgsCommand.option_list + (
+        make_option('--optimize', '-o', '-O', action='store_true', dest='optimize', 
+            help='Remove optimized python bytecode files'),
+        make_option('--path', '-p', action='store', dest='path', 
+            help='Specify path to recurse into'),
+    )
+    help = "Removes all python bytecode compiled files from the project."
+    
+    requires_model_validation = False
+    
+    def handle_noargs(self, **options):
+        project_root = options.get("path", None)
+        if not project_root:
+            project_root = get_project_root()
+        exts = options.get("optimize", False) and [".pyc", ".pyo"] or [".pyc"]
+        verbose = int(options.get("verbosity", 1))>1
+
+        for root, dirs, files in os.walk(project_root):
+            for file in files:
+                ext = os.path.splitext(file)[1]
+                if ext in exts:
+                    full_path = _j(root, file)
+                    if verbose:
+                        print full_path
+                    os.remove(full_path)
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        make_option('--verbosity', '-v', action="store", dest="verbosity",
+            default='1', type='choice', choices=['0', '1', '2'],
+            help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/compile_pyc.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,39 @@
+from django.core.management.base import NoArgsCommand
+from django_extensions.management.utils import get_project_root
+from random import choice
+from optparse import make_option
+from os.path import join as _j
+import py_compile 
+import os
+
+class Command(NoArgsCommand):
+    option_list = NoArgsCommand.option_list + (
+        make_option('--path', '-p', action='store', dest='path', 
+            help='Specify path to recurse into'),
+    )
+    help = "Compile python bytecode files for the project."
+    
+    requires_model_validation = False
+    
+    def handle_noargs(self, **options):
+        project_root = options.get("path", None)
+        if not project_root:
+            project_root = get_project_root()
+        verbose = int(options.get("verbosity", 1))>1
+
+        for root, dirs, files in os.walk(project_root):
+            for file in files:
+                ext = os.path.splitext(file)[1]
+                if ext==".py":
+                    full_path = _j(root, file)
+                    if verbose:
+                        print "%sc" % full_path
+                    py_compile.compile(full_path)
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        make_option('--verbosity', '-v', action="store", dest="verbosity",
+            default='1', type='choice', choices=['0', '1', '2'],
+            help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/create_app.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,72 @@
+import os
+import re
+import django_extensions
+from django.core.management.base import CommandError, LabelCommand, _make_writeable
+from optparse import make_option
+
+class Command(LabelCommand):
+    option_list = LabelCommand.option_list + (
+        make_option('--template', '-t', action='store', dest='app_template', 
+            help='The path to the app template'),
+        make_option('--parent_path', '-p', action='store', dest='parent_path', 
+            help='The parent path of the app to be created'),
+    )
+    
+    help = ("Creates a Django application directory structure based on the specified template directory.")
+    args = "[appname]"
+    label = 'application name'
+    
+    requires_model_validation = False
+    can_import_settings = True
+    
+    def handle_label(self, label, **options):
+        project_dir = os.getcwd()
+        project_name = os.path.split(project_dir)[-1]
+        app_name =label
+        app_template = options.get('app_template') or os.path.join(django_extensions.__path__[0], 'conf', 'app_template')
+        app_dir = os.path.join(options.get('parent_path') or project_dir, app_name)
+                
+        if not os.path.exists(app_template):
+            raise CommandError("The template path, %r, does not exist." % app_template)
+        
+        if not re.search(r'^\w+$', label):
+            raise CommandError("%r is not a valid application name. Please use only numbers, letters and underscores." % label)
+        try:
+            os.makedirs(app_dir)
+        except OSError, e:
+            raise CommandError(e)
+        
+        copy_template(app_template, app_dir, project_name, app_name)
+        
+def copy_template(app_template, copy_to, project_name, app_name):
+    """copies the specified template directory to the copy_to location"""
+    import shutil
+    
+    # walks the template structure and copies it
+    for d, subdirs, files in os.walk(app_template):
+        relative_dir = d[len(app_template)+1:]
+        if relative_dir and not os.path.exists(os.path.join(copy_to, relative_dir)):
+            os.mkdir(os.path.join(copy_to, relative_dir))
+        for i, subdir in enumerate(subdirs):
+            if subdir.startswith('.'):
+                del subdirs[i]
+        for f in files:
+            if f.endswith('.pyc') or f.startswith('.DS_Store'):
+                continue
+            path_old = os.path.join(d, f)
+            path_new = os.path.join(copy_to, relative_dir, f.replace('app_name', app_name))
+            if os.path.exists(path_new):
+                path_new = os.path.join(copy_to, relative_dir, f)
+                if os.path.exists(path_new):
+                    continue
+            path_new = path_new.rstrip(".tmpl")
+            fp_old = open(path_old, 'r')
+            fp_new = open(path_new, 'w')
+            fp_new.write(fp_old.read().replace('{{ app_name }}', app_name).replace('{{ project_name }}', project_name))
+            fp_old.close()
+            fp_new.close()
+            try:
+                shutil.copymode(path_old, path_new)
+                _make_writeable(path_new)
+            except OSError:
+                sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/create_command.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,78 @@
+import os
+from django.core.management.base import CommandError, AppCommand, _make_writeable
+from optparse import make_option
+
+class Command(AppCommand):
+    option_list = AppCommand.option_list + (
+        make_option('--name', '-n', action='store', dest='command_name', default='sample',
+            help='The name to use for the management command'),
+        make_option('--base', '-b', action='store', dest='base_command', default='Base',
+            help='The base class used for implementation of this command. Should be one of Base, App, Label, or NoArgs'),
+    )
+    
+    help = ("Creates a Django management command directory structure for the given app name"
+            " in the current directory.")
+    args = "[appname]"
+    label = 'application name'
+
+    requires_model_validation = False
+    # Can't import settings during this command, because they haven't
+    # necessarily been created.
+    can_import_settings = True
+
+    def handle_app(self, app, **options):
+        directory = os.getcwd()
+        app_name = app.__name__.split('.')[-2]
+        project_dir = os.path.join(directory, app_name)
+        if not os.path.exists(project_dir):
+            try:
+                os.mkdir(project_dir)
+            except OSError, e:
+                raise CommandError(e)
+        
+        copy_template('command_template', project_dir, options.get('command_name'), '%sCommand' % options.get('base_command'))
+            
+def copy_template(template_name, copy_to, command_name, base_command):
+    """copies the specified template directory to the copy_to location"""
+    import django_extensions
+    import re
+    import shutil
+    
+    template_dir = os.path.join(django_extensions.__path__[0], 'conf', template_name)
+
+    handle_method = "handle(self, *args, **options)"
+    if base_command == 'AppCommand':
+        handle_method = "handle_app(self, app, **options)"
+    elif base_command == 'LabelCommand':
+        handle_method = "handle_label(self, label, **options)"
+    elif base_command == 'NoArgsCommand':
+        handle_method = "handle_noargs(self, **options)"
+    
+    # walks the template structure and copies it
+    for d, subdirs, files in os.walk(template_dir):
+        relative_dir = d[len(template_dir)+1:]
+        if relative_dir and not os.path.exists(os.path.join(copy_to, relative_dir)):
+            os.mkdir(os.path.join(copy_to, relative_dir))
+        for i, subdir in enumerate(subdirs):
+            if subdir.startswith('.'):
+                del subdirs[i]
+        for f in files:
+            if f.endswith('.pyc') or f.startswith('.DS_Store'):
+                continue
+            path_old = os.path.join(d, f)
+            path_new = os.path.join(copy_to, relative_dir, f.replace('sample', command_name))
+            if os.path.exists(path_new):
+                path_new = os.path.join(copy_to, relative_dir, f)
+                if os.path.exists(path_new):
+                    continue
+            path_new = path_new.rstrip(".tmpl")
+            fp_old = open(path_old, 'r')
+            fp_new = open(path_new, 'w')
+            fp_new.write(fp_old.read().replace('{{ command_name }}', command_name).replace('{{ base_command }}', base_command).replace('{{ handle_method }}', handle_method))
+            fp_old.close()
+            fp_new.close()
+            try:
+                shutil.copymode(path_old, path_new)
+                _make_writeable(path_new)
+            except OSError:
+                sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/create_jobs.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,54 @@
+import os
+import sys
+from django.core.management.base import CommandError, AppCommand, _make_writeable
+
+class Command(AppCommand):
+    help = ("Creates a Django jobs command directory structure for the given app name in the current directory.")
+    args = "[appname]"
+    label = 'application name'
+
+    requires_model_validation = False
+    # Can't import settings during this command, because they haven't
+    # necessarily been created.
+    can_import_settings = True
+
+    def handle_app(self, app, **options):
+        app_dir = os.path.dirname(app.__file__)
+        copy_template('jobs_template', app_dir)
+
+def copy_template(template_name, copy_to):
+    """copies the specified template directory to the copy_to location"""
+    import django_extensions
+    import re
+    import shutil
+    
+    template_dir = os.path.join(django_extensions.__path__[0], 'conf', template_name)
+
+    # walks the template structure and copies it
+    for d, subdirs, files in os.walk(template_dir):
+        relative_dir = d[len(template_dir)+1:]
+        if relative_dir and not os.path.exists(os.path.join(copy_to, relative_dir)):
+            os.mkdir(os.path.join(copy_to, relative_dir))
+        for i, subdir in enumerate(subdirs):
+            if subdir.startswith('.'):
+                del subdirs[i]
+        for f in files:
+            if f.endswith('.pyc') or f.startswith('.DS_Store'):
+                continue
+            path_old = os.path.join(d, f)
+            path_new = os.path.join(copy_to, relative_dir, f)
+            if os.path.exists(path_new):
+                path_new = os.path.join(copy_to, relative_dir, f)
+                if os.path.exists(path_new):
+                    continue
+            path_new = path_new.rstrip(".tmpl")
+            fp_old = open(path_old, 'r')
+            fp_new = open(path_new, 'w')
+            fp_new.write(fp_old.read())
+            fp_old.close()
+            fp_new.close()
+            try:
+                shutil.copymode(path_old, path_new)
+                _make_writeable(path_new)
+            except OSError:
+                sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/describe_form.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,64 @@
+from django.core.management.base import LabelCommand, CommandError
+from django.utils.encoding import force_unicode
+
+class Command(LabelCommand):
+    help = "Outputs the specified model as a form definition to the shell."
+    args = "[app.model]"
+    label = 'application name and model name'
+    
+    requires_model_validation = True
+    can_import_settings = True
+
+    def handle_label(self, label, **options):    
+        return describe_form(label)
+
+
+def describe_form(label, fields=None):
+    """
+    Returns a string describing a form based on the model
+    """
+    from django.db.models.loading import get_model
+    try:
+        app_name, model_name = label.split('.')[-2:]
+    except (IndexError, ValueError):
+        raise CommandError("Need application and model name in the form: appname.model")
+    model = get_model(app_name, model_name)
+
+    opts = model._meta
+    field_list = []
+    for f in opts.fields + opts.many_to_many:
+        if not f.editable:
+            continue
+        if fields and not f.name in fields:
+            continue
+        formfield = f.formfield()
+        if not '__dict__' in dir(formfield):
+            continue
+        attrs = {}
+        valid_fields = ['required', 'initial', 'max_length', 'min_length', 'max_value', 'min_value', 'max_digits', 'decimal_places', 'choices', 'help_text', 'label']
+        for k,v in formfield.__dict__.items():
+            if k in valid_fields and v != None:
+                # ignore defaults, to minimize verbosity
+                if k == 'required' and v:
+                    continue
+                if k == 'help_text' and not v:
+                    continue
+                if k == 'widget':
+                    attrs[k] = v.__class__
+                elif k in ['help_text', 'label']:
+                    attrs[k] = force_unicode(v).strip()
+                else:
+                    attrs[k] = v
+                
+        params = ', '.join(['%s=%r' % (k, v) for k, v in attrs.items()])
+        field_list.append('    %(field_name)s = forms.%(field_type)s(%(params)s)' % { 'field_name': f.name, 
+                                                                                  'field_type': formfield.__class__.__name__, 
+                                                                                  'params': params })
+                                                                               
+    return '''
+from django import forms
+from %(app_name)s.models import %(object_name)s
+    
+class %(object_name)sForm(forms.Form):
+%(field_list)s
+''' % { 'app_name': app_name, 'object_name': opts.object_name,  'field_list': '\n'.join(field_list) }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/dumpscript.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,515 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+      Title: Dumpscript management command
+    Project: Hardytools (queryset-refactor version)
+     Author: Will Hardy (http://willhardy.com.au)
+       Date: June 2008
+      Usage: python manage.py dumpscript appname > scripts/scriptname.py
+  $Revision: 217 $
+
+Description: 
+    Generates a Python script that will repopulate the database using objects.
+    The advantage of this approach is that it is easy to understand, and more
+    flexible than directly populating the database, or using XML.
+
+    * It also allows for new defaults to take effect and only transfers what is
+      needed.
+    * If a new database schema has a NEW ATTRIBUTE, it is simply not
+      populated (using a default value will make the transition smooth :)
+    * If a new database schema REMOVES AN ATTRIBUTE, it is simply ignored
+      and the data moves across safely (I'm assuming we don't want this
+      attribute anymore.
+    * Problems may only occur if there is a new model and is now a required
+      ForeignKey for an existing model. But this is easy to fix by editing the
+      populate script :)
+
+Improvements:
+    See TODOs and FIXMEs scattered throughout :-)
+
+"""
+
+import sys
+from django.db import models
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.management.base import BaseCommand
+from django.utils.encoding import smart_unicode, force_unicode
+from django.contrib.contenttypes.models import ContentType
+
+class Command(BaseCommand):
+    help = 'Dumps the data as a customised python script.'
+    args = '[appname ...]'
+
+    def handle(self, *app_labels, **options):
+
+        # Get the models we want to export
+        models = get_models(app_labels)
+
+        # A dictionary is created to keep track of all the processed objects,
+        # so that foreign key references can be made using python variable names.
+        # This variable "context" will be passed around like the town bicycle.
+        context = {}
+
+        # Create a dumpscript object and let it format itself as a string
+        print Script(models=models, context=context)
+
+
+def get_models(app_labels):
+    """ Gets a list of models for the given app labels, with some exceptions. 
+        TODO: If a required model is referenced, it should also be included.
+        Or at least discovered with a get_or_create() call.
+    """
+
+    from django.db.models import get_app, get_apps, get_model
+    from django.db.models import get_models as get_all_models
+
+    # These models are not to be output, e.g. because they can be generated automatically
+    # TODO: This should be "appname.modelname" string
+    from django.contrib.contenttypes.models import ContentType
+    EXCLUDED_MODELS = (ContentType, )
+
+    models = []
+
+    # If no app labels are given, return all
+    if not app_labels:
+        for app in get_apps():
+            models += [ m for m in get_all_models(app) if m not in EXCLUDED_MODELS ]
+
+    # Get all relevant apps
+    for app_label in app_labels:
+        # If a specific model is mentioned, get only that model
+        if "." in app_label:
+            app_label, model_name = app_label.split(".", 1)
+            models.append(get_model(app_label, model_name))
+        # Get all models for a given app
+        else:
+            models += [ m for m in get_all_models(get_app(app_label)) if m not in EXCLUDED_MODELS ]
+
+    return models
+
+
+
+class Code(object):
+    """ A snippet of python script. 
+        This keeps track of import statements and can be output to a string.
+        In the future, other features such as custom indentation might be included
+        in this class.
+    """
+
+    def __init__(self):
+        self.imports = {}
+        self.indent = -1 
+
+    def __str__(self):
+        """ Returns a string representation of this script. 
+        """
+        if self.imports:
+            sys.stderr.write(repr(self.import_lines))
+            return flatten_blocks([""] + self.import_lines + [""] + self.lines, num_indents=self.indent)
+        else:
+            return flatten_blocks(self.lines, num_indents=self.indent)
+
+    def get_import_lines(self):
+        """ Takes the stored imports and converts them to lines
+        """
+        if self.imports:
+            return [ "from %s import %s" % (value, key) for key, value in self.imports.items() ]
+        else:
+            return []
+    import_lines = property(get_import_lines)
+
+
+class ModelCode(Code):
+    " Produces a python script that can recreate data for a given model class. "
+
+    def __init__(self, model, context={}):
+        self.model = model
+        self.context = context
+        self.instances = []
+        self.indent = 0
+
+    def get_imports(self):
+        """ Returns a dictionary of import statements, with the variable being
+            defined as the key. 
+        """
+        return { self.model.__name__: smart_unicode(self.model.__module__) }
+    imports = property(get_imports)
+
+    def get_lines(self):
+        """ Returns a list of lists or strings, representing the code body. 
+            Each list is a block, each string is a statement.
+        """
+        code = []
+
+        for counter, item in enumerate(self.model.objects.all()):
+            instance = InstanceCode(instance=item, id=counter+1, context=self.context)
+            self.instances.append(instance)
+            if instance.waiting_list:
+                code += instance.lines
+ 
+        # After each instance has been processed, try again.
+        # This allows self referencing fields to work.
+        for instance in self.instances:
+            if instance.waiting_list:
+                code += instance.lines
+
+        return code
+
+    lines = property(get_lines)
+
+
+class InstanceCode(Code):
+    " Produces a python script that can recreate data for a given model instance. "
+
+    def __init__(self, instance, id, context={}):
+        """ We need the instance in question and an id """
+
+        self.instance = instance
+        self.model = self.instance.__class__
+        self.context = context
+        self.variable_name = "%s_%s" % (self.instance._meta.db_table, id)
+        self.skip_me = None
+        self.instantiated = False
+
+        self.indent  = 0 
+        self.imports = {}
+
+        self.waiting_list = list(self.model._meta.fields)
+
+        self.many_to_many_waiting_list = {} 
+        for field in self.model._meta.many_to_many:
+            self.many_to_many_waiting_list[field] = list(getattr(self.instance, field.name).all())
+
+    def get_lines(self, force=False):
+        """ Returns a list of lists or strings, representing the code body. 
+            Each list is a block, each string is a statement.
+            
+            force (True or False): if an attribute object cannot be included, 
+            it is usually skipped to be processed later. With 'force' set, there
+            will be no waiting: a get_or_create() call is written instead.
+        """
+        code_lines = []
+
+        # Don't return anything if this is an instance that should be skipped
+        if self.skip():
+            return []
+
+        # Initialise our new object
+        # e.g. model_name_35 = Model()
+        code_lines += self.instantiate()
+
+        # Add each field
+        # e.g. model_name_35.field_one = 1034.91
+        #      model_name_35.field_two = "text"
+        code_lines += self.get_waiting_list()
+
+        if force:
+            # TODO: Check that M2M are not affected
+            code_lines += self.get_waiting_list(force=force)
+
+        # Print the save command for our new object
+        # e.g. model_name_35.save()
+        if code_lines:
+            code_lines.append("%s.save()\n" % (self.variable_name))
+
+        code_lines += self.get_many_to_many_lines(force=force)
+
+        return code_lines
+    lines = property(get_lines)
+
+    def skip(self):
+        """ Determine whether or not this object should be skipped.
+            If this model is a parent of a single subclassed instance, skip it.
+            The subclassed instance will create this parent instance for us.
+
+            TODO: Allow the user to force its creation?
+        """
+
+        if self.skip_me is not None:
+            return self.skip_me
+
+        try:
+            # Django trunk since r7722 uses CollectedObjects instead of dict
+            from django.db.models.query import CollectedObjects
+            sub_objects = CollectedObjects()
+        except ImportError:
+            # previous versions don't have CollectedObjects
+            sub_objects = {}
+        self.instance._collect_sub_objects(sub_objects)
+        if reduce(lambda x, y: x+y, [self.model in so._meta.parents for so in sub_objects.keys()]) == 1:
+            pk_name = self.instance._meta.pk.name
+            key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
+            self.context[key] = None
+            self.skip_me = True
+        else:
+            self.skip_me = False
+
+        return self.skip_me
+
+    def instantiate(self):
+        " Write lines for instantiation "
+        # e.g. model_name_35 = Model()
+        code_lines = []
+
+        if not self.instantiated:
+            code_lines.append("%s = %s()" % (self.variable_name, self.model.__name__))
+            self.instantiated = True
+
+            # Store our variable name for future foreign key references
+            pk_name = self.instance._meta.pk.name
+            key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
+            self.context[key] = self.variable_name
+
+        return code_lines
+
+
+    def get_waiting_list(self, force=False):
+        " Add lines for any waiting fields that can be completed now. "
+
+        code_lines = []
+
+        # Process normal fields
+        for field in list(self.waiting_list):
+            try:
+                # Find the value, add the line, remove from waiting list and move on
+                value = get_attribute_value(self.instance, field, self.context, force=force)
+                code_lines.append('%s.%s = %s' % (self.variable_name, field.name, value))
+                self.waiting_list.remove(field)
+            except SkipValue, e:
+                # Remove from the waiting list and move on
+                self.waiting_list.remove(field)
+                continue
+            except DoLater, e:
+                # Move on, maybe next time
+                continue
+
+
+        return code_lines
+
+
+    def get_many_to_many_lines(self, force=False):
+        """ Generates lines that define many to many relations for this instance. """
+
+        lines = []
+
+        for field, rel_items in self.many_to_many_waiting_list.items():
+            for rel_item in list(rel_items):
+                try:
+                    pk_name = rel_item._meta.pk.name
+                    key = '%s_%s' % (rel_item.__class__.__name__, getattr(rel_item, pk_name))
+                    value = "%s" % self.context[key]
+                    lines.append('%s.%s.add(%s)' % (self.variable_name, field.name, value))
+                    self.many_to_many_waiting_list[field].remove(rel_item)
+                except KeyError:
+                    if force:
+                        value = "%s.objects.get(%s=%s)" % (rel_item._meta.object_name, pk_name, getattr(rel_item, pk_name))
+                        lines.append('%s.%s.add(%s)' % (self.variable_name, field.name, value))
+                        self.many_to_many_waiting_list[field].remove(rel_item)
+
+        if lines:
+            lines.append("")
+
+        return lines
+
+
+class Script(Code):
+    " Produces a complete python script that can recreate data for the given apps. "
+
+    def __init__(self, models, context={}):
+        self.models = models
+        self.context = context
+
+        self.indent = -1 
+        self.imports = {}
+
+    def get_lines(self):
+        """ Returns a list of lists or strings, representing the code body. 
+            Each list is a block, each string is a statement.
+        """
+        code = [ self.FILE_HEADER.strip() ]
+
+        # Queue and process the required models
+        for model_class in queue_models(self.models, context=self.context):
+            sys.stderr.write('Processing model: %s\n' % model_class.model.__name__)
+            code.append(model_class.import_lines)
+            code.append("")
+            code.append(model_class.lines)
+
+        # Process left over foreign keys from cyclic models
+        for model in self.models:
+            sys.stderr.write('Re-processing model: %s\n' % model.model.__name__)
+            for instance in model.instances:
+                if instance.waiting_list or instance.many_to_many_waiting_list:
+                    code.append(instance.get_lines(force=True))
+
+        return code
+
+    lines = property(get_lines)
+
+    # A user-friendly file header
+    FILE_HEADER = """
+
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file has been automatically generated, changes may be lost if you
+# go and generate it again. It was generated with the following command:
+# %s
+
+import datetime
+from decimal import Decimal
+from django.contrib.contenttypes.models import ContentType
+
+def run():
+
+""" % " ".join(sys.argv)
+
+
+
+# HELPER FUNCTIONS
+#-------------------------------------------------------------------------------
+
+def flatten_blocks(lines, num_indents=-1):
+    """ Takes a list (block) or string (statement) and flattens it into a string
+        with indentation. 
+    """
+
+    # The standard indent is four spaces
+    INDENTATION = " " * 4
+
+    if not lines:
+        return ""
+
+    # If this is a string, add the indentation and finish here
+    if isinstance(lines, basestring):
+        return INDENTATION * num_indents + lines
+
+    # If this is not a string, join the lines and recurse
+    return "\n".join([ flatten_blocks(line, num_indents+1) for line in lines ])
+
+
+
+
+def get_attribute_value(item, field, context, force=False):
+    """ Gets a string version of the given attribute's value, like repr() might. """
+
+    # Find the value of the field, catching any database issues
+    try:
+        value = getattr(item, field.name)
+    except ObjectDoesNotExist:
+        raise SkipValue('Could not find object for %s.%s, ignoring.\n' % (item.__class__.__name__, field.name))
+
+    # AutoField: We don't include the auto fields, they'll be automatically recreated
+    if isinstance(field, models.AutoField):
+        raise SkipValue()
+
+    # Some databases (eg MySQL) might store boolean values as 0/1, this needs to be cast as a bool
+    elif isinstance(field, models.BooleanField) and value is not None:
+        return repr(bool(value))
+
+    # Post file-storage-refactor, repr() on File/ImageFields no longer returns the path
+    elif isinstance(field, models.FileField):
+        return repr(force_unicode(value))
+
+    # ForeignKey fields, link directly using our stored python variable name
+    elif isinstance(field, models.ForeignKey) and value is not None:
+
+        # Special case for contenttype foreign keys: no need to output any
+        # content types in this script, as they can be generated again 
+        # automatically.
+        # NB: Not sure if "is" will always work
+        if field.rel.to is ContentType:
+            return 'ContentType.objects.get(app_label="%s", model="%s")' % (value.app_label, value.model)
+
+        # Generate an identifier (key) for this foreign object
+        pk_name = value._meta.pk.name
+        key = '%s_%s' % (value.__class__.__name__, getattr(value, pk_name))
+
+        if key in context:
+            variable_name = context[key]
+            # If the context value is set to None, this should be skipped.
+            # This identifies models that have been skipped (inheritance)
+            if variable_name is None:
+                raise SkipValue()
+            # Return the variable name listed in the context 
+            return "%s" % variable_name
+        elif force:
+            return "%s.objects.get(%s=%s)" % (value._meta.object_name, pk_name, getattr(value, pk_name))
+        else:
+            raise DoLater('(FK) %s.%s\n' % (item.__class__.__name__, field.name))
+
+
+    # A normal field (e.g. a python built-in)
+    else:
+        return repr(value)
+
+def queue_models(models, context):
+    """ Works an an appropriate ordering for the models.
+        This isn't essential, but makes the script look nicer because 
+        more instances can be defined on their first try.
+    """
+
+    # Max number of cycles allowed before we call it an infinite loop.
+    MAX_CYCLES = 5
+
+    model_queue = []
+    number_remaining_models = len(models)
+    allowed_cycles = MAX_CYCLES
+
+    while number_remaining_models > 0:
+        previous_number_remaining_models = number_remaining_models
+
+        model = models.pop(0)
+        
+        # If the model is ready to be processed, add it to the list
+        if check_dependencies(model, model_queue):
+            model_class = ModelCode(model=model, context=context)
+            model_queue.append(model_class)
+
+        # Otherwise put the model back at the end of the list
+        else:
+            models.append(model)
+
+        # Check for infinite loops. 
+        # This means there is a cyclic foreign key structure
+        # That cannot be resolved by re-ordering
+        number_remaining_models = len(models)
+        if number_remaining_models == previous_number_remaining_models:
+            allowed_cycles -= 1
+            if allowed_cycles <= 0:
+                # Add the remaining models, but do not remove them from the model list
+                missing_models = [ ModelCode(model=m, context=context) for m in models ]
+                model_queue += missing_models
+                # Replace the models with the model class objects 
+                # (sure, this is a little bit of hackery)
+                models[:] = missing_models
+                break
+        else:
+            allowed_cycles = MAX_CYCLES
+
+    return model_queue
+
+
+def check_dependencies(model, model_queue):
+    " Check that all the depenedencies for this model are already in the queue. "
+
+    # A list of allowed links: existing fields, itself and the special case ContentType
+    allowed_links = [ m.model.__name__ for m in model_queue ] + [model.__name__, 'ContentType']
+
+    # For each ForeignKey or ManyToMany field, check that a link is possible
+    for field in model._meta.fields + model._meta.many_to_many:
+        if field.rel and field.rel.to.__name__ not in allowed_links:
+            return False
+
+    return True
+
+
+
+# EXCEPTIONS
+#-------------------------------------------------------------------------------
+
+class SkipValue(Exception):
+    """ Value could not be parsed or should simply be skipped. """
+
+class DoLater(Exception):
+    """ Value could not be parsed or should simply be skipped. """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/export_emails.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,114 @@
+from django.core.management.base import BaseCommand, CommandError
+from django.contrib.auth.models import User, Group
+from optparse import make_option
+from sys import stdout
+from csv import writer
+
+FORMATS = [
+    'address',
+    'google',
+    'outlook',
+    'linkedin',
+    'vcard',
+]
+
+def full_name(first_name, last_name, username, **extra):
+    name = u" ".join(n for n in [first_name, last_name] if n)
+    if not name: return username
+    return name
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--group', '-g', action='store', dest='group', default=None,
+            help='Limit to users which are part of the supplied group name'),
+        make_option('--format', '-f', action='store', dest='format', default=FORMATS[0],
+            help="output format. May be one of '" + "', '".join(FORMATS) + "'."),
+    )
+
+    help = ("Export user email address list in one of a number of formats.")
+    args = "[output file]"
+    label = 'filename to save to'
+
+    requires_model_validation = True
+    can_import_settings = True
+    encoding = 'utf-8' # RED_FLAG: add as an option -DougN
+
+    def handle(self, *args, **options):
+        if len(args) > 1:
+            raise CommandError("extra arguments supplied")
+        group = options['group']
+        if group and not Group.objects.filter(name=group).count()==1:
+            names = u"', '".join(g['name'] for g in Group.objects.values('name')).encode('utf-8')
+            if names: names = "'" + names + "'."
+            raise CommandError("Unknown group '" + group + "'. Valid group names are: " + names)
+        if len(args) and args[0] != '-':
+            outfile = file(args[0], 'w')
+        else:
+            outfile = stdout
+
+        qs = User.objects.all().order_by('last_name', 'first_name', 'username', 'email')
+        if group: qs = qs.filter(group__name=group).distinct()
+        qs = qs.values('last_name', 'first_name', 'username', 'email')
+        getattr(self, options['format'])(qs, outfile)
+
+    def address(self, qs, out):
+        """simple single entry per line in the format of:
+            "full name" <my@address.com>;
+        """
+        out.write(u"\n".join(u'"%s" <%s>;' % (full_name(**ent), ent['email']) 
+                             for ent in qs).encode(self.encoding))
+        out.write("\n")
+
+    def google(self, qs, out):
+        """CSV format suitable for importing into google GMail
+        """
+        csvf = writer(out)
+        csvf.writerow(['Name', 'Email'])
+        for ent in qs:
+            csvf.writerow([full_name(**ent).encode(self.encoding), 
+                           ent['email'].encode(self.encoding)])
+
+    def outlook(self, qs, out):
+        """CSV format suitable for importing into outlook
+        """
+        csvf = writer(out)
+        columns = ['Name','E-mail Address','Notes','E-mail 2 Address','E-mail 3 Address',
+                   'Mobile Phone','Pager','Company','Job Title','Home Phone','Home Phone 2',
+                   'Home Fax','Home Address','Business Phone','Business Phone 2',
+                   'Business Fax','Business Address','Other Phone','Other Fax','Other Address']
+        csvf.writerow(columns)
+        empty = [''] * (len(columns) - 2)
+        for ent in qs:
+            csvf.writerow([full_name(**ent).encode(self.encoding), 
+                           ent['email'].encode(self.encoding)] + empty)
+
+    def linkedin(self, qs, out):
+        """CSV format suitable for importing into linkedin Groups.
+        perfect for pre-approving members of a linkedin group.
+        """
+        csvf = writer(out)
+        csvf.writerow(['First Name', 'Last Name', 'Email'])
+        for ent in qs:
+            csvf.writerow([ent['first_name'].encode(self.encoding), 
+                           ent['last_name'].encode(self.encoding), 
+                           ent['email'].encode(self.encoding)])
+
+    def vcard(self, qs, out):
+        try:
+            import vobject
+        except ImportError:
+            print self.style.ERROR_OUTPUT("Please install python-vobject to use the vcard export format.")
+            import sys
+            sys.exit(1)
+        for ent in qs:
+            card = vobject.vCard()
+            card.add('fn').value = full_name(**ent)
+            if not ent['last_name'] and not ent['first_name']:
+                # fallback to fullname, if both first and lastname are not declared
+                card.add('n').value = vobject.vcard.Name(full_name(**ent))
+            else:
+                card.add('n').value = vobject.vcard.Name(ent['last_name'], ent['first_name'])
+            emailpart = card.add('email')
+            emailpart.value = ent['email']
+            emailpart.type_param = 'INTERNET'
+            out.write(card.serialize().encode(self.encoding))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/generate_secret_key.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,10 @@
+from random import choice
+from django.core.management.base import NoArgsCommand
+
+class Command(NoArgsCommand):
+    help = "Generates a new SECRET_KEY that can be used in a project settings file."
+    
+    requires_model_validation = False
+    
+    def handle_noargs(self, **options):
+        return ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/graph_models.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,60 @@
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+from django_extensions.management.modelviz import generate_dot
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--disable-fields', '-d', action='store_true', dest='disable_fields',
+            help='Do not show the class member fields'),
+        make_option('--group-models', '-g', action='store_true', dest='group_models',
+            help='Group models together respective to there application'),
+        make_option('--all-applications', '-a', action='store_true', dest='all_applications',
+            help='Automaticly include all applications from INSTALLED_APPS'),
+        make_option('--output', '-o', action='store', dest='outputfile',
+            help='Render output file. Type of output dependend on file extensions. Use png or jpg to render graph to image.'),
+        make_option('--layout', '-l', action='store', dest='layout', default='dot',
+            help='Layout to be used by GraphViz for visualization. Layouts: circo dot fdp neato nop nop1 nop2 twopi'),
+    )
+
+    help = ("Creates a GraphViz dot file for the specified app names.  You can pass multiple app names and they will all be combined into a single model.  Output is usually directed to a dot file.")
+    args = "[appname]"
+    label = 'application name'
+
+    requires_model_validation = True
+    can_import_settings = True
+
+    def handle(self, *args, **options):
+        if len(args) < 1 and not options['all_applications']:
+            raise CommandError("need one or more arguments for appname")
+
+        dotdata = generate_dot(args, **options)
+        if options['outputfile']:
+            self.render_output(dotdata, **options)
+        else:
+            self.print_output(dotdata)
+
+    def print_output(self, dotdata):
+        print dotdata
+
+    def render_output(self, dotdata, **kwargs):
+        try:
+            import pygraphviz
+        except ImportError, e:
+            raise CommandError("need pygraphviz python module ( apt-get install python-pygraphviz )")
+
+        vizdata = ' '.join(dotdata.split("\n")).strip()
+        version = pygraphviz.__version__.rstrip("-svn")
+        try:
+            if [int(v) for v in version.split('.')]<(0,36):
+                # HACK around old/broken AGraph before version 0.36 (ubuntu ships with this old version)
+                import tempfile
+                tmpfile = tempfile.NamedTemporaryFile()
+                tmpfile.write(vizdata)
+                tmpfile.seek(0)
+                vizdata = tmpfile.name
+        except ValueError:
+            pass
+
+        graph = pygraphviz.AGraph(vizdata)
+        graph.layout(prog=kwargs['layout'])
+        graph.draw(kwargs['outputfile'])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/mail_debug.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,41 @@
+from django.core.management.base import BaseCommand
+import sys
+import smtpd
+import asyncore
+
+class Command(BaseCommand):
+    help = "Starts a test mail server for development."
+    args = '[optional port number or ippaddr:port]'
+
+    requires_model_validation = False
+
+    def handle(self, addrport='', *args, **options):
+        if args:
+            raise CommandError('Usage is runserver %s' % self.args)
+        if not addrport:
+            addr = ''
+            port = '1025'
+        else:
+            try:
+                addr, port = addrport.split(':')
+            except ValueError:
+                addr, port = '', addrport
+        if not addr:
+            addr = '127.0.0.1'
+
+        if not port.isdigit():
+            raise CommandError("%r is not a valid port number." % port)
+        else:
+            port = int(port)
+
+        quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
+
+        def inner_run():
+            print "Now accepting mail at %s:%s" % (addr, port)
+            server = smtpd.DebuggingServer((addr,port), None)
+            asyncore.loop()
+
+        try: 
+            inner_run()
+        except KeyboardInterrupt:
+            pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/passwd.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,37 @@
+from django.core.management.base import BaseCommand, CommandError
+from django.contrib.auth.models import User
+import getpass
+
+class Command(BaseCommand):
+    help = "Clone of the UNIX program ``passwd'', for django.contrib.auth."
+
+    requires_model_validation = False
+
+    def handle(self, *args, **options):
+        if len(args) > 1:
+            raise CommandError("need exactly one or zero arguments for username")
+
+        if args:
+            username, = args
+        else:
+            username = getpass.getuser()
+
+        try:
+            u = User.objects.get(username=username)
+        except User.DoesNotExist:
+            raise CommandError("user %s does not exist" % username)
+
+        print "Changing password for user", u.username
+        p1 = p2 = ""
+        while "" in (p1, p2) or p1 != p2:
+            p1 = getpass.getpass()
+            p2 = getpass.getpass("Password (again): ")
+            if p1 != p2:
+                print "Passwords do not match, try again"
+            elif "" in (p1, p2):
+                raise CommandError("aborted")
+
+        u.set_password(p1)
+        u.save()
+
+        return "Password changed successfully for user", u.username
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/print_user_for_session.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,49 @@
+from django.core.management.base import BaseCommand, CommandError
+from django.contrib.auth.models import User
+from django.contrib.sessions.models import Session
+import re
+
+SESSION_RE = re.compile("^[0-9a-f]{20,40}$")
+
+class Command(BaseCommand):
+    help = ("print the user information for the provided session key. "
+            "this is very helpful when trying to track down the person who "
+            "experienced a site crash.")
+    args = "session_key"
+    label = 'session key for the user'
+
+    requires_model_validation = True
+    can_import_settings = True
+
+    def handle(self, *args, **options):
+        if len(args) > 1:
+            raise CommandError("extra arguments supplied")
+        if len(args) < 1:
+            raise CommandError("session_key argument missing")
+        key = args[0].lower()
+        if not SESSION_RE.match(key):
+            raise CommandError("malformed session key")
+        try:
+            session = Session.objects.get(pk=key)
+        except Session.DoesNotExist:
+            print "Session Key does not exist. Expired?"
+            return
+
+        data = session.get_decoded()
+        print 'Session to Expire:', session.expire_date
+        print 'Raw Data:', data
+        uid = data.get('_auth_user_id', None)
+        if uid is None:
+            print 'No user associated with session'
+            return
+        print "User id:", uid
+        try:
+            user = User.objects.get(pk=uid)
+        except User.DoesNotExist:
+            print "No user associated with that id."
+            return
+        for key in ['username', 'email', 'first_name', 'last_name']:
+            print key+': ' + getattr(user, key)
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/reset_db.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,118 @@
+"""
+originally from http://www.djangosnippets.org/snippets/828/ by dnordberg
+"""
+
+
+from django.conf import settings
+from django.core.management.base import CommandError, BaseCommand
+from django.db import connection
+import logging
+from optparse import make_option
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--noinput', action='store_false',
+                    dest='interactive', default=True,
+                    help='Tells Django to NOT prompt the user for input of any kind.'),
+        make_option('--no-utf8', action='store_true',
+                    dest='no_utf8_support', default=False,
+                    help='Tells Django to not create a UTF-8 charset database'),
+    )
+    help = "Resets the database for this project."
+
+    def handle(self, *args, **options):
+        """
+        Resets the database for this project.
+    
+        Note: Transaction wrappers are in reverse as a work around for
+        autocommit, anybody know how to do this the right way?
+        """
+
+        if options.get('interactive'):
+            confirm = raw_input("""
+You have requested a database reset.
+This will IRREVERSIBLY DESTROY
+ALL data in the database "%s".
+Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """ % (settings.DATABASE_NAME,))
+        else:
+            confirm = 'yes'
+
+        if confirm != 'yes':
+            print "Reset cancelled."
+            return
+
+        engine = settings.DATABASE_ENGINE
+    
+        if engine == 'sqlite3':
+            import os
+            try:
+                logging.info("Unlinking sqlite3 database")
+                os.unlink(settings.DATABASE_NAME)
+            except OSError:
+                pass
+        elif engine == 'mysql':
+            import MySQLdb as Database
+            kwargs = {
+                'user': settings.DATABASE_USER,
+                'passwd': settings.DATABASE_PASSWORD,
+            }
+            if settings.DATABASE_HOST.startswith('/'):
+                kwargs['unix_socket'] = settings.DATABASE_HOST
+            else:
+                kwargs['host'] = settings.DATABASE_HOST
+            if settings.DATABASE_PORT:
+                kwargs['port'] = int(settings.DATABASE_PORT)
+            connection = Database.connect(**kwargs)
+            drop_query = 'DROP DATABASE IF EXISTS %s' % settings.DATABASE_NAME
+            utf8_support = options.get('no_utf8_support', False) and '' or 'CHARACTER SET utf8'
+            create_query = 'CREATE DATABASE %s %s' % (settings.DATABASE_NAME, utf8_support)
+            logging.info('Executing... "' + drop_query + '"')
+            connection.query(drop_query)
+            logging.info('Executing... "' + create_query + '"')
+            connection.query(create_query)
+        elif engine == 'postgresql' or engine == 'postgresql_psycopg2':
+            if engine == 'postgresql':
+                import psycopg as Database
+            elif engine == 'postgresql_psycopg2':
+                import psycopg2 as Database
+            
+            if settings.DATABASE_NAME == '':
+                from django.core.exceptions import ImproperlyConfigured
+                raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
+            
+            conn_string = "dbname=%s" % settings.DATABASE_NAME
+            if settings.DATABASE_USER:
+                conn_string += " user=%s" % settings.DATABASE_USER
+            if settings.DATABASE_PASSWORD:
+                conn_string += " password='%s'" % settings.DATABASE_PASSWORD
+            if settings.DATABASE_HOST:
+                conn_string += " host=%s" % settings.DATABASE_HOST
+            if settings.DATABASE_PORT:
+                conn_string += " port=%s" % settings.DATABASE_PORT
+            connection = Database.connect(conn_string)
+            connection.set_isolation_level(0) #autocommit false
+            cursor = connection.cursor()
+            drop_query = 'DROP DATABASE %s' % settings.DATABASE_NAME
+            logging.info('Executing... "' + drop_query + '"')
+    
+            try:
+                cursor.execute(drop_query)
+            except Database.ProgrammingError, e:
+                logging.info("Error: "+str(e))
+    
+            # Encoding should be SQL_ASCII (7-bit postgres default) or prefered UTF8 (8-bit)
+            create_query = ("""
+CREATE DATABASE %s
+    WITH OWNER = %s
+        ENCODING = 'UTF8'
+        TABLESPACE = pg_default;
+""" % (settings.DATABASE_NAME, settings.DATABASE_USER))
+            logging.info('Executing... "' + create_query + '"')
+            cursor.execute(create_query)
+    
+        else:
+            raise CommandError, "Unknown database engine %s", engine
+    
+        print "Reset successful."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/runjob.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,59 @@
+from django.core.management.base import LabelCommand
+from optparse import make_option
+from django_extensions.management.jobs import get_job, print_jobs
+
+class Command(LabelCommand):
+    option_list = LabelCommand.option_list + (
+        make_option('--list', '-l', action="store_true", dest="list_jobs",
+            help="List all jobs with their description"),
+    )
+    help = "Run a single maintenance job."
+    args = "[app_name] job_name"
+    label = ""
+    
+    requires_model_validation = True
+
+    def runjob(self, app_name, job_name, options):
+        verbosity = int(options.get('verbosity', 1))
+        if verbosity>1:
+            print "Executing job: %s (app: %s)" % (job_name, app_name)
+        try:
+            job = get_job(app_name, job_name)
+        except KeyError, e:
+            if app_name:
+                print "Error: Job %s for applabel %s not found" % (app_name, job_name)
+            else:
+                print "Error: Job %s not found" % job_name
+            print "Use -l option to view all the available jobs"
+            return
+        try:
+            job().execute()
+        except Exception, e:
+            import traceback
+            print "ERROR OCCURED IN JOB: %s (APP: %s)" % (job_name, app_name)
+            print "START TRACEBACK:"
+            traceback.print_exc()
+            print "END TRACEBACK\n"
+    
+    def handle(self, *args, **options):
+        app_name = None
+        job_name = None
+        if len(args)==1:
+            job_name = args[0]
+        elif len(args)==2:
+            app_name, job_name = args
+        if options.get('list_jobs'):
+            print_jobs(only_scheduled=False, show_when=True, show_appname=True)
+        else:
+            if not job_name:
+                print "Run a single maintenance job. Please specify the name of the job."
+                return
+            self.runjob(app_name, job_name, options)
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        make_option('--verbosity', '-v', action="store", dest="verbosity",
+            default='1', type='choice', choices=['0', '1', '2'],
+            help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/runjobs.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,90 @@
+from django.core.management.base import LabelCommand
+from optparse import make_option
+from django_extensions.management.jobs import get_jobs, print_jobs
+
+class Command(LabelCommand):
+    option_list = LabelCommand.option_list + (
+        make_option('--list', '-l', action="store_true", dest="list_jobs",
+            help="List all jobs with their description"),
+    )
+    help = "Runs scheduled maintenance jobs."
+    args = "[hourly daily weekly monthly]"
+    label = ""
+
+    requires_model_validation = True
+
+    def usage_msg(self):
+        print "Run scheduled jobs. Please specify 'hourly', 'daily', 'weekly' or 'monthly'"
+
+    def runjobs(self, when, options):
+        verbosity = int(options.get('verbosity', 1))
+        jobs = get_jobs(when, only_scheduled=True)
+        list = jobs.keys()
+        list.sort()
+        for app_name, job_name in list:
+            job = jobs[(app_name, job_name)]
+            if verbosity>1:
+                print "Executing %s job: %s (app: %s)" % (when, job_name, app_name)
+            try:
+                job().execute()
+            except Exception, e:
+                import traceback
+                print "ERROR OCCURED IN %s JOB: %s (APP: %s)" % (when.upper(), job_name, app_name)
+                print "START TRACEBACK:"
+                traceback.print_exc()
+                print "END TRACEBACK\n"
+
+    def runjobs_by_signals(self, when, options):
+        """ Run jobs from the signals """
+        # Thanks for Ian Holsman for the idea and code
+        from django_extensions.management import signals
+        from django.db import models
+        from django.conf import settings
+
+        verbosity = int(options.get('verbosity', 1))
+        for app_name in settings.INSTALLED_APPS:
+            try:
+                __import__(app_name + '.management', '', '', [''])
+            except ImportError:
+                pass
+
+        for app in models.get_apps():
+            if verbosity>1:
+                app_name = '.'.join(app.__name__.rsplit('.')[:-1])
+                print "Sending %s job signal for: %s" % (when, app_name)
+            if when == 'hourly':
+                signals.run_hourly_jobs.send(sender=app, app=app)
+            elif when == 'daily':
+                signals.run_daily_jobs.send(sender=app, app=app)
+            elif when == 'weekly':
+                signals.run_weekly_jobs.send(sender=app, app=app)
+            elif when == 'monthly':
+                signals.run_monthly_jobs.send(sender=app, app=app)
+
+    def handle(self, *args, **options):
+        when = None
+        if len(args)>1:
+            self.usage_msg()
+            return
+        elif len(args)==1:
+            if not args[0] in ['hourly', 'daily', 'weekly', 'monthly']:
+                self.usage_msg()
+                return
+            else:
+                when = args[0]
+        if options.get('list_jobs'):
+            print_jobs(when, only_scheduled=True, show_when=True, show_appname=True)
+        else:
+            if not when:
+                self.usage_msg()
+                return
+            self.runjobs(when, options)
+            self.runjobs_by_signals(when, options)
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        make_option('--verbosity', '-v', action="store", dest="verbosity",
+            default='1', type='choice', choices=['0', '1', '2'],
+            help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/runprofileserver.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,213 @@
+"""
+runprofileserver.py
+
+    Starts a lightweight Web server with profiling enabled.
+
+Credits for kcachegrind support taken from lsprofcalltree.py go to:
+ David Allouche
+ Jp Calderone & Itamar Shtull-Trauring
+ Johan Dahlin
+"""
+
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+import os
+import sys
+
+def label(code):
+    if isinstance(code, str):
+        return ('~', 0, code)    # built-in functions ('~' sorts at the end)
+    else:
+        return '%s %s:%d' % (code.co_name,
+                             code.co_filename,
+                             code.co_firstlineno)
+
+class KCacheGrind(object):
+    def __init__(self, profiler):
+        self.data = profiler.getstats()
+        self.out_file = None
+
+    def output(self, out_file):
+        self.out_file = out_file
+        print >> out_file, 'events: Ticks'
+        self._print_summary()
+        for entry in self.data:
+            self._entry(entry)
+
+    def _print_summary(self):
+        max_cost = 0
+        for entry in self.data:
+            totaltime = int(entry.totaltime * 1000)
+            max_cost = max(max_cost, totaltime)
+        print >> self.out_file, 'summary: %d' % (max_cost,)
+
+    def _entry(self, entry):
+        out_file = self.out_file
+
+        code = entry.code
+        #print >> out_file, 'ob=%s' % (code.co_filename,)
+        if isinstance(code, str):
+            print >> out_file, 'fi=~'
+        else:
+            print >> out_file, 'fi=%s' % (code.co_filename,)
+        print >> out_file, 'fn=%s' % (label(code),)
+
+        inlinetime = int(entry.inlinetime * 1000)
+        if isinstance(code, str):
+            print >> out_file, '0 ', inlinetime
+        else:
+            print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
+
+        # recursive calls are counted in entry.calls
+        if entry.calls:
+            calls = entry.calls
+        else:
+            calls = []
+
+        if isinstance(code, str):
+            lineno = 0
+        else:
+            lineno = code.co_firstlineno
+
+        for subentry in calls:
+            self._subentry(lineno, subentry)
+        print >> out_file
+
+    def _subentry(self, lineno, subentry):
+        out_file = self.out_file
+        code = subentry.code
+        #print >> out_file, 'cob=%s' % (code.co_filename,)
+        print >> out_file, 'cfn=%s' % (label(code),)
+        if isinstance(code, str):
+            print >> out_file, 'cfi=~'
+            print >> out_file, 'calls=%d 0' % (subentry.callcount,)
+        else:
+            print >> out_file, 'cfi=%s' % (code.co_filename,)
+            print >> out_file, 'calls=%d %d' % (
+                subentry.callcount, code.co_firstlineno)
+
+        totaltime = int(subentry.totaltime * 1000)
+        print >> out_file, '%d %d' % (lineno, totaltime)
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--noreload', action='store_false', dest='use_reloader', default=True,
+            help='Tells Django to NOT use the auto-reloader.'),
+        make_option('--adminmedia', dest='admin_media_path', default='',
+            help='Specifies the directory from which to serve admin media.'),
+        make_option('--prof-path', dest='prof_path', default='/tmp',
+            help='Specifies the directory which to save profile information in.'),
+        make_option('--nomedia', action='store_true', dest='no_media', default=False,
+            help='Do not profile MEDIA_URL and ADMIN_MEDIA_URL'),
+        make_option('--use-cprofile', action='store_true', dest='use_cprofile', default=False,
+            help='Use cProfile if available, this is disabled per default because of incompatibilities.'),
+        make_option('--kcachegrind', action='store_true', dest='use_lsprof', default=False,
+            help='Create kcachegrind compatible lsprof files, this requires and automatically enables cProfile.'),
+    )
+    help = "Starts a lightweight Web server with profiling enabled."
+    args = '[optional port number, or ipaddr:port]'
+
+    # Validation is called explicitly each time the server is reloaded.
+    requires_model_validation = False
+
+    def handle(self, addrport='', *args, **options):
+        import django
+        from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
+        from django.core.handlers.wsgi import WSGIHandler
+        if args:
+            raise CommandError('Usage is runserver %s' % self.args)
+        if not addrport:
+            addr = ''
+            port = '8000'
+        else:
+            try:
+                addr, port = addrport.split(':')
+            except ValueError:
+                addr, port = '', addrport
+        if not addr:
+            addr = '127.0.0.1'
+
+        if not port.isdigit():
+            raise CommandError("%r is not a valid port number." % port)
+
+        use_reloader = options.get('use_reloader', True)
+        admin_media_path = options.get('admin_media_path', '')
+        shutdown_message = options.get('shutdown_message', '')
+        no_media = options.get('no_media', False)
+        quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
+
+        def inner_run():
+            from django.conf import settings
+
+            import hotshot, time, os
+            USE_CPROFILE = options.get('use_cprofile', False)
+            USE_LSPROF = options.get('use_lsprof', False)
+            if USE_LSPROF:
+               USE_CPROFILE = True
+            if USE_CPROFILE:
+                try:
+                    import cProfile
+                    USE_CPROFILE = True
+                except ImportError:
+                    print "cProfile disabled, module cannot be imported!"
+                    USE_CPROFILE = False
+            if USE_LSPROF and not USE_CPROFILE:
+                raise SystemExit("Kcachegrind compatible output format required cProfile from Python 2.5")
+            prof_path = options.get('prof_path', '/tmp')
+            def make_profiler_handler(inner_handler):
+                def handler(environ, start_response):
+                    path_info = environ['PATH_INFO']
+                    # normally /media/ is MEDIA_URL, but in case still check it in case it's differently
+                    # should be hardly a penalty since it's an OR expression.
+                    # TODO: fix this to check the configuration settings and not make assumpsions about where media are on the url
+                    if no_media and (path_info.startswith('/media') or path_info.startswith(settings.MEDIA_URL)):
+                        return inner_handler(environ, start_response)
+                    path_name = path_info.strip("/").replace('/', '.') or "root"
+                    profname = "%s.%.3f.prof" % (path_name, time.time())
+                    profname = os.path.join(prof_path, profname)
+                    if USE_CPROFILE:
+                        prof = cProfile.Profile()
+                    else:
+                        prof = hotshot.Profile(profname)
+                    try:
+                        return prof.runcall(inner_handler, environ, start_response)
+                    finally:
+                        if USE_LSPROF:
+                            kg = KCacheGrind(prof)
+                            kg.output(file(profname, 'w'))
+                        elif USE_CPROFILE:
+                            prof.dump_stats(profname)
+                return handler
+
+            print "Validating models..."
+            self.validate(display_num_errors=True)
+            print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
+            print "Development server is running at http://%s:%s/" % (addr, port)
+            print "Quit the server with %s." % quit_command
+            try:
+                path = admin_media_path or django.__path__[0] + '/contrib/admin/media'
+                handler = make_profiler_handler(AdminMediaHandler(WSGIHandler(), path))
+                run(addr, int(port), handler)
+            except WSGIServerException, e:
+                # Use helpful error messages instead of ugly tracebacks.
+                ERRORS = {
+                    13: "You don't have permission to access that port.",
+                    98: "That port is already in use.",
+                    99: "That IP address can't be assigned-to.",
+                }
+                try:
+                    error_text = ERRORS[e.args[0].args[0]]
+                except (AttributeError, KeyError):
+                    error_text = str(e)
+                sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
+                # Need to use an OS exit because sys.exit doesn't work in a thread
+                os._exit(1)
+            except KeyboardInterrupt:
+                if shutdown_message:
+                    print shutdown_message
+                sys.exit(0)
+        if use_reloader:
+            from django.utils import autoreload
+            autoreload.main(inner_run)
+        else:
+            inner_run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/runscript.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,134 @@
+from django.core.management.base import BaseCommand
+from django.core.management.color import no_style
+from optparse import make_option
+import sys
+import os
+
+try:
+    set
+except NameError:
+    from sets import Set as set   # Python 2.3 fallback
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--fixtures', action='store_true', dest='infixtures', default=False,
+            help='Only look in app.fixtures subdir'),
+        make_option('--noscripts', action='store_true', dest='noscripts', default=False,
+            help='Look in app.scripts subdir'),
+        make_option('-s', '--silent', action='store_true', dest='silent', default=False,
+            help='Run silently, do not show errors and tracebacks'),
+        make_option('--no-traceback', action='store_true', dest='no_traceback', default=False,
+            help='Do not show tracebacks'),
+    )
+    help = 'Runs a script in django context.'
+    args = "script [script ...]"
+
+    def handle(self, *scripts, **options):
+        from django.db.models import get_apps
+        
+        NOTICE = self.style.SQL_TABLE
+        NOTICE2 = self.style.SQL_FIELD
+        ERROR = self.style.ERROR_OUTPUT
+        ERROR2 = self.style.NOTICE
+
+        subdirs = []
+
+        if not options.get('noscripts'):
+            subdirs.append('scripts')
+        if options.get('infixtures'):
+            subdirs.append('fixtures')
+        verbosity = int(options.get('verbosity', 1))
+        show_traceback = options.get('traceback', True)
+        if show_traceback is None:
+            # XXX: traceback is set to None from Django ?
+            show_traceback = True
+        no_traceback = options.get('no_traceback', False)
+        if no_traceback:
+            show_traceback = False
+        silent = options.get('silent', False)
+        if silent:
+            verbosity = 0
+
+        if len(subdirs) < 1:
+            print NOTICE("No subdirs to run left.")
+            return
+
+        if len(scripts) < 1:
+            print ERROR("Script name required.")
+            return
+
+        def run_script(mod):
+            # TODO: add arguments to run
+            try:
+                mod.run()
+            except Exception, e:
+                if silent:
+                    return
+                if verbosity > 0:
+                    print ERROR("Exception while running run() in '%s'" % mod.__name__)
+                if show_traceback:
+                    raise
+        
+        def my_import(mod):
+            if verbosity > 1:
+                print NOTICE("Check for %s" % mod)
+            try:
+                t = __import__(mod, [], [], [" "])
+                #if verbosity > 1:
+                #    print NOTICE("Found script %s ..." % mod)
+                if hasattr(t, "run"):
+                    if verbosity > 1:
+                        print NOTICE2("Found script '%s' ..." % mod)
+                    #if verbosity > 1:
+                    #    print NOTICE("found run() in %s. executing..." % mod)
+                    return t
+                else:
+                    if verbosity > 1:
+                        print ERROR2("Find script '%s' but no run() function found." % mod)
+            except ImportError:
+                return False
+        
+        def find_modules_for_script(script):
+            """ find script module which contains 'run' attribute """
+            modules = []
+            # first look in apps
+            for app in get_apps():
+                app_name = app.__name__.split(".")[:-1] # + ['fixtures']
+                for subdir in subdirs:
+                    mod = my_import(".".join(app_name + [subdir, script]))
+                    if mod:
+                        modules.append(mod)
+
+            # try app.DIR.script import
+            sa = script.split(".")
+            for subdir in subdirs:
+                nn = ".".join(sa[:-1] + [subdir, sa[-1]])
+                mod = my_import(nn)
+                if mod:
+                    modules.append(mod)
+
+            # try direct import
+            if script.find(".") != -1:
+                mod = my_import(script)
+                if mod:
+                    modules.append(mod)
+            
+            return modules
+        
+        for script in scripts:
+            modules = find_modules_for_script(script)
+            if not modules:
+                if verbosity>0 and not silent:
+                    print ERROR("No module for script '%s' found" % script)
+            for mod in modules:
+                if verbosity>1:
+                    print NOTICE2("Running script '%s' ..." % mod.__name__)
+                run_script(mod)
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        make_option('--verbosity', '-v', action="store", dest="verbosity",
+                    default='1', type='choice', choices=['0', '1', '2'],
+                    help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/runserver_plus.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,75 @@
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+import os
+import sys
+
+def null_technical_500_response(request, exc_type, exc_value, tb):
+    raise exc_type, exc_value, tb
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--noreload', action='store_false', dest='use_reloader', default=True,
+            help='Tells Django to NOT use the auto-reloader.'),
+        make_option('--browser', action='store_true', dest='open_browser',
+            help='Tells Django to open a browser.'),
+        make_option('--adminmedia', dest='admin_media_path', default='',
+            help='Specifies the directory from which to serve admin media.'),
+    )
+    help = "Starts a lightweight Web server for development."
+    args = '[optional port number, or ipaddr:port]'
+
+    # Validation is called explicitly each time the server is reloaded.
+    requires_model_validation = False
+
+    def handle(self, addrport='', *args, **options):
+        import django
+        from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
+        from django.core.handlers.wsgi import WSGIHandler
+        try:
+            from werkzeug import run_simple, DebuggedApplication
+        except:
+            raise CommandError("Werkzeug is required to use runserver_plus.  Please visit http://werkzeug.pocoo.org/download")
+
+        # usurp django's handler
+        from django.views import debug
+        debug.technical_500_response = null_technical_500_response
+
+        if args:
+            raise CommandError('Usage is runserver %s' % self.args)
+        if not addrport:
+            addr = ''
+            port = '8000'
+        else:
+            try:
+                addr, port = addrport.split(':')
+            except ValueError:
+                addr, port = '', addrport
+        if not addr:
+            addr = '127.0.0.1'
+
+        if not port.isdigit():
+            raise CommandError("%r is not a valid port number." % port)
+
+        use_reloader = options.get('use_reloader', True)
+        open_browser = options.get('open_browser', False)
+        admin_media_path = options.get('admin_media_path', '')
+        shutdown_message = options.get('shutdown_message', '')
+        quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
+
+        def inner_run():
+            from django.conf import settings
+            print "Validating models..."
+            self.validate(display_num_errors=True)
+            print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
+            print "Development server is running at http://%s:%s/" % (addr, port)
+            print "Using the Werkzeug debugger (http://werkzeug.pocoo.org/)"
+            print "Quit the server with %s." % quit_command
+            path = admin_media_path or django.__path__[0] + '/contrib/admin/media'
+            handler = AdminMediaHandler(WSGIHandler(), path)
+            if open_browser:
+                import webbrowser
+                url = "http://%s:%s/" % (addr, port)
+                webbrowser.open(url)
+            run_simple(addr, int(port), DebuggedApplication(handler, True), 
+                       use_reloader=use_reloader, use_debugger=True)            
+        inner_run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/set_fake_emails.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,75 @@
+"""
+set_fake_emails.py 
+
+    Give all users a new email account. Useful for testing in a 
+    development environment. As such, this command is only available when
+    setting.DEBUG is True.
+
+"""
+from optparse import make_option
+
+from django.conf import settings
+from django.core.management.base import NoArgsCommand, CommandError
+
+DEFAULT_FAKE_EMAIL = '%(username)s@example.com'
+
+class Command(NoArgsCommand):
+    option_list = NoArgsCommand.option_list + (
+        make_option('--email', dest='default_email', default=DEFAULT_FAKE_EMAIL,
+            help='Use this as the new email format.'),
+        make_option('-a', '--no-admin', action="store_true", dest='no_admin', default=False,
+            help='Do not change administrator accounts'),
+        make_option('-s', '--no-staff', action="store_true", dest='no_staff', default=False,
+            help='Do not change staff accounts'),
+        make_option('--include', dest='include_regexp', default=None,
+            help='Include usernames matching this regexp.'),
+        make_option('--exclude', dest='exclude_regexp', default=None,
+            help='Exclude usernames matching this regexp.'),
+        make_option('--include-groups', dest='include_groups', default=None,
+            help='Include users matching this group. (use comma seperation for multiple groups)'),
+        make_option('--exclude-groups', dest='exclude_groups', default=None,
+            help='Exclude users matching this group. (use comma seperation for multiple groups)'),
+    )
+    help = '''DEBUG only: give all users a new email based on their account data ("%s" by default). Possible parameters are: username, first_name, last_name''' % (DEFAULT_FAKE_EMAIL, )
+    requires_model_validation = False
+
+    def handle_noargs(self, **options):
+        if not settings.DEBUG:
+            raise CommandError('Only available in debug mode')
+            
+        from django.contrib.auth.models import User, Group
+        email = options.get('default_email', DEFAULT_FAKE_EMAIL)
+        include_regexp = options.get('include_regexp', None)
+        exclude_regexp = options.get('exclude_regexp', None)
+        include_groups = options.get('include_groups', None)
+        exclude_groups = options.get('exclude_groups', None)
+        no_admin = options.get('no_admin', False)
+        no_staff = options.get('no_staff', False)
+        
+        users = User.objects.all()
+        if no_admin:
+            users = users.exclude(is_superuser=True)
+        if no_staff:
+            users = users.exclude(is_staff=True)
+        if exclude_groups:
+            groups = Group.objects.filter(name__in=exclude_groups.split(","))
+            if groups:
+                users = users.exclude(groups__in=groups)
+            else:
+                raise CommandError("No group matches filter: %s" % exclude_groups)
+        if include_groups:
+            groups = Group.objects.filter(name__in=include_groups.split(","))
+            if groups:
+                users = users.filter(groups__in=groups)
+            else:
+                raise CommandError("No groups matches filter: %s" % include_groups)
+        if exclude_regexp:
+            users = users.exclude(username__regex=exclude_regexp)
+        if include_regexp:
+            users = users.filter(username__regex=include_regexp)
+        for user in users:
+            user.email = email % {'username': user.username,
+                                  'first_name': user.first_name,
+                                  'last_name': user.last_name}
+            user.save()
+        print 'Changed %d emails' % users.count()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/set_fake_passwords.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,44 @@
+"""
+set_fake_passwords.py 
+
+    Reset all user passwords to a common value. Useful for testing in a 
+    development environment. As such, this command is only available when
+    setting.DEBUG is True.
+
+"""
+from optparse import make_option
+
+from django.conf import settings
+from django.core.management.base import NoArgsCommand, CommandError
+
+DEFAULT_FAKE_PASSWORD = 'password'
+
+class Command(NoArgsCommand):
+    option_list = NoArgsCommand.option_list + (
+        make_option('--prompt', dest='prompt_passwd', default=False, action='store_true',
+            help='Prompts for the new password to apply to all users'),
+        make_option('--password', dest='default_passwd', default=DEFAULT_FAKE_PASSWORD,
+            help='Use this as default password.'),
+    )
+    help = 'DEBUG only: sets all user passwords to a common value ("%s" by default)' % (DEFAULT_FAKE_PASSWORD, )
+    requires_model_validation = False
+
+    def handle_noargs(self, **options):
+        if not settings.DEBUG:
+            raise CommandError('Only available in debug mode')
+            
+        from django.contrib.auth.models import User
+        if options.get('prompt_passwd', False):
+            from getpass import getpass
+            passwd = getpass('Password: ')
+            if not passwd:
+                raise CommandError('You must enter a valid password')
+        else:
+            passwd = options.get('default_passwd', DEFAULT_FAKE_PASSWORD)
+        
+        users = User.objects.all()
+        for user in users:
+            user.set_password(passwd)
+            user.save()
+            
+        print 'Reset %d passwords' % users.count()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/shell_plus.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,77 @@
+import os
+from django.core.management.base import NoArgsCommand
+from optparse import make_option
+
+class Command(NoArgsCommand):
+    option_list = NoArgsCommand.option_list + (
+        make_option('--plain', action='store_true', dest='plain',
+            help='Tells Django to use plain Python, not IPython.'),
+        make_option('--no-pythonrc', action='store_true', dest='no_pythonrc',
+            help='Tells Django to use plain Python, not IPython.'),
+    )
+    help = "Like the 'shell' command but autoloads the models of all installed Django apps."
+
+    requires_model_validation = True
+
+    def handle_noargs(self, **options):
+        # XXX: (Temporary) workaround for ticket #1796: force early loading of all
+        # models from installed apps. (this is fixed by now, but leaving it here
+        # for people using 0.96 or older trunk (pre [5919]) versions.
+        from django.db.models.loading import get_models, get_apps
+        loaded_models = get_models()
+
+        use_plain = options.get('plain', False)
+        use_pythonrc = not options.get('no_pythonrc', True)
+
+        # Set up a dictionary to serve as the environment for the shell, so
+        # that tab completion works on objects that are imported at runtime.
+        # See ticket 5082.
+        from django.conf import settings
+        imported_objects = {'settings': settings}
+        for app_mod in get_apps():
+            app_models = get_models(app_mod)
+            if not app_models:
+                continue
+            model_labels = ", ".join([model.__name__ for model in app_models])
+            print self.style.SQL_COLTYPE("From '%s' autoload: %s" % (app_mod.__name__.split('.')[-2], model_labels))
+            for model in app_models:
+                try:
+                    imported_objects[model.__name__] = getattr(__import__(app_mod.__name__, {}, {}, model.__name__), model.__name__)
+                except AttributeError, e:
+                    print self.style.ERROR_OUTPUT("Failed to import '%s' from '%s' reason: %s" % (model.__name__, app_mod.__name__.split('.')[-2], str(e)))
+                    continue
+        try:
+            if use_plain:
+                # Don't bother loading IPython, because the user wants plain Python.
+                raise ImportError
+            import IPython
+            # Explicitly pass an empty list as arguments, because otherwise IPython
+            # would use sys.argv from this script.
+            shell = IPython.Shell.IPShell(argv=[], user_ns=imported_objects)
+            shell.mainloop()
+        except ImportError:
+            # Using normal Python shell
+            import code
+            try: # Try activating rlcompleter, because it's handy.
+                import readline
+            except ImportError:
+                pass
+            else:
+                # We don't have to wrap the following import in a 'try', because
+                # we already know 'readline' was imported successfully.
+                import rlcompleter
+                readline.set_completer(rlcompleter.Completer(imported_objects).complete)
+                readline.parse_and_bind("tab:complete")
+
+            # We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system
+            # conventions and get $PYTHONSTARTUP first then import user.
+            if use_pythonrc:
+                pythonrc = os.environ.get("PYTHONSTARTUP") 
+                if pythonrc and os.path.isfile(pythonrc): 
+                    try: 
+                        execfile(pythonrc) 
+                    except NameError: 
+                        pass
+                # This will import .pythonrc.py as a side-effect
+                import user
+            code.interact(local=imported_objects)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/show_urls.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,44 @@
+from django.conf import settings
+from django.core.management.base import BaseCommand
+try:
+    # 2008-05-30 admindocs found in newforms-admin brand
+    from django.contrib.admindocs.views import extract_views_from_urlpatterns, simplify_regex
+except ImportError:
+    # fall back to trunk, pre-NFA merge
+    from django.contrib.admin.views.doc import extract_views_from_urlpatterns, simplify_regex
+        
+from django_extensions.management.color import color_style
+
+class Command(BaseCommand):
+    help = "Displays all of the url matching routes for the project."
+    
+    requires_model_validation = True
+    
+    def handle(self, *args, **options):
+        if args:
+            appname, = args
+
+        style = color_style()
+
+        if settings.ADMIN_FOR:
+            settings_modules = [__import__(m, {}, {}, ['']) for m in settings.ADMIN_FOR]
+        else:
+            settings_modules = [settings]
+
+        views = []
+        for settings_mod in settings_modules:
+            try:
+                urlconf = __import__(settings_mod.ROOT_URLCONF, {}, {}, [''])
+            except Exception, e:
+                if options.get('traceback', None):
+                    import traceback
+                    traceback.print_exc()
+                print style.ERROR("Error occurred while trying to load %s: %s" % (settings_mod.ROOT_URLCONF, str(e)))
+                continue
+            view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
+            for (func, regex) in view_functions:
+                func_name = hasattr(func, '__name__') and func.__name__ or repr(func)
+                views.append("%(url)s\t%(module)s.%(name)s" % {'name': style.MODULE_NAME(func_name),
+                                       'module': style.MODULE(func.__module__),
+                                       'url': style.URL(simplify_regex(regex))})
+        return "\n".join([v for v in views])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/sqldiff.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,572 @@
+"""
+sqldiff.py - Prints the (approximated) difference between models and database
+
+TODO:
+ - better support for relations
+ - better support for constraints (mainly postgresql?)
+ - support for table spaces with postgresql
+ 
+KNOWN ISSUES:
+ - MySQL has by far the most problems with introspection. Please be
+   carefull when using MySQL with sqldiff. 
+   - Booleans are reported back as Integers, so there's know way to know if
+     there was a real change.
+   - Varchar sizes are reported back without unicode support so there size
+     may change in comparison to the real length of the varchar.   
+   - Some of the 'fixes' to counter these problems might create false 
+     positives or false negatives.
+"""
+
+from django.core.management.base import BaseCommand
+from django.core.management import sql as _sql
+from django.core.management import CommandError
+from django.core.management.color import no_style
+from django.db import transaction, connection
+from django.db.models.fields import IntegerField
+from optparse import make_option
+
+ORDERING_FIELD = IntegerField('_order', null=True)
+
+def flatten(l, ltypes=(list, tuple)):
+    ltype = type(l)
+    l = list(l)
+    i = 0
+    while i < len(l):
+        while isinstance(l[i], ltypes):
+            if not l[i]:
+                l.pop(i)
+                i -= 1
+                break
+            else:
+                l[i:i + 1] = l[i]
+        i += 1
+    return ltype(l)
+
+class SQLDiff(object):
+    DATA_TYPES_REVERSE_OVERRIDE = {
+    }
+    
+    DIFF_TYPES = [
+        'comment',
+        'table-missing-in-db',
+        'field-missing-in-db',
+        'field-missing-in-model',
+        'index-missing-in-db',
+        'index-missing-in-model',
+        'unique-missing-in-db',
+        'unique-missing-in-model',
+        'field-type-differ',
+        'field-parameter-differ',
+    ]
+    DIFF_TEXTS = {
+        'comment': 'comment: %(0)s',
+        'table-missing-in-db': "table '%(0)s' missing in database",
+        'field-missing-in-db' : "field '%(1)s' defined in model but missing in database",
+        'field-missing-in-model' : "field '%(1)s' defined in database but missing in model",
+        'index-missing-in-db' : "field '%(1)s' INDEX defined in model but missing in database",
+        'index-missing-in-model' : "field '%(1)s' INDEX defined in database schema but missing in model",
+        'unique-missing-in-db' : "field '%(1)s' UNIQUE defined in model but missing in database",
+        'unique-missing-in-model' : "field '%(1)s' UNIQUE defined in database schema but missing in model",
+        'field-type-differ' : "field '%(1)s' not of same type: db='%(3)s', model='%(2)s'",
+        'field-parameter-differ' : "field '%(1)s' parameters differ: db='%(3)s', model='%(2)s'",
+    }
+
+    SQL_FIELD_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
+    SQL_FIELD_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP COLUMN'), style.SQL_FIELD(qn(args[1])))
+    SQL_INDEX_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('CREATE INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))), style.SQL_KEYWORD('ON'), style.SQL_TABLE(qn(args[0])), style.SQL_FIELD(qn(args[1])))
+    # FIXME: need to lookup index name instead of just appending _idx to table + fieldname
+    SQL_INDEX_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s;" % (style.SQL_KEYWORD('DROP INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))))
+    SQL_UNIQUE_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_KEYWORD('UNIQUE'), style.SQL_FIELD(qn(args[1])))
+    # FIXME: need to lookup unique constraint name instead of appending _key to table + fieldname
+    SQL_UNIQUE_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP'), style.SQL_KEYWORD('CONSTRAINT'), style.SQL_TABLE(qn("%s_key" % ('_'.join(args[:2])))))
+    SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
+    SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
+    SQL_COMMENT = lambda self, style, qn, args: style.NOTICE('-- Comment: %s' % style.SQL_TABLE(args[0]))
+    SQL_TABLE_MISSING_IN_DB = lambda self, style, qn, args: style.NOTICE('-- Table missing: %s' % args[0])
+
+    def __init__(self, app_models, options):
+        self.app_models = app_models
+        self.options = options
+        self.dense = options.get('dense_output', False)
+
+        try:
+            self.introspection = connection.introspection
+        except AttributeError:
+            from django.db import get_introspection_module
+            self.introspection = get_introspection_module()
+
+        self.cursor = connection.cursor()
+        self.django_tables = self.get_django_tables(options.get('only_existing', True))
+        self.db_tables = self.introspection.get_table_list(self.cursor)
+        self.differences = []
+        self.unknown_db_fields = {}
+        
+        self.DIFF_SQL = {
+            'comment': self.SQL_COMMENT,
+            'table-missing-in-db': self.SQL_TABLE_MISSING_IN_DB,
+            'field-missing-in-db': self.SQL_FIELD_MISSING_IN_DB,
+            'field-missing-in-model': self.SQL_FIELD_MISSING_IN_MODEL,
+            'index-missing-in-db': self.SQL_INDEX_MISSING_IN_DB,
+            'index-missing-in-model': self.SQL_INDEX_MISSING_IN_MODEL,
+            'unique-missing-in-db': self.SQL_UNIQUE_MISSING_IN_DB,
+            'unique-missing-in-model': self.SQL_UNIQUE_MISSING_IN_MODEL,
+            'field-type-differ': self.SQL_FIELD_TYPE_DIFFER,
+            'field-parameter-differ': self.SQL_FIELD_PARAMETER_DIFFER,
+        }
+
+
+    def add_app_model_marker(self, app_label, model_name):
+        self.differences.append((app_label, model_name, []))
+        
+    def add_difference(self, diff_type, *args):
+        assert diff_type in self.DIFF_TYPES, 'Unknown difference type'
+        self.differences[-1][-1].append((diff_type, args))
+
+    def get_django_tables(self, only_existing):
+        try:
+            django_tables = self.introspection.django_table_names(only_existing=only_existing)
+        except AttributeError:
+            # backwards compatibility for before introspection refactoring (r8296)
+            try:
+                django_tables = _sql.django_table_names(only_existing=only_existing)
+            except AttributeError:
+                # backwards compatibility for before svn r7568
+                django_tables = _sql.django_table_list(only_existing=only_existing)
+        return django_tables
+
+    def sql_to_dict(self, query,param):
+        """ sql_to_dict(query, param) -> list of dicts
+
+        code from snippet at http://www.djangosnippets.org/snippets/1383/
+        """
+        cursor = connection.cursor()
+        cursor.execute(query,param)
+        fieldnames = [name[0] for name in cursor.description]
+        result = []
+        for row in cursor.fetchall():
+            rowset = []
+            for field in zip(fieldnames, row):
+                rowset.append(field)
+            result.append(dict(rowset))
+        return result
+
+    def get_field_model_type(self, field):
+        return field.db_type()
+
+    def get_field_db_type(self, description, field=None, table_name=None):
+        from django.db import models
+        # DB-API cursor.description
+        #(name, type_code, display_size, internal_size, precision, scale, null_ok) = description
+        type_code = description[1]
+        if type_code in self.DATA_TYPES_REVERSE_OVERRIDE:
+            reverse_type = self.DATA_TYPES_REVERSE_OVERRIDE[type_code]
+        else:
+            try:
+                try:
+                    reverse_type = self.introspection.data_types_reverse[type_code]
+                except AttributeError:
+                    # backwards compatibility for before introspection refactoring (r8296)
+                    reverse_type = self.introspection.DATA_TYPES_REVERSE.get(type_code)
+            except KeyError:
+                # type_code not found in data_types_reverse map
+                key = (self.differences[-1][:2], description[:2])
+                if key not in self.unknown_db_fields:
+                    self.unknown_db_fields[key] = 1
+                    self.add_difference('comment', "Unknown database type for field '%s' (%s)" % (description[0], type_code))
+                return None
+        
+        kwargs = {}
+        if isinstance(reverse_type, tuple):
+            kwargs.update(reverse_type[1])
+            reverse_type = reverse_type[0]
+
+        if reverse_type == "CharField" and description[3]:
+            kwargs['max_length'] = description[3]
+
+        if reverse_type == "DecimalField":
+            kwargs['max_digits'] = description[4]
+            kwargs['decimal_places'] = description[5]
+
+        if description[6]:
+            kwargs['blank'] = True
+            if not reverse_type in ('TextField', 'CharField'):
+                kwargs['null'] = True
+
+        field_db_type = getattr(models, reverse_type)(**kwargs).db_type()
+        return field_db_type
+
+    def strip_parameters(self, field_type):
+        if field_type:
+            return field_type.split(" ")[0].split("(")[0]
+        return field_type
+
+    def find_unique_missing_in_db(self, meta, table_indexes, table_name):
+        for field in meta.fields:
+            if field.unique:
+                attname = field.db_column or field.attname
+                if attname in table_indexes and table_indexes[attname]['unique']:
+                    continue
+                self.add_difference('unique-missing-in-db', table_name, attname)
+
+    def find_unique_missing_in_model(self, meta, table_indexes, table_name):
+        # TODO: Postgresql does not list unique_togethers in table_indexes
+        #       MySQL does
+        fields = dict([(field.db_column or field.name, field.unique) for field in meta.fields])
+        for att_name, att_opts in table_indexes.iteritems():
+            if att_opts['unique'] and att_name in fields and not fields[att_name]:
+                if att_name in flatten(meta.unique_together): continue
+                self.add_difference('unique-missing-in-model', table_name, att_name)
+
+    def find_index_missing_in_db(self, meta, table_indexes, table_name):
+        for field in meta.fields:
+            if field.db_index:
+                attname = field.db_column or field.attname
+                if not attname in table_indexes:
+                    self.add_difference('index-missing-in-db', table_name, attname)
+
+    def find_index_missing_in_model(self, meta, table_indexes, table_name):
+        fields = dict([(field.name, field) for field in meta.fields])
+        for att_name, att_opts in table_indexes.iteritems():
+            if att_name in fields:
+                field = fields[att_name]
+                if field.db_index: continue
+                if att_opts['primary_key'] and field.primary_key: continue
+                if att_opts['unique'] and field.unique: continue
+                if att_opts['unique'] and att_name in flatten(meta.unique_together): continue
+                self.add_difference('index-missing-in-model', table_name, att_name)
+
+    def find_field_missing_in_model(self, fieldmap, table_description, table_name):
+        for row in table_description:
+            if row[0] not in fieldmap:
+                self.add_difference('field-missing-in-model', table_name, row[0])
+
+    def find_field_missing_in_db(self, fieldmap, table_description, table_name):
+        db_fields = [row[0] for row in table_description]
+        for field_name, field in fieldmap.iteritems():
+            if field_name not in db_fields:
+                self.add_difference('field-missing-in-db', table_name, field_name, field.db_type())
+
+    def find_field_type_differ(self, meta, table_description, table_name, func=None):
+        db_fields = dict([(row[0], row) for row in table_description])
+        for field in meta.fields:
+            if field.name not in db_fields: continue
+            description = db_fields[field.name]
+
+            model_type = self.strip_parameters(self.get_field_model_type(field))
+            db_type = self.strip_parameters(self.get_field_db_type(description, field))
+
+            # use callback function if defined
+            if func:
+                model_type, db_type = func(field, description, model_type, db_type)
+
+            if not model_type==db_type:
+                self.add_difference('field-type-differ', table_name, field.name, model_type, db_type)
+
+    def find_field_parameter_differ(self, meta, table_description, table_name, func=None):
+        db_fields = dict([(row[0], row) for row in table_description])
+        for field in meta.fields:
+            if field.name not in db_fields: continue
+            description = db_fields[field.name]
+
+            model_type = self.get_field_model_type(field)
+            db_type = self.get_field_db_type(description, field, table_name)
+
+            if not self.strip_parameters(model_type)==self.strip_parameters(db_type):
+                continue
+            
+            # use callback function if defined
+            if func:
+                model_type, db_type = func(field, description, model_type, db_type)
+
+            if not model_type==db_type:
+                self.add_difference('field-parameter-differ', table_name, field.name, model_type, db_type)
+    
+    @transaction.commit_manually
+    def find_differences(self):
+        cur_app_label = None
+        for app_model in self.app_models:
+            meta = app_model._meta
+            table_name = meta.db_table
+            app_label = meta.app_label
+
+            if cur_app_label!=app_label:
+                # Marker indicating start of difference scan for this table_name
+                self.add_app_model_marker(app_label, app_model.__name__)
+
+            #if not table_name in self.django_tables:
+            if not table_name in self.db_tables:
+                # Table is missing from database
+                self.add_difference('table-missing-in-db', table_name)
+                continue
+            
+            table_indexes = self.introspection.get_indexes(self.cursor, table_name)
+            fieldmap = dict([(field.db_column or field.get_attname(), field) for field in meta.fields])
+            
+            # add ordering field if model uses order_with_respect_to
+            if meta.order_with_respect_to:
+                fieldmap['_order'] = ORDERING_FIELD
+
+            try:
+                table_description = self.introspection.get_table_description(self.cursor, table_name)
+            except Exception, e:
+                model_diffs.append((app_model.__name__, [str(e).strip()]))
+                transaction.rollback() # reset transaction
+                continue
+            
+            # Fields which are defined in database but not in model
+            # 1) find: 'unique-missing-in-model'
+            self.find_unique_missing_in_model(meta, table_indexes, table_name)
+            # 2) find: 'index-missing-in-model'
+            self.find_index_missing_in_model(meta, table_indexes, table_name)
+            # 3) find: 'field-missing-in-model'
+            self.find_field_missing_in_model(fieldmap, table_description, table_name)
+
+            # Fields which are defined in models but not in database
+            # 4) find: 'field-missing-in-db'
+            self.find_field_missing_in_db(fieldmap, table_description, table_name)
+            # 5) find: 'unique-missing-in-db'
+            self.find_unique_missing_in_db(meta, table_indexes, table_name)
+            # 6) find: 'index-missing-in-db'
+            self.find_index_missing_in_db(meta, table_indexes, table_name)
+
+            # Fields which have a different type or parameters
+            # 7) find: 'type-differs'
+            self.find_field_type_differ(meta, table_description, table_name)
+            # 8) find: 'type-parameter-differs'
+            self.find_field_parameter_differ(meta, table_description, table_name)
+
+    def print_diff(self, style=no_style()):
+        """ print differences to stdout """
+        if self.options.get('sql', True):
+            self.print_diff_sql(style)
+        else:
+            self.print_diff_text(style)
+
+    def print_diff_text(self, style):
+        cur_app_label = None
+        for app_label, model_name, diffs in self.differences:
+            if not diffs: continue
+            if not self.dense and cur_app_label != app_label:
+                print style.NOTICE("+ Application:"), style.SQL_TABLE(app_label)
+                cur_app_label = app_label
+            if not self.dense:
+                print style.NOTICE("|-+ Differences for model:"), style.SQL_TABLE(model_name)
+            for diff in diffs:
+                diff_type, diff_args = diff
+                text = self.DIFF_TEXTS[diff_type] % dict((str(i), style.SQL_TABLE(e)) for i, e in enumerate(diff_args))
+                text = "'".join(i%2==0 and style.ERROR_OUTPUT(e) or e for i, e in enumerate(text.split("'")))
+                if not self.dense:
+                    print style.NOTICE("|--+"), text
+                else:
+                    print style.NOTICE("App"), style.SQL_TABLE(app_name), style.NOTICE('Model'), style.SQL_TABLE(model_name), text
+
+    def print_diff_sql(self, style):
+        cur_app_label = None
+        qn = connection.ops.quote_name
+        print style.SQL_KEYWORD("BEGIN;")
+        for app_label, model_name, diffs in self.differences:
+            if not diffs: continue
+            if not self.dense and cur_app_label != app_label:
+                print style.NOTICE("-- Application: %s" % style.SQL_TABLE(app_label))
+                cur_app_label = app_label
+            if not self.dense:
+                print style.NOTICE("-- Model: %s" % style.SQL_TABLE(model_name))
+            for diff in diffs:
+                diff_type, diff_args = diff
+                text = self.DIFF_SQL[diff_type](style, qn, diff_args)
+                if self.dense:
+                    text = text.replace("\n\t", " ")
+                print text
+        print style.SQL_KEYWORD("COMMIT;")
+        
+class GenericSQLDiff(SQLDiff):
+    pass
+
+class MySQLDiff(SQLDiff):
+    # All the MySQL hacks together create something of a problem
+    # Fixing one bug in MySQL creates another issue. So just keep in mind
+    # that this is way unreliable for MySQL atm.
+    def get_field_db_type(self, description, field=None, table_name=None):
+        from MySQLdb.constants import FIELD_TYPE
+        # weird bug? in mysql db-api where it returns three times the correct value for field length
+        # if i remember correctly it had something todo with unicode strings
+        # TODO: Fix this is a more meaningful and better understood manner
+        description = list(description)
+        if description[1] not in [FIELD_TYPE.TINY, FIELD_TYPE.SHORT]: # exclude tinyints from conversion.
+            description[3] = description[3]/3
+            description[4] = description[4]/3
+        db_type = super(MySQLDiff, self).get_field_db_type(description)
+        if not db_type:
+            return
+        if field:
+            if field.primary_key and db_type=='integer':
+                db_type += ' AUTO_INCREMENT'
+            # MySQL isn't really sure about char's and varchar's like sqlite
+            field_type = self.get_field_model_type(field)
+            # Fix char/varchar inconsistencies
+            if self.strip_parameters(field_type)=='char' and self.strip_parameters(db_type)=='varchar':
+                db_type = db_type.lstrip("var")
+            # They like to call 'bool's 'tinyint(1)' and introspection makes that a integer
+            # just convert it back to it's proper type, a bool is a bool and nothing else.
+            if db_type=='integer' and description[1]==FIELD_TYPE.TINY and description[4]==1:
+                db_type = 'bool'
+            if db_type=='integer' and description[1]==FIELD_TYPE.SHORT:
+                db_type = 'smallint UNSIGNED' # FIXME: what about if it's not UNSIGNED ?
+        return db_type
+
+class SqliteSQLDiff(SQLDiff):
+    # Unique does not seem to be implied on Sqlite for Primary_key's
+    # if this is more generic among databases this might be usefull
+    # to add to the superclass's find_unique_missing_in_db method
+    def find_unique_missing_in_db(self, meta, table_indexes, table_name):
+        for field in meta.fields:
+            if field.unique:
+                attname = field.attname
+                if attname in table_indexes and table_indexes[attname]['unique']:
+                    continue
+                if table_indexes[attname]['primary_key']:
+                    continue
+                self.add_difference('unique-missing-in-db', table_name, attname)
+
+    # Finding Indexes by using the get_indexes dictionary doesn't seem to work
+    # for sqlite.
+    def find_index_missing_in_db(self, meta, table_indexes, table_name):
+        pass
+
+    def find_index_missing_in_model(self, meta, table_indexes, table_name):
+        pass
+
+    def get_field_db_type(self, description, field=None, table_name=None):
+        db_type = super(SqliteSQLDiff, self).get_field_db_type(description)
+        if not db_type:
+            return
+        if field:
+            field_type = self.get_field_model_type(field)
+            # Fix char/varchar inconsistencies
+            if self.strip_parameters(field_type)=='char' and self.strip_parameters(db_type)=='varchar':
+                db_type = db_type.lstrip("var")
+        return db_type
+
+class PostgresqlSQLDiff(SQLDiff):
+    DATA_TYPES_REVERSE_OVERRIDE = {
+        20: 'IntegerField',
+        1042: 'CharField',
+    }
+
+    # Hopefully in the future we can add constraint checking and other more
+    # advanced checks based on this database.
+    SQL_LOAD_CONSTRAINTS = """
+    SELECT nspname, relname, conname, attname, pg_get_constraintdef(pg_constraint.oid)
+    FROM pg_constraint
+    INNER JOIN pg_attribute ON pg_constraint.conrelid = pg_attribute.attrelid AND pg_attribute.attnum = any(pg_constraint.conkey)
+    INNER JOIN pg_class ON conrelid=pg_class.oid
+    INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
+    ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END,contype,nspname,relname,conname;
+    """
+
+    SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2]))
+    SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args:  "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2]))
+
+    def __init__(self, app_models, options):
+        SQLDiff.__init__(self, app_models, options)
+        self.check_constraints = {}
+        self.load_constraints()
+
+    def load_constraints(self):
+        for dct in self.sql_to_dict(self.SQL_LOAD_CONSTRAINTS, []):
+            key = (dct['nspname'], dct['relname'], dct['attname'])
+            if 'CHECK' in dct['pg_get_constraintdef']:
+                self.check_constraints[key] = dct
+
+    def get_field_db_type(self, description, field=None, table_name=None):
+        db_type = super(PostgresqlSQLDiff, self).get_field_db_type(description)
+        if not db_type:
+            return
+        if field:
+            if field.primary_key and db_type=='integer':
+                db_type = 'serial'
+            if table_name:
+                tablespace = field.db_tablespace
+                if tablespace=="":
+                    tablespace = "public"
+                check_constraint = self.check_constraints.get((tablespace, table_name, field.attname),{}).get('pg_get_constraintdef', None)
+                if check_constraint:
+                    check_constraint = check_constraint.replace("((", "(")
+                    check_constraint = check_constraint.replace("))", ")")
+                    check_constraint = '("'.join([')' in e and '" '.join(e.split(" ", 1)) or e for e in check_constraint.split("(")])
+                    # TODO: might be more then one constraint in definition ?
+                    db_type += ' '+check_constraint
+        return db_type
+
+    """
+    def find_field_type_differ(self, meta, table_description, table_name):
+        def callback(field, description, model_type, db_type):
+            if field.primary_key and db_type=='integer':
+                db_type = 'serial'
+            return model_type, db_type
+        super(PostgresqlSQLDiff, self).find_field_type_differs(meta, table_description, table_name, callback)
+    """
+
+DATABASE_SQLDIFF_CLASSES = {
+    'postgresql_psycopg2' : PostgresqlSQLDiff,
+    'postgresql': PostgresqlSQLDiff,
+    'mysql': MySQLDiff,
+    'sqlite3': SqliteSQLDiff,
+    'oracle': GenericSQLDiff
+}
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--all-applications', '-a', action='store_true', dest='all_applications',
+                    help="Automaticly include all application from INSTALLED_APPS."),
+        make_option('--not-only-existing', '-e', action='store_false', dest='only_existing',
+                    help="Check all tables that exist in the database, not only tables that should exist based on models."),
+        make_option('--dense-output', '-d', action='store_true', dest='dense_output',
+                    help="Shows the output in dense format, normally output is spreaded over multiple lines."),
+        make_option('--output_text', '-t', action='store_false', dest='sql', default=True,
+                    help="Outputs the differences as descriptive text instead of SQL"),
+    )
+    
+    help = """Prints the (approximated) difference between models and fields in the database for the given app name(s).
+
+It indicates how columns in the database are different from the sql that would
+be generated by Django. This command is not a database migration tool. (Though
+it can certainly help) It's purpose is to show the current differences as a way
+to check/debug ur models compared to the real database tables and columns."""
+
+    output_transaction = False
+    args = '<appname appname ...>'
+
+    def handle(self, *app_labels, **options):
+        from django.db import models
+        from django.conf import settings
+
+        if settings.DATABASE_ENGINE =='dummy':
+            # This must be the "dummy" database backend, which means the user
+            # hasn't set DATABASE_ENGINE.
+            raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" +
+                "because you haven't specified the DATABASE_ENGINE setting.\n" +
+                "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.")
+
+        if options.get('all_applications', False):
+            app_models = models.get_models()
+        else:
+            if not app_labels:
+                raise CommandError('Enter at least one appname.')
+            try:
+                app_list = [models.get_app(app_label) for app_label in app_labels]
+            except (models.ImproperlyConfigured, ImportError), e:
+                raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
+
+            app_models = []
+            for app in app_list:
+                app_models.extend(models.get_models(app))
+
+        if not app_models:
+            raise CommandError('Unable to execute sqldiff no models founds.')
+
+        cls = DATABASE_SQLDIFF_CLASSES.get(settings.DATABASE_ENGINE, GenericSQLDiff)
+        sqldiff_instance = cls(app_models, options)
+        sqldiff_instance.find_differences()
+        sqldiff_instance.print_diff(self.style)
+        return
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/sync_media_s3.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,265 @@
+"""
+Sync Media to S3
+================
+
+Django command that scans all files in your settings.MEDIA_ROOT folder and
+uploads them to S3 with the same directory structure.
+
+This command can optionally do the following but it is off by default:
+* gzip compress any CSS and Javascript files it finds and adds the appropriate
+  'Content-Encoding' header.
+* set a far future 'Expires' header for optimal caching.
+
+Note: This script requires the Python boto library and valid Amazon Web
+Services API keys.
+
+Required settings.py variables:
+AWS_ACCESS_KEY_ID = ''
+AWS_SECRET_ACCESS_KEY = ''
+AWS_BUCKET_NAME = ''
+
+Command options are:
+  -p PREFIX, --prefix=PREFIX
+                        The prefix to prepend to the path on S3.
+  --gzip                Enables gzipping CSS and Javascript files.
+  --expires             Enables setting a far future expires header.
+  --force               Skip the file mtime check to force upload of all
+                        files.
+  --filter-list         Override default directory and file exclusion
+                        filters. (enter as comma seperated line)
+
+TODO:
+ * Use fnmatch (or regex) to allow more complex FILTER_LIST rules.
+
+"""
+import datetime
+import email
+import mimetypes
+import optparse
+import os
+import sys
+import time
+
+from django.conf import settings
+from django.core.management.base import BaseCommand, CommandError
+
+# Make sure boto is available
+try:
+    import boto
+    import boto.exception
+except ImportError:
+    raise ImportError, "The boto Python library is not installed."
+
+class Command(BaseCommand):
+
+    # Extra variables to avoid passing these around
+    AWS_ACCESS_KEY_ID = ''
+    AWS_SECRET_ACCESS_KEY = ''
+    AWS_BUCKET_NAME = ''
+    DIRECTORY = ''
+    FILTER_LIST = ['.DS_Store', '.svn', '.hg', '.git', 'Thumbs.db']
+    GZIP_CONTENT_TYPES = (
+        'text/css',
+        'application/javascript',
+        'application/x-javascript'
+    )
+
+    upload_count = 0
+    skip_count = 0
+
+    option_list = BaseCommand.option_list + (
+        optparse.make_option('-p', '--prefix',
+            dest='prefix', default='',
+            help="The prefix to prepend to the path on S3."),
+        optparse.make_option('-d', '--dir',
+            dest='dir', default=settings.MEDIA_ROOT,
+            help="The root directory to use instead of your MEDIA_ROOT"),
+        optparse.make_option('--gzip',
+            action='store_true', dest='gzip', default=False,
+            help="Enables gzipping CSS and Javascript files."),
+        optparse.make_option('--expires',
+            action='store_true', dest='expires', default=False,
+            help="Enables setting a far future expires header."),
+        optparse.make_option('--force',
+            action='store_true', dest='force', default=False,
+            help="Skip the file mtime check to force upload of all files."),
+        optparse.make_option('--filter-list', dest='filter_list',
+            action='store', default='',
+            help="Override default directory and file exclusion filters. (enter as comma seperated line)"),
+    )
+
+    help = 'Syncs the complete MEDIA_ROOT structure and files to S3 into the given bucket name.'
+    args = 'bucket_name'
+
+    can_import_settings = True
+
+    def handle(self, *args, **options):
+
+        # Check for AWS keys in settings
+        if not hasattr(settings, 'AWS_ACCESS_KEY_ID') or \
+           not hasattr(settings, 'AWS_SECRET_ACCESS_KEY'):
+           raise CommandError('Missing AWS keys from settings file.  Please' +
+                     'supply both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.')
+        else:
+            self.AWS_ACCESS_KEY_ID = settings.AWS_ACCESS_KEY_ID
+            self.AWS_SECRET_ACCESS_KEY = settings.AWS_SECRET_ACCESS_KEY
+
+        if not hasattr(settings, 'AWS_BUCKET_NAME'):
+            raise CommandError('Missing bucket name from settings file. Please' +
+                ' add the AWS_BUCKET_NAME to your settings file.')
+        else:
+            if not settings.AWS_BUCKET_NAME:
+                raise CommandError('AWS_BUCKET_NAME cannot be empty.')
+        self.AWS_BUCKET_NAME = settings.AWS_BUCKET_NAME
+
+        if not hasattr(settings, 'MEDIA_ROOT'):
+            raise CommandError('MEDIA_ROOT must be set in your settings.')
+        else:
+            if not settings.MEDIA_ROOT:
+                raise CommandError('MEDIA_ROOT must be set in your settings.')
+
+        self.verbosity = int(options.get('verbosity'))
+        self.prefix = options.get('prefix')
+        self.do_gzip = options.get('gzip')
+        self.do_expires = options.get('expires')
+        self.do_force = options.get('force')
+        self.DIRECTORY = options.get('dir')
+        self.FILTER_LIST = getattr(settings, 'FILTER_LIST', self.FILTER_LIST)
+        filter_list = options.get('filter_list').split(',')
+        if filter_list:
+            # command line option overrides default filter_list and
+            # settings.filter_list
+            self.FILTER_LIST = filter_list
+
+        # Now call the syncing method to walk the MEDIA_ROOT directory and
+        # upload all files found.
+        self.sync_s3()
+
+        print
+        print "%d files uploaded." % (self.upload_count)
+        print "%d files skipped." % (self.skip_count)
+
+    def sync_s3(self):
+        """
+        Walks the media directory and syncs files to S3
+        """
+        bucket, key = self.open_s3()
+        os.path.walk(self.DIRECTORY, self.upload_s3,
+            (bucket, key, self.AWS_BUCKET_NAME, self.DIRECTORY))
+
+    def compress_string(self, s):
+        """Gzip a given string."""
+        import cStringIO, gzip
+        zbuf = cStringIO.StringIO()
+        zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf)
+        zfile.write(s)
+        zfile.close()
+        return zbuf.getvalue()
+
+    def open_s3(self):
+        """
+        Opens connection to S3 returning bucket and key
+        """
+        conn = boto.connect_s3(self.AWS_ACCESS_KEY_ID, self.AWS_SECRET_ACCESS_KEY)
+        try:
+            bucket = conn.get_bucket(self.AWS_BUCKET_NAME)
+        except boto.exception.S3ResponseError:
+            bucket = conn.create_bucket(self.AWS_BUCKET_NAME)
+        return bucket, boto.s3.key.Key(bucket)
+
+    def upload_s3(self, arg, dirname, names):
+        """
+        This is the callback to os.path.walk and where much of the work happens
+        """
+        bucket, key, bucket_name, root_dir = arg # expand arg tuple
+
+        # Skip directories we don't want to sync
+        if os.path.basename(dirname) in self.FILTER_LIST:
+            # prevent walk from processing subfiles/subdirs below the ignored one
+            del names[:]
+            return 
+
+        # Later we assume the MEDIA_ROOT ends with a trailing slash
+        if not root_dir.endswith(os.path.sep):
+            root_dir = root_dir + os.path.sep
+
+        for file in names:
+            headers = {}
+
+            if file in self.FILTER_LIST:
+                continue # Skip files we don't want to sync
+
+            filename = os.path.join(dirname, file)
+            if os.path.isdir(filename):
+                continue # Don't try to upload directories
+
+            file_key = filename[len(root_dir):]
+            if self.prefix:
+                file_key = '%s/%s' % (self.prefix, file_key)
+
+            # Check if file on S3 is older than local file, if so, upload
+            if not self.do_force:
+                s3_key = bucket.get_key(file_key)
+                if s3_key:
+                    s3_datetime = datetime.datetime(*time.strptime(
+                        s3_key.last_modified, '%a, %d %b %Y %H:%M:%S %Z')[0:6])
+                    local_datetime = datetime.datetime.utcfromtimestamp(
+                        os.stat(filename).st_mtime)
+                    if local_datetime < s3_datetime:
+                        self.skip_count += 1
+                        if self.verbosity > 1:
+                            print "File %s hasn't been modified since last " \
+                                "being uploaded" % (file_key)
+                        continue
+
+            # File is newer, let's process and upload
+            if self.verbosity > 0:
+                print "Uploading %s..." % (file_key)
+
+            content_type = mimetypes.guess_type(filename)[0]
+            if content_type:
+                headers['Content-Type'] = content_type
+            file_obj = open(filename, 'rb')
+            file_size = os.fstat(file_obj.fileno()).st_size
+            filedata = file_obj.read()
+            if self.do_gzip:
+                # Gzipping only if file is large enough (>1K is recommended) 
+                # and only if file is a common text type (not a binary file)
+                if file_size > 1024 and content_type in self.GZIP_CONTENT_TYPES:
+                    filedata = self.compress_string(filedata)
+                    headers['Content-Encoding'] = 'gzip'
+                    if self.verbosity > 1:
+                        print "\tgzipped: %dk to %dk" % \
+                            (file_size/1024, len(filedata)/1024)
+            if self.do_expires:
+                # HTTP/1.0
+                headers['Expires'] = '%s GMT' % (email.Utils.formatdate(
+                    time.mktime((datetime.datetime.now() +
+                    datetime.timedelta(days=365*2)).timetuple())))
+                # HTTP/1.1
+                headers['Cache-Control'] = 'max-age %d' % (3600 * 24 * 365 * 2)
+                if self.verbosity > 1:
+                    print "\texpires: %s" % (headers['Expires'])
+                    print "\tcache-control: %s" % (headers['Cache-Control'])
+
+            try:
+                key.name = file_key
+                key.set_contents_from_string(filedata, headers, replace=True)
+                key.set_acl('public-read')
+            except boto.s3.connection.S3CreateError, e:
+                print "Failed: %s" % e
+            except Exception, e:
+                print e
+                raise
+            else:
+                self.upload_count += 1
+
+            file_obj.close()
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        optparse.make_option('-v', '--verbosity',
+            dest='verbosity', default=1, action='count',
+            help="Verbose mode. Multiple -v options increase the verbosity."),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/commands/syncdata.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,221 @@
+""" 
+SyncData
+========
+
+Django command similar to 'loaddata' but also deletes.
+After 'syncdata' has run, the database will have the same data as the fixture - anything
+missing will of been added, anything different will of been updated,
+and anything extra will of been deleted.
+"""
+
+from django.core.management.base import BaseCommand
+from django.core.management.color import no_style
+from optparse import make_option
+import sys
+import os
+
+class Command(BaseCommand):
+    """ syncdata command """
+    
+    help = 'Makes the current database have the same data as the fixture(s), no more, no less.'
+    args = "fixture [fixture ...]"
+    
+    def remove_objects_not_in(self, objects_to_keep, verbosity):
+        """
+        Deletes all the objects in the database that are not in objects_to_keep.
+        - objects_to_keep: A map where the keys are classes, and the values are a
+         set of the objects of that class we should keep.
+        """
+        for class_ in objects_to_keep.keys():
+
+            current = class_.objects.all()
+            current_ids = set( [x.id for x in current] )
+            keep_ids = set( [x.id for x in objects_to_keep[class_]] )
+
+            remove_these_ones = current_ids.difference(keep_ids)
+            if remove_these_ones:
+
+                for obj in current:
+                    if obj.id in remove_these_ones:
+                        obj.delete()
+                        if verbosity >= 2:
+                            print "Deleted object: "+ unicode(obj)
+
+            if verbosity > 0 and remove_these_ones:
+                num_deleted = len(remove_these_ones)
+                if num_deleted > 1:
+                    type_deleted = unicode(class_._meta.verbose_name_plural)
+                else:
+                    type_deleted = unicode(class_._meta.verbose_name)
+
+                print "Deleted "+ str(num_deleted) +" "+ type_deleted
+
+    def handle(self, *fixture_labels, **options):
+        """ Main method of a Django command """
+        from django.db.models import get_apps
+        from django.core import serializers
+        from django.db import connection, transaction
+        from django.conf import settings
+
+        self.style = no_style()
+
+        verbosity = int(options.get('verbosity', 1))
+        show_traceback = options.get('traceback', False)
+        
+        # Keep a count of the installed objects and fixtures
+        fixture_count = 0
+        object_count = 0
+        objects_per_fixture = []
+        models = set()
+
+        humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
+
+        # Get a cursor (even though we don't need one yet). This has
+        # the side effect of initializing the test database (if
+        # it isn't already initialized).
+        cursor = connection.cursor()
+
+        # Start transaction management. All fixtures are installed in a
+        # single transaction to ensure that all references are resolved.
+        transaction.commit_unless_managed()
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+
+        app_fixtures = [os.path.join(os.path.dirname(app.__file__), 'fixtures') \
+                        for app in get_apps()]
+        for fixture_label in fixture_labels:
+            parts = fixture_label.split('.')
+            if len(parts) == 1:
+                fixture_name = fixture_label
+                formats = serializers.get_public_serializer_formats()
+            else:
+                fixture_name, format = '.'.join(parts[:-1]), parts[-1]
+                if format in serializers.get_public_serializer_formats():
+                    formats = [format]
+                else:
+                    formats = []
+
+            if formats:
+                if verbosity > 1:
+                    print "Loading '%s' fixtures..." % fixture_name
+            else:
+                sys.stderr.write(
+                    self.style.ERROR("Problem installing fixture '%s': %s is not a known "+ \
+                                     "serialization format." % (fixture_name, format))
+                    )
+                transaction.rollback()
+                transaction.leave_transaction_management()
+                return
+
+            if os.path.isabs(fixture_name):
+                fixture_dirs = [fixture_name]
+            else:
+                fixture_dirs = app_fixtures + list(settings.FIXTURE_DIRS) + ['']
+
+            for fixture_dir in fixture_dirs:
+                if verbosity > 1:
+                    print "Checking %s for fixtures..." % humanize(fixture_dir)
+
+                label_found = False
+                for format in formats:
+                    serializer = serializers.get_serializer(format)
+                    if verbosity > 1:
+                        print "Trying %s for %s fixture '%s'..." % \
+                            (humanize(fixture_dir), format, fixture_name)
+                    try:
+                        full_path = os.path.join(fixture_dir, '.'.join([fixture_name, format]))
+                        fixture = open(full_path, 'r')
+                        if label_found:
+                            fixture.close()
+                            print self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting." %
+                                (fixture_name, humanize(fixture_dir)))
+                            transaction.rollback()
+                            transaction.leave_transaction_management()
+                            return
+                        else:
+                            fixture_count += 1
+                            objects_per_fixture.append(0)
+                            if verbosity > 0:
+                                print "Installing %s fixture '%s' from %s." % \
+                                    (format, fixture_name, humanize(fixture_dir))
+                            try:
+                                objects_to_keep = {}
+                                objects = serializers.deserialize(format, fixture)
+                                for obj in objects:
+                                    object_count += 1
+                                    objects_per_fixture[-1] += 1
+
+                                    class_ = obj.object.__class__
+                                    if not class_ in objects_to_keep:
+                                        objects_to_keep[class_] = set()
+                                    objects_to_keep[class_].add(obj.object)
+                                    
+                                    models.add(class_)
+                                    obj.save()
+
+                                self.remove_objects_not_in(objects_to_keep, verbosity)
+
+                                label_found = True
+                            except (SystemExit, KeyboardInterrupt):
+                                raise
+                            except Exception:
+                                import traceback
+                                fixture.close()
+                                transaction.rollback()
+                                transaction.leave_transaction_management()
+                                if show_traceback:
+                                    traceback.print_exc()
+                                else:
+                                    sys.stderr.write(
+                                        self.style.ERROR("Problem installing fixture '%s': %s\n" %
+                                             (full_path, traceback.format_exc())))
+                                return
+                            fixture.close()
+                    except:
+                        if verbosity > 1:
+                            print "No %s fixture '%s' in %s." % \
+                                (format, fixture_name, humanize(fixture_dir))
+
+        # If any of the fixtures we loaded contain 0 objects, assume that an 
+        # error was encountered during fixture loading.
+        if 0 in objects_per_fixture:
+            sys.stderr.write(
+                self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" %
+                    (fixture_name)))
+            transaction.rollback()
+            transaction.leave_transaction_management()
+            return
+            
+        # If we found even one object in a fixture, we need to reset the 
+        # database sequences.
+        if object_count > 0:
+            sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
+            if sequence_sql:
+                if verbosity > 1:
+                    print "Resetting sequences"
+                for line in sequence_sql:
+                    cursor.execute(line)
+            
+        transaction.commit()
+        transaction.leave_transaction_management()
+
+        if object_count == 0:
+            if verbosity > 1:
+                print "No fixtures found."
+        else:
+            if verbosity > 0:
+                print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count)
+                
+        # Close the DB connection. This is required as a workaround for an
+        # edge case in MySQL: if the same connection is used to
+        # create tables, load data, and query, the query can return
+        # incorrect results. See Django #7572, MySQL #37735.
+        connection.close()
+
+# Backwards compatibility for Django r9110
+if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
+    Command.option_list += (
+        make_option('--verbosity', '-v', action="store", dest="verbosity",
+            default='1', type='choice', choices=['0', '1', '2'],
+            help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/jobs.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,154 @@
+"""
+django_extensions.management.jobs
+"""
+
+import os
+from imp import find_module
+
+_jobs = None
+
+def noneimplementation(meth):
+    return None
+
+class JobError(Exception):
+    pass
+
+class BaseJob(object):
+    help = "undefined job description."
+    when = None
+
+    def execute(self):
+        raise NotImplementedError("Job needs to implement the execute method")
+
+class HourlyJob(BaseJob):
+    when = "hourly"
+
+class DailyJob(BaseJob):
+    when = "daily"
+
+class WeeklyJob(BaseJob):
+    when = "weekly"
+
+class MonthlyJob(BaseJob):
+    when = "monthly"
+
+def my_import(name):
+    imp = __import__(name)
+    mods = name.split('.')
+    if len(mods)>1:
+        for mod in mods[1:]:
+            imp = getattr(imp, mod)
+    return imp
+
+def find_jobs(jobs_dir):
+    try:
+        return [f[:-3] for f in os.listdir(jobs_dir) \
+                if not f.startswith('_') and f.endswith(".py")]
+    except OSError:
+        return []
+
+def find_job_module(app_name, when=None):
+    parts = app_name.split('.')
+    parts.append('jobs')
+    if when:
+        parts.append(when)
+    parts.reverse()
+    path = None
+    while parts:
+        part = parts.pop()
+        f, path, descr = find_module(part, path and [path] or None)
+    return path
+
+def import_job(app_name, name, when=None):
+    jobmodule = "%s.jobs.%s%s" % (app_name, when and "%s." % when or "", name)
+    job_mod = my_import(jobmodule)
+    # todo: more friendly message for AttributeError if job_mod does not exist
+    try:
+        job = job_mod.Job
+    except:
+        raise JobError("Job module %s does not contain class instance named 'Job'" % jobmodule)
+    if when and not (job.when == when or job.when == None):
+        raise JobError("Job %s is not a %s job." % (jobmodule, when))
+    return job
+
+def get_jobs(when=None, only_scheduled=False):
+    """
+    Returns a dictionary mapping of job names together with there respective
+    application class.
+    """
+    global _jobs
+    # FIXME: HACK: make sure the project dir is on the path when executed as ./manage.py
+    import sys
+    try:
+        cpath = os.path.dirname(os.path.realpath(sys.argv[0]))
+        ppath = os.path.dirname(cpath)
+        if ppath not in sys.path:
+            sys.path.append(ppath)
+    except:
+        pass
+    if _jobs is None:
+        _jobs = {}
+        if True:
+            from django.conf import settings
+            for app_name in settings.INSTALLED_APPS:
+                scandirs = (None, 'hourly', 'daily', 'weekly', 'monthly')
+                if when:
+                    scandirs = None, when
+                for subdir in scandirs:
+                    try:
+                        path = find_job_module(app_name, subdir)
+                        for name in find_jobs(path):
+                            if (app_name, name) in _jobs:
+                                raise JobError("Duplicate job %s" % name)
+                            job = import_job(app_name, name, subdir)
+                            if only_scheduled and job.when == None:
+                                # only include jobs which are scheduled
+                                continue
+                            if when and job.when != when:
+                                # generic job not in same schedule
+                                continue
+                            _jobs[(app_name, name)] = job
+                    except ImportError:
+                        pass # No job module -- continue scanning
+    return _jobs
+
+def get_job(app_name, job_name):
+    jobs = get_jobs()
+    if app_name:
+        return jobs[(app_name, job_name)]
+    else:
+        for a, j in jobs.keys():
+            if j==job_name:
+                return jobs[(a, j)]
+        raise KeyError("Job not found: %s" % job_name)
+
+def print_jobs(when=None, only_scheduled=False, show_when=True, \
+                show_appname=False, show_header=True):
+    jobmap = get_jobs(when, only_scheduled=only_scheduled)
+    print "Job List: %i jobs" % len(jobmap)
+    jlist = jobmap.keys()
+    jlist.sort()
+    appname_spacer = "%%-%is" % max(len(e[0]) for e in jlist)
+    name_spacer = "%%-%is" % max(len(e[1]) for e in jlist)
+    when_spacer = "%%-%is" % max(len(e.when) for e in jobmap.values() if e.when)
+    if show_header:
+        line = " "
+        if show_appname:
+            line += appname_spacer % "appname" + " - "
+        line += name_spacer % "jobname"
+        if show_when:
+            line += " - " + when_spacer % "when"
+        line += " - help"
+        print line
+        print "-"*80
+
+    for app_name, job_name in jlist:
+        job = jobmap[(app_name, job_name)]
+        line = " "
+        if show_appname:
+            line += appname_spacer % app_name + " - "
+        line += name_spacer % job_name
+        if show_when:
+            line += " - " + when_spacer % (job.when and job.when or "")
+        line += " - " + job.help
+        print line
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/modelviz.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+"""Django model to DOT (Graphviz) converter
+by Antonio Cavedoni <antonio@cavedoni.org>
+
+Make sure your DJANGO_SETTINGS_MODULE is set to your project or
+place this script in the same directory of the project and call
+the script like this:
+
+$ python modelviz.py [-h] [-a] [-d] [-g] [-i <model_names>] <app_label> ... <app_label> > <filename>.dot
+$ dot <filename>.dot -Tpng -o <filename>.png
+
+options:
+    -h, --help
+    show this help message and exit.
+
+    -a, --all_applications
+    show models from all applications.
+
+    -d, --disable_fields
+    don't show the class member fields.
+
+    -g, --group_models
+    draw an enclosing box around models from the same app.
+
+    -i, --include_models=User,Person,Car
+    only include selected models in graph.
+"""
+__version__ = "0.9"
+__svnid__ = "$Id$"
+__license__ = "Python"
+__author__ = "Antonio Cavedoni <http://cavedoni.com/>"
+__contributors__ = [
+   "Stefano J. Attardi <http://attardi.org/>",
+   "limodou <http://www.donews.net/limodou/>",
+   "Carlo C8E Miron",
+   "Andre Campos <cahenan@gmail.com>",
+   "Justin Findlay <jfindlay@gmail.com>",
+   "Alexander Houben <alexander@houben.ch>",
+   "Bas van Oostveen <v.oostveen@gmail.com>",
+]
+
+import getopt, sys
+
+from django.core.management import setup_environ
+
+try:
+    import settings
+except ImportError:
+    pass
+else:
+    setup_environ(settings)
+
+from django.utils.safestring import mark_safe
+from django.template import Template, Context
+from django.db import models
+from django.db.models import get_models
+from django.db.models.fields.related import \
+    ForeignKey, OneToOneField, ManyToManyField
+
+try:
+    from django.db.models.fields.generic import GenericRelation
+except ImportError:
+    from django.contrib.contenttypes.generic import GenericRelation
+
+head_template = """
+digraph name {
+  fontname = "Helvetica"
+  fontsize = 8
+
+  node [
+    fontname = "Helvetica"
+    fontsize = 8
+    shape = "plaintext"
+  ]
+  edge [
+    fontname = "Helvetica"
+    fontsize = 8
+  ]
+
+"""
+
+body_template = """
+{% if use_subgraph %}
+subgraph {{ cluster_app_name }} {
+  label=<
+        <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
+        <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER"
+        ><FONT FACE="Helvetica Bold" COLOR="Black" POINT-SIZE="12"
+        >{{ app_name }}</FONT></TD></TR>
+        </TABLE>
+        >
+  color=olivedrab4
+  style="rounded"
+{% endif %}
+
+  {% for model in models %}
+    {{ model.app_name }}_{{ model.name }} [label=<
+    <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
+     <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
+     ><FONT FACE="Helvetica Bold" COLOR="white"
+     >{{ model.name }}{% if model.abstracts %}<BR/>&lt;<FONT FACE="Helvetica Italic">{{ model.abstracts|join:"," }}</FONT>&gt;{% endif %}</FONT></TD></TR>
+
+    {% if not disable_fields %}
+        {% for field in model.fields %}
+        <TR><TD ALIGN="LEFT" BORDER="0"
+        ><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica {% if field.abstract %}Italic{% else %}Bold{% endif %}">{{ field.name }}</FONT
+        ></TD>
+        <TD ALIGN="LEFT"
+        ><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica {% if field.abstract %}Italic{% else %}Bold{% endif %}">{{ field.type }}</FONT
+        ></TD></TR>
+        {% endfor %}
+    {% endif %}
+    </TABLE>
+    >]
+  {% endfor %}
+
+{% if use_subgraph %}
+}
+{% endif %}
+"""
+
+rel_template = """
+  {% for model in models %}
+    {% for relation in model.relations %}
+    {% if relation.needs_node %}
+    {{ relation.target_app }}_{{ relation.target }} [label=<
+        <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
+        <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
+        ><FONT FACE="Helvetica Bold" COLOR="white"
+        >{{ relation.target }}</FONT></TD></TR>
+        </TABLE>
+        >]
+    {% endif %}
+    {{ model.app_name }}_{{ model.name }} -> {{ relation.target_app }}_{{ relation.target }}
+    [label="{{ relation.name }}"] {{ relation.arrows }};
+    {% endfor %}
+  {% endfor %}
+"""
+
+tail_template = """
+}
+"""
+
+def generate_dot(app_labels, **kwargs):
+    disable_fields = kwargs.get('disable_fields', False)
+    include_models = kwargs.get('include_models', [])
+    all_applications = kwargs.get('all_applications', False)
+    use_subgraph = kwargs.get('group_models', False)
+
+    dot = head_template
+
+    apps = []
+    if all_applications:
+        apps = models.get_apps()
+
+    for app_label in app_labels:
+        app = models.get_app(app_label)
+        if not app in apps:
+            apps.append(app)
+
+    graphs = []
+    for app in apps:
+        graph = Context({
+            'name': '"%s"' % app.__name__,
+            'app_name': "%s" % '.'.join(app.__name__.split('.')[:-1]),
+            'cluster_app_name': "cluster_%s" % app.__name__.replace(".", "_"),
+            'disable_fields': disable_fields,
+            'use_subgraph': use_subgraph,
+            'models': []
+        })
+
+        for appmodel in get_models(app):
+            abstracts = [e.__name__ for e in appmodel.__bases__ if hasattr(e, '_meta') and e._meta.abstract]
+            abstract_fields = []
+            for e in appmodel.__bases__:
+                if hasattr(e, '_meta') and e._meta.abstract:
+                    abstract_fields.extend(e._meta.fields)
+            model = {
+                'app_name': app.__name__.replace(".", "_"),
+                'name': appmodel.__name__,
+                'abstracts': abstracts,
+                'fields': [],
+                'relations': []
+            }
+
+            # consider given model name ?
+            def consider(model_name):
+                return not include_models or model_name in include_models
+
+            if not consider(appmodel._meta.object_name):
+                continue
+
+            # model attributes
+            def add_attributes(field):
+                model['fields'].append({
+                    'name': field.name,
+                    'type': type(field).__name__,
+                    'blank': field.blank,
+                    'abstract': field in abstract_fields,
+                })
+
+            for field in appmodel._meta.fields:
+                add_attributes(field)
+
+            if appmodel._meta.many_to_many:
+                for field in appmodel._meta.many_to_many:
+                    add_attributes(field)
+
+            # relations
+            def add_relation(field, extras=""):
+                _rel = {
+                    'target_app': field.rel.to.__module__.replace('.','_'),
+                    'target': field.rel.to.__name__,
+                    'type': type(field).__name__,
+                    'name': field.name,
+                    'arrows': extras,
+                    'needs_node': True
+                }
+                if _rel not in model['relations'] and consider(_rel['target']):
+                    model['relations'].append(_rel)
+
+            for field in appmodel._meta.fields:
+                if isinstance(field, ForeignKey):
+                    add_relation(field)
+                elif isinstance(field, OneToOneField):
+                    add_relation(field, '[arrowhead=none arrowtail=none]')
+
+            if appmodel._meta.many_to_many:
+                for field in appmodel._meta.many_to_many:
+                    if isinstance(field, ManyToManyField) and getattr(field, 'creates_table', False):
+                        add_relation(field, '[arrowhead=normal arrowtail=normal]')
+                    elif isinstance(field, GenericRelation):
+                        add_relation(field, mark_safe('[style="dotted"] [arrowhead=normal arrowtail=normal]'))
+            graph['models'].append(model)
+        graphs.append(graph)
+
+    nodes = []
+    for graph in graphs:
+        nodes.extend([e['name'] for e in graph['models']])
+
+    for graph in graphs:
+        # don't draw duplication nodes because of relations
+        for model in graph['models']:
+            for relation in model['relations']:
+                if relation['target'] in nodes:
+                    relation['needs_node'] = False
+        # render templates
+        t = Template(body_template)
+        dot += '\n' + t.render(graph)
+
+    for graph in graphs:
+        t = Template(rel_template)
+        dot += '\n' + t.render(graph)
+
+    dot += '\n' + tail_template
+    return dot
+
+def main():
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hadgi:",
+                    ["help", "all_applications", "disable_fields", "group_models", "include_models="])
+    except getopt.GetoptError, error:
+        print __doc__
+        sys.exit(error)
+    
+    kwargs = {}
+    for opt, arg in opts:
+        if opt in ("-h", "--help"):
+            print __doc__
+            sys.exit()
+        if opt in ("-a", "--all_applications"):
+            kwargs['all_applications'] = True
+        if opt in ("-d", "--disable_fields"):
+            kwargs['disable_fields'] = True
+        if opt in ("-g", "--group_models"):
+            kwargs['group_models'] = True
+        if opt in ("-i", "--include_models"):
+            kwargs['include_models'] = arg.split(',')
+
+    if not args and not kwargs.get('all_applications', False):
+        print __doc__
+        sys.exit()
+
+    print generate_dot(args, **kwargs)
+
+if __name__ == "__main__":
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/signals.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,9 @@
+"""
+signals we use to trigger regular batch jobs
+"""
+from django.dispatch import Signal
+
+run_hourly_jobs = Signal()
+run_daily_jobs = Signal()
+run_weekly_jobs = Signal()
+run_monthly_jobs = Signal()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/management/utils.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,7 @@
+from django.conf import settings
+import os
+
+def get_project_root():
+    """ get the project root directory """
+    settings_mod = __import__(settings.SETTINGS_MODULE, {}, {}, [''])
+    return os.path.dirname(os.path.abspath(settings_mod.__file__))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/media/django_extensions/css/jquery.autocomplete.css	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,43 @@
+/*****************************************************************************
+ * jQuery autocomplete
+ ****************************************************************************/
+.ac_results {
+    padding: 0px;
+    border: 1px solid #ccc;
+    background-color: #fff;
+    overflow: hidden;
+    z-index: 99999;
+    text-align: left;
+}
+
+.ac_results ul {
+    width: 100%;
+    list-style-position: outside;
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.ac_results li {
+    margin: 0px;
+    padding: 3px 5px;
+    cursor: default;
+    display: block;
+    font: menu;
+    font-size: 12px;
+    line-height: 14px;
+    overflow: hidden;
+}
+
+.ac_loading {
+    background: white url('../img/indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+    background-color: #eee;
+}
+
+.ac_over {
+    background-color: #999;
+    color: white;
+}
Binary file web/lib/django_extensions/media/django_extensions/img/indicator.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/media/django_extensions/js/jquery.ajaxQueue.js	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,116 @@
+/**
+ * Ajax Queue Plugin
+ * 
+ * Homepage: http://jquery.com/plugins/project/ajaxqueue
+ * Documentation: http://docs.jquery.com/AjaxQueue
+ */
+
+/**
+
+<script>
+$(function(){
+	jQuery.ajaxQueue({
+		url: "test.php",
+		success: function(html){ jQuery("ul").append(html); }
+	});
+	jQuery.ajaxQueue({
+		url: "test.php",
+		success: function(html){ jQuery("ul").append(html); }
+	});
+	jQuery.ajaxSync({
+		url: "test.php",
+		success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
+	});
+	jQuery.ajaxSync({
+		url: "test.php",
+		success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
+	});
+});
+</script>
+<ul style="position: absolute; top: 5px; right: 5px;"></ul>
+
+ */
+/*
+ * Queued Ajax requests.
+ * A new Ajax request won't be started until the previous queued 
+ * request has finished.
+ */
+
+/*
+ * Synced Ajax requests.
+ * The Ajax request will happen as soon as you call this method, but
+ * the callbacks (success/error/complete) won't fire until all previous
+ * synced requests have been completed.
+ */
+
+
+(function($) {
+	
+	var ajax = $.ajax;
+	
+	var pendingRequests = {};
+	
+	var synced = [];
+	var syncedData = [];
+	
+	$.ajax = function(settings) {
+		// create settings for compatibility with ajaxSetup
+		settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
+		
+		var port = settings.port;
+		
+		switch(settings.mode) {
+		case "abort": 
+			if ( pendingRequests[port] ) {
+				pendingRequests[port].abort();
+			}
+			return pendingRequests[port] = ajax.apply(this, arguments);
+		case "queue": 
+			var _old = settings.complete;
+			settings.complete = function(){
+				if ( _old )
+					_old.apply( this, arguments );
+				jQuery([ajax]).dequeue("ajax" + port );;
+			};
+		
+			jQuery([ ajax ]).queue("ajax" + port, function(){
+				ajax( settings );
+			});
+			return;
+		case "sync":
+			var pos = synced.length;
+	
+			synced[ pos ] = {
+				error: settings.error,
+				success: settings.success,
+				complete: settings.complete,
+				done: false
+			};
+		
+			syncedData[ pos ] = {
+				error: [],
+				success: [],
+				complete: []
+			};
+		
+			settings.error = function(){ syncedData[ pos ].error = arguments; };
+			settings.success = function(){ syncedData[ pos ].success = arguments; };
+			settings.complete = function(){
+				syncedData[ pos ].complete = arguments;
+				synced[ pos ].done = true;
+		
+				if ( pos == 0 || !synced[ pos-1 ] )
+					for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
+						if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
+						if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
+						if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
+		
+						synced[i] = null;
+						syncedData[i] = null;
+					}
+			};
+		}
+		return ajax.apply(this, arguments);
+	};
+	
+})(jQuery);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/media/django_extensions/js/jquery.autocomplete.js	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,759 @@
+/*
+ * Autocomplete - jQuery plugin 1.0.2
+ *
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
+ *
+ */
+
+;(function($) {
+	
+$.fn.extend({
+	autocomplete: function(urlOrData, options) {
+		var isUrl = typeof urlOrData == "string";
+		options = $.extend({}, $.Autocompleter.defaults, {
+			url: isUrl ? urlOrData : null,
+			data: isUrl ? null : urlOrData,
+			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
+			max: options && !options.scroll ? 10 : 150
+		}, options);
+		
+		// if highlight is set to false, replace it with a do-nothing function
+		options.highlight = options.highlight || function(value) { return value; };
+		
+		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
+		options.formatMatch = options.formatMatch || options.formatItem;
+		
+		return this.each(function() {
+			new $.Autocompleter(this, options);
+		});
+	},
+	result: function(handler) {
+		return this.bind("result", handler);
+	},
+	search: function(handler) {
+		return this.trigger("search", [handler]);
+	},
+	flushCache: function() {
+		return this.trigger("flushCache");
+	},
+	setOptions: function(options){
+		return this.trigger("setOptions", [options]);
+	},
+	unautocomplete: function() {
+		return this.trigger("unautocomplete");
+	}
+});
+
+$.Autocompleter = function(input, options) {
+
+	var KEY = {
+		UP: 38,
+		DOWN: 40,
+		DEL: 46,
+		TAB: 9,
+		RETURN: 13,
+		ESC: 27,
+		COMMA: 188,
+		PAGEUP: 33,
+		PAGEDOWN: 34,
+		BACKSPACE: 8
+	};
+
+	// Create $ object for input element
+	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+
+	var timeout;
+	var previousValue = "";
+	var cache = $.Autocompleter.Cache(options);
+	var hasFocus = 0;
+	var lastKeyPressCode;
+	var config = {
+		mouseDownOnSelect: false
+	};
+	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
+	
+	var blockSubmit;
+	
+	// prevent form submit in opera when selecting with return key
+	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
+		if (blockSubmit) {
+			blockSubmit = false;
+			return false;
+		}
+	});
+	
+	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
+	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
+		// track last key pressed
+		lastKeyPressCode = event.keyCode;
+		switch(event.keyCode) {
+		
+			case KEY.UP:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.prev();
+				} else {
+					onChange(0, true);
+				}
+				break;
+				
+			case KEY.DOWN:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.next();
+				} else {
+					onChange(0, true);
+				}
+				break;
+				
+			case KEY.PAGEUP:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.pageUp();
+				} else {
+					onChange(0, true);
+				}
+				break;
+				
+			case KEY.PAGEDOWN:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.pageDown();
+				} else {
+					onChange(0, true);
+				}
+				break;
+			
+			// matches also semicolon
+			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
+			case KEY.TAB:
+			case KEY.RETURN:
+				if( selectCurrent() ) {
+					// stop default to prevent a form submit, Opera needs special handling
+					event.preventDefault();
+					blockSubmit = true;
+					return false;
+				}
+				break;
+				
+			case KEY.ESC:
+				select.hide();
+				break;
+				
+			default:
+				clearTimeout(timeout);
+				timeout = setTimeout(onChange, options.delay);
+				break;
+		}
+	}).focus(function(){
+		// track whether the field has focus, we shouldn't process any
+		// results if the field no longer has focus
+		hasFocus++;
+	}).blur(function() {
+		hasFocus = 0;
+		if (!config.mouseDownOnSelect) {
+			hideResults();
+		}
+	}).click(function() {
+		// show select when clicking in a focused field
+		if ( hasFocus++ > 1 && !select.visible() ) {
+			onChange(0, true);
+		}
+	}).bind("search", function() {
+		// TODO why not just specifying both arguments?
+		var fn = (arguments.length > 1) ? arguments[1] : null;
+		function findValueCallback(q, data) {
+			var result;
+			if( data && data.length ) {
+				for (var i=0; i < data.length; i++) {
+					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
+						result = data[i];
+						break;
+					}
+				}
+			}
+			if( typeof fn == "function" ) fn(result);
+			else $input.trigger("result", result && [result.data, result.value]);
+		}
+		$.each(trimWords($input.val()), function(i, value) {
+			request(value, findValueCallback, findValueCallback);
+		});
+	}).bind("flushCache", function() {
+		cache.flush();
+	}).bind("setOptions", function() {
+		$.extend(options, arguments[1]);
+		// if we've updated the data, repopulate
+		if ( "data" in arguments[1] )
+			cache.populate();
+	}).bind("unautocomplete", function() {
+		select.unbind();
+		$input.unbind();
+		$(input.form).unbind(".autocomplete");
+	});
+	
+	
+	function selectCurrent() {
+		var selected = select.selected();
+		if( !selected )
+			return false;
+		
+		var v = selected.result;
+		previousValue = v;
+		
+		if ( options.multiple ) {
+			var words = trimWords($input.val());
+			if ( words.length > 1 ) {
+				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
+			}
+			v += options.multipleSeparator;
+		}
+		
+		$input.val(v);
+		hideResultsNow();
+		$input.trigger("result", [selected.data, selected.value]);
+		return true;
+	}
+	
+	function onChange(crap, skipPrevCheck) {
+		if( lastKeyPressCode == KEY.DEL ) {
+			select.hide();
+			return;
+		}
+		
+		var currentValue = $input.val();
+		
+		if ( !skipPrevCheck && currentValue == previousValue )
+			return;
+		
+		previousValue = currentValue;
+		
+		currentValue = lastWord(currentValue);
+		if ( currentValue.length >= options.minChars) {
+			$input.addClass(options.loadingClass);
+			if (!options.matchCase)
+				currentValue = currentValue.toLowerCase();
+			request(currentValue, receiveData, hideResultsNow);
+		} else {
+			stopLoading();
+			select.hide();
+		}
+	};
+	
+	function trimWords(value) {
+		if ( !value ) {
+			return [""];
+		}
+		var words = value.split( options.multipleSeparator );
+		var result = [];
+		$.each(words, function(i, value) {
+			if ( $.trim(value) )
+				result[i] = $.trim(value);
+		});
+		return result;
+	}
+	
+	function lastWord(value) {
+		if ( !options.multiple )
+			return value;
+		var words = trimWords(value);
+		return words[words.length - 1];
+	}
+	
+	// fills in the input box w/the first match (assumed to be the best match)
+	// q: the term entered
+	// sValue: the first matching result
+	function autoFill(q, sValue){
+		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
+		// if the last user key pressed was backspace, don't autofill
+		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
+			// fill in the value (keep the case the user has typed)
+			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
+			// select the portion of the value not typed by the user (so the next character will erase)
+			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
+		}
+	};
+
+	function hideResults() {
+		clearTimeout(timeout);
+		timeout = setTimeout(hideResultsNow, 200);
+	};
+
+	function hideResultsNow() {
+		var wasVisible = select.visible();
+		select.hide();
+		clearTimeout(timeout);
+		stopLoading();
+		if (options.mustMatch) {
+			// call search and run callback
+			$input.search(
+				function (result){
+					// if no value found, clear the input box
+					if( !result ) {
+						if (options.multiple) {
+							var words = trimWords($input.val()).slice(0, -1);
+							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
+						}
+						else
+							$input.val( "" );
+					}
+				}
+			);
+		}
+		if (wasVisible)
+			// position cursor at end of input field
+			$.Autocompleter.Selection(input, input.value.length, input.value.length);
+	};
+
+	function receiveData(q, data) {
+		if ( data && data.length && hasFocus ) {
+			stopLoading();
+			select.display(data, q);
+			autoFill(q, data[0].value);
+			select.show();
+		} else {
+			hideResultsNow();
+		}
+	};
+
+	function request(term, success, failure) {
+		if (!options.matchCase)
+			term = term.toLowerCase();
+		var data = cache.load(term);
+		// recieve the cached data
+		if (data && data.length) {
+			success(term, data);
+		// if an AJAX url has been supplied, try loading the data now
+		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
+			
+			var extraParams = {
+				timestamp: +new Date()
+			};
+			$.each(options.extraParams, function(key, param) {
+				extraParams[key] = typeof param == "function" ? param() : param;
+			});
+			
+			$.ajax({
+				// try to leverage ajaxQueue plugin to abort previous requests
+				mode: "abort",
+				// limit abortion to this input
+				port: "autocomplete" + input.name,
+				dataType: options.dataType,
+				url: options.url,
+				data: $.extend({
+					q: lastWord(term),
+					limit: options.max
+				}, extraParams),
+				success: function(data) {
+					var parsed = options.parse && options.parse(data) || parse(data);
+					cache.add(term, parsed);
+					success(term, parsed);
+				}
+			});
+		} else {
+			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
+			select.emptyList();
+			failure(term);
+		}
+	};
+	
+	function parse(data) {
+		var parsed = [];
+		var rows = data.split("\n");
+		for (var i=0; i < rows.length; i++) {
+			var row = $.trim(rows[i]);
+			if (row) {
+				row = row.split("|");
+				parsed[parsed.length] = {
+					data: row,
+					value: row[0],
+					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
+				};
+			}
+		}
+		return parsed;
+	};
+
+	function stopLoading() {
+		$input.removeClass(options.loadingClass);
+	};
+
+};
+
+$.Autocompleter.defaults = {
+	inputClass: "ac_input",
+	resultsClass: "ac_results",
+	loadingClass: "ac_loading",
+	minChars: 1,
+	delay: 400,
+	matchCase: false,
+	matchSubset: true,
+	matchContains: false,
+	cacheLength: 10,
+	max: 100,
+	mustMatch: false,
+	extraParams: {},
+	selectFirst: true,
+	formatItem: function(row) { return row[0]; },
+	formatMatch: null,
+	autoFill: false,
+	width: 0,
+	multiple: false,
+	multipleSeparator: ", ",
+	highlight: function(value, term) {
+		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
+	},
+    scroll: true,
+    scrollHeight: 180
+};
+
+$.Autocompleter.Cache = function(options) {
+
+	var data = {};
+	var length = 0;
+	
+	function matchSubset(s, sub) {
+		if (!options.matchCase) 
+			s = s.toLowerCase();
+		var i = s.indexOf(sub);
+		if (i == -1) return false;
+		return i == 0 || options.matchContains;
+	};
+	
+	function add(q, value) {
+		if (length > options.cacheLength){
+			flush();
+		}
+		if (!data[q]){ 
+			length++;
+		}
+		data[q] = value;
+	}
+	
+	function populate(){
+		if( !options.data ) return false;
+		// track the matches
+		var stMatchSets = {},
+			nullData = 0;
+
+		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
+		if( !options.url ) options.cacheLength = 1;
+		
+		// track all options for minChars = 0
+		stMatchSets[""] = [];
+		
+		// loop through the array and create a lookup structure
+		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
+			var rawValue = options.data[i];
+			// if rawValue is a string, make an array otherwise just reference the array
+			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
+			
+			var value = options.formatMatch(rawValue, i+1, options.data.length);
+			if ( value === false )
+				continue;
+				
+			var firstChar = value.charAt(0).toLowerCase();
+			// if no lookup array for this character exists, look it up now
+			if( !stMatchSets[firstChar] ) 
+				stMatchSets[firstChar] = [];
+
+			// if the match is a string
+			var row = {
+				value: value,
+				data: rawValue,
+				result: options.formatResult && options.formatResult(rawValue) || value
+			};
+			
+			// push the current match into the set list
+			stMatchSets[firstChar].push(row);
+
+			// keep track of minChars zero items
+			if ( nullData++ < options.max ) {
+				stMatchSets[""].push(row);
+			}
+		};
+
+		// add the data items to the cache
+		$.each(stMatchSets, function(i, value) {
+			// increase the cache size
+			options.cacheLength++;
+			// add to the cache
+			add(i, value);
+		});
+	}
+	
+	// populate any existing data
+	setTimeout(populate, 25);
+	
+	function flush(){
+		data = {};
+		length = 0;
+	}
+	
+	return {
+		flush: flush,
+		add: add,
+		populate: populate,
+		load: function(q) {
+			if (!options.cacheLength || !length)
+				return null;
+			/* 
+			 * if dealing w/local data and matchContains than we must make sure
+			 * to loop through all the data collections looking for matches
+			 */
+			if( !options.url && options.matchContains ){
+				// track all matches
+				var csub = [];
+				// loop through all the data grids for matches
+				for( var k in data ){
+					// don't search through the stMatchSets[""] (minChars: 0) cache
+					// this prevents duplicates
+					if( k.length > 0 ){
+						var c = data[k];
+						$.each(c, function(i, x) {
+							// if we've got a match, add it to the array
+							if (matchSubset(x.value, q)) {
+								csub.push(x);
+							}
+						});
+					}
+				}				
+				return csub;
+			} else 
+			// if the exact item exists, use it
+			if (data[q]){
+				return data[q];
+			} else
+			if (options.matchSubset) {
+				for (var i = q.length - 1; i >= options.minChars; i--) {
+					var c = data[q.substr(0, i)];
+					if (c) {
+						var csub = [];
+						$.each(c, function(i, x) {
+							if (matchSubset(x.value, q)) {
+								csub[csub.length] = x;
+							}
+						});
+						return csub;
+					}
+				}
+			}
+			return null;
+		}
+	};
+};
+
+$.Autocompleter.Select = function (options, input, select, config) {
+	var CLASSES = {
+		ACTIVE: "ac_over"
+	};
+	
+	var listItems,
+		active = -1,
+		data,
+		term = "",
+		needsInit = true,
+		element,
+		list;
+	
+	// Create results
+	function init() {
+		if (!needsInit)
+			return;
+		element = $("<div/>")
+		.hide()
+		.addClass(options.resultsClass)
+		.css("position", "absolute")
+		.appendTo(document.body);
+	
+		list = $("<ul/>").appendTo(element).mouseover( function(event) {
+			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
+	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
+			    $(target(event)).addClass(CLASSES.ACTIVE);            
+	        }
+		}).click(function(event) {
+			$(target(event)).addClass(CLASSES.ACTIVE);
+			select();
+			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
+			input.focus();
+			return false;
+		}).mousedown(function() {
+			config.mouseDownOnSelect = true;
+		}).mouseup(function() {
+			config.mouseDownOnSelect = false;
+		});
+		
+		if( options.width > 0 )
+			element.css("width", options.width);
+			
+		needsInit = false;
+	} 
+	
+	function target(event) {
+		var element = event.target;
+		while(element && element.tagName != "LI")
+			element = element.parentNode;
+		// more fun with IE, sometimes event.target is empty, just ignore it then
+		if(!element)
+			return [];
+		return element;
+	}
+
+	function moveSelect(step) {
+		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
+		movePosition(step);
+        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
+        if(options.scroll) {
+            var offset = 0;
+            listItems.slice(0, active).each(function() {
+				offset += this.offsetHeight;
+			});
+            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
+                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
+            } else if(offset < list.scrollTop()) {
+                list.scrollTop(offset);
+            }
+        }
+	};
+	
+	function movePosition(step) {
+		active += step;
+		if (active < 0) {
+			active = listItems.size() - 1;
+		} else if (active >= listItems.size()) {
+			active = 0;
+		}
+	}
+	
+	function limitNumberOfItems(available) {
+		return options.max && options.max < available
+			? options.max
+			: available;
+	}
+	
+	function fillList() {
+		list.empty();
+		var max = limitNumberOfItems(data.length);
+		for (var i=0; i < max; i++) {
+			if (!data[i])
+				continue;
+			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
+			if ( formatted === false )
+				continue;
+			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
+			$.data(li, "ac_data", data[i]);
+		}
+		listItems = list.find("li");
+		if ( options.selectFirst ) {
+			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
+			active = 0;
+		}
+		// apply bgiframe if available
+		if ( $.fn.bgiframe )
+			list.bgiframe();
+	}
+	
+	return {
+		display: function(d, q) {
+			init();
+			data = d;
+			term = q;
+			fillList();
+		},
+		next: function() {
+			moveSelect(1);
+		},
+		prev: function() {
+			moveSelect(-1);
+		},
+		pageUp: function() {
+			if (active != 0 && active - 8 < 0) {
+				moveSelect( -active );
+			} else {
+				moveSelect(-8);
+			}
+		},
+		pageDown: function() {
+			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
+				moveSelect( listItems.size() - 1 - active );
+			} else {
+				moveSelect(8);
+			}
+		},
+		hide: function() {
+			element && element.hide();
+			listItems && listItems.removeClass(CLASSES.ACTIVE);
+			active = -1;
+		},
+		visible : function() {
+			return element && element.is(":visible");
+		},
+		current: function() {
+			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
+		},
+		show: function() {
+			var offset = $(input).offset();
+			element.css({
+				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
+				top: offset.top + input.offsetHeight,
+				left: offset.left
+			}).show();
+            if(options.scroll) {
+                list.scrollTop(0);
+                list.css({
+					maxHeight: options.scrollHeight,
+					overflow: 'auto'
+				});
+				
+                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+					var listHeight = 0;
+					listItems.each(function() {
+						listHeight += this.offsetHeight;
+					});
+					var scrollbarsVisible = listHeight > options.scrollHeight;
+                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
+					if (!scrollbarsVisible) {
+						// IE doesn't recalculate width when scrollbar disappears
+						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
+					}
+                }
+                
+            }
+		},
+		selected: function() {
+			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
+			return selected && selected.length && $.data(selected[0], "ac_data");
+		},
+		emptyList: function (){
+			list && list.empty();
+		},
+		unbind: function() {
+			element && element.remove();
+		}
+	};
+};
+
+$.Autocompleter.Selection = function(field, start, end) {
+	if( field.createTextRange ){
+		var selRange = field.createTextRange();
+		selRange.collapse(true);
+		selRange.moveStart("character", start);
+		selRange.moveEnd("character", end);
+		selRange.select();
+	} else if( field.setSelectionRange ){
+		field.setSelectionRange(start, end);
+	} else {
+		if( field.selectionStart ){
+			field.selectionStart = start;
+			field.selectionEnd = end;
+		}
+	}
+	field.focus();
+};
+
+})(jQuery);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/media/django_extensions/js/jquery.bgiframe.min.js	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,10 @@
+/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
+ * $Rev: 2447 $
+ *
+ * Version 2.1.1
+ */
+(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/media/django_extensions/js/jquery.js	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,3558 @@
+(function(){
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-27 21:17:26 +0200 (Di, 27 Mai 2008) $
+ * $Rev: 5700 $
+ */
+
+// Map over jQuery in case of overwrite
+var _jQuery = window.jQuery,
+// Map over the $ in case of overwrite
+	_$ = window.$;
+
+var jQuery = window.jQuery = window.$ = function( selector, context ) {
+	// The jQuery object is actually just the init constructor 'enhanced'
+	return new jQuery.fn.init( selector, context );
+};
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,
+
+// Is it a simple selector
+	isSimple = /^.[^:#\[\.]*$/,
+
+// Will speed up references to undefined, and allows munging its name.
+	undefined;
+
+jQuery.fn = jQuery.prototype = {
+	init: function( selector, context ) {
+		// Make sure that a selection was provided
+		selector = selector || document;
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this[0] = selector;
+			this.length = 1;
+			return this;
+		}
+		// Handle HTML strings
+		if ( typeof selector == "string" ) {
+			// Are we dealing with HTML string or an ID?
+			var match = quickExpr.exec( selector );
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] )
+					selector = jQuery.clean( [ match[1] ], context );
+
+				// HANDLE: $("#id")
+				else {
+					var elem = document.getElementById( match[3] );
+
+					// Make sure an element was located
+					if ( elem ){
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id != match[3] )
+							return jQuery().find( selector );
+
+						// Otherwise, we inject the element directly into the jQuery object
+						return jQuery( elem );
+					}
+					selector = [];
+				}
+
+			// HANDLE: $(expr, [context])
+			// (which is just equivalent to: $(content).find(expr)
+			} else
+				return jQuery( context ).find( selector );
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) )
+			return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+		return this.setArray(jQuery.makeArray(selector));
+	},
+
+	// The current version of jQuery being used
+	jquery: "1.2.6",
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	// The number of elements contained in the matched element set
+	length: 0,
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == undefined ?
+
+			// Return a 'clean' array
+			jQuery.makeArray( this ) :
+
+			// Return just the object
+			this[ num ];
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+		// Build a new jQuery matched element set
+		var ret = jQuery( elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Force the current matched set of elements to become
+	// the specified array of elements (destroying the stack in the process)
+	// You should use pushStack() in order to do this, but maintain the stack
+	setArray: function( elems ) {
+		// Resetting the length to 0, then using the native Array push
+		// is a super-fast way to populate an object with array-like properties
+		this.length = 0;
+		Array.prototype.push.apply( this, elems );
+
+		return this;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+		var ret = -1;
+
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem && elem.jquery ? elem[0] : elem
+		, this );
+	},
+
+	attr: function( name, value, type ) {
+		var options = name;
+
+		// Look for the case where we're accessing a style value
+		if ( name.constructor == String )
+			if ( value === undefined )
+				return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+			else {
+				options = {};
+				options[ name ] = value;
+			}
+
+		// Check to see if we're setting style values
+		return this.each(function(i){
+			// Set all the styles
+			for ( name in options )
+				jQuery.attr(
+					type ?
+						this.style :
+						this,
+					name, jQuery.prop( this, options[ name ], type, i, name )
+				);
+		});
+	},
+
+	css: function( key, value ) {
+		// ignore negative width and height values
+		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+			value = undefined;
+		return this.attr( key, value, "curCSS" );
+	},
+
+	text: function( text ) {
+		if ( typeof text != "object" && text != null )
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+		var ret = "";
+
+		jQuery.each( text || this, function(){
+			jQuery.each( this.childNodes, function(){
+				if ( this.nodeType != 8 )
+					ret += this.nodeType != 1 ?
+						this.nodeValue :
+						jQuery.fn.text( [ this ] );
+			});
+		});
+
+		return ret;
+	},
+
+	wrapAll: function( html ) {
+		if ( this[0] )
+			// The elements to wrap the target around
+			jQuery( html, this[0].ownerDocument )
+				.clone()
+				.insertBefore( this[0] )
+				.map(function(){
+					var elem = this;
+
+					while ( elem.firstChild )
+						elem = elem.firstChild;
+
+					return elem;
+				})
+				.append(this);
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		return this.each(function(){
+			jQuery( this ).contents().wrapAll( html );
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function(){
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, false, function(elem){
+			if (this.nodeType == 1)
+				this.appendChild( elem );
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, true, function(elem){
+			if (this.nodeType == 1)
+				this.insertBefore( elem, this.firstChild );
+		});
+	},
+
+	before: function() {
+		return this.domManip(arguments, false, false, function(elem){
+			this.parentNode.insertBefore( elem, this );
+		});
+	},
+
+	after: function() {
+		return this.domManip(arguments, false, true, function(elem){
+			this.parentNode.insertBefore( elem, this.nextSibling );
+		});
+	},
+
+	end: function() {
+		return this.prevObject || jQuery( [] );
+	},
+
+	find: function( selector ) {
+		var elems = jQuery.map(this, function(elem){
+			return jQuery.find( selector, elem );
+		});
+
+		return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+			jQuery.unique( elems ) :
+			elems );
+	},
+
+	clone: function( events ) {
+		// Do the clone
+		var ret = this.map(function(){
+			if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+				// IE copies events bound via attachEvent when
+				// using cloneNode. Calling detachEvent on the
+				// clone will also remove the events from the orignal
+				// In order to get around this, we use innerHTML.
+				// Unfortunately, this means some modifications to
+				// attributes in IE that are actually only stored
+				// as properties will not be copied (such as the
+				// the name attribute on an input).
+				var clone = this.cloneNode(true),
+					container = document.createElement("div");
+				container.appendChild(clone);
+				return jQuery.clean([container.innerHTML])[0];
+			} else
+				return this.cloneNode(true);
+		});
+
+		// Need to set the expando to null on the cloned set if it exists
+		// removeData doesn't work here, IE removes it from the original as well
+		// this is primarily for IE but the data expando shouldn't be copied over in any browser
+		var clone = ret.find("*").andSelf().each(function(){
+			if ( this[ expando ] != undefined )
+				this[ expando ] = null;
+		});
+
+		// Copy the events from the original to the clone
+		if ( events === true )
+			this.find("*").andSelf().each(function(i){
+				if (this.nodeType == 3)
+					return;
+				var events = jQuery.data( this, "events" );
+
+				for ( var type in events )
+					for ( var handler in events[ type ] )
+						jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+			});
+
+		// Return the cloned set
+		return ret;
+	},
+
+	filter: function( selector ) {
+		return this.pushStack(
+			jQuery.isFunction( selector ) &&
+			jQuery.grep(this, function(elem, i){
+				return selector.call( elem, i );
+			}) ||
+
+			jQuery.multiFilter( selector, this ) );
+	},
+
+	not: function( selector ) {
+		if ( selector.constructor == String )
+			// test special case where just one selector is passed in
+			if ( isSimple.test( selector ) )
+				return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+			else
+				selector = jQuery.multiFilter( selector, this );
+
+		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+		return this.filter(function() {
+			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+		});
+	},
+
+	add: function( selector ) {
+		return this.pushStack( jQuery.unique( jQuery.merge(
+			this.get(),
+			typeof selector == 'string' ?
+				jQuery( selector ) :
+				jQuery.makeArray( selector )
+		)));
+	},
+
+	is: function( selector ) {
+		return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+	},
+
+	hasClass: function( selector ) {
+		return this.is( "." + selector );
+	},
+
+	val: function( value ) {
+		if ( value == undefined ) {
+
+			if ( this.length ) {
+				var elem = this[0];
+
+				// We need to handle select boxes special
+				if ( jQuery.nodeName( elem, "select" ) ) {
+					var index = elem.selectedIndex,
+						values = [],
+						options = elem.options,
+						one = elem.type == "select-one";
+
+					// Nothing was selected
+					if ( index < 0 )
+						return null;
+
+					// Loop through all the selected options
+					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+						var option = options[ i ];
+
+						if ( option.selected ) {
+							// Get the specifc value for the option
+							value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+							// We don't need an array for one selects
+							if ( one )
+								return value;
+
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+
+					return values;
+
+				// Everything else, we just grab the value
+				} else
+					return (this[0].value || "").replace(/\r/g, "");
+
+			}
+
+			return undefined;
+		}
+
+		if( value.constructor == Number )
+			value += '';
+
+		return this.each(function(){
+			if ( this.nodeType != 1 )
+				return;
+
+			if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+					jQuery.inArray(this.name, value) >= 0);
+
+			else if ( jQuery.nodeName( this, "select" ) ) {
+				var values = jQuery.makeArray(value);
+
+				jQuery( "option", this ).each(function(){
+					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+						jQuery.inArray( this.text, values ) >= 0);
+				});
+
+				if ( !values.length )
+					this.selectedIndex = -1;
+
+			} else
+				this.value = value;
+		});
+	},
+
+	html: function( value ) {
+		return value == undefined ?
+			(this[0] ?
+				this[0].innerHTML :
+				null) :
+			this.empty().append( value );
+	},
+
+	replaceWith: function( value ) {
+		return this.after( value ).remove();
+	},
+
+	eq: function( i ) {
+		return this.slice( i, i + 1 );
+	},
+
+	slice: function() {
+		return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function(elem, i){
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	},
+
+	data: function( key, value ){
+		var parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value === undefined ) {
+			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+			if ( data === undefined && this.length )
+				data = jQuery.data( this[0], key );
+
+			return data === undefined && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+		} else
+			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+				jQuery.data( this, key, value );
+			});
+	},
+
+	removeData: function( key ){
+		return this.each(function(){
+			jQuery.removeData( this, key );
+		});
+	},
+
+	domManip: function( args, table, reverse, callback ) {
+		var clone = this.length > 1, elems;
+
+		return this.each(function(){
+			if ( !elems ) {
+				elems = jQuery.clean( args, this.ownerDocument );
+
+				if ( reverse )
+					elems.reverse();
+			}
+
+			var obj = this;
+
+			if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+				obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+			var scripts = jQuery( [] );
+
+			jQuery.each(elems, function(){
+				var elem = clone ?
+					jQuery( this ).clone( true )[0] :
+					this;
+
+				// execute all scripts after the elements have been injected
+				if ( jQuery.nodeName( elem, "script" ) )
+					scripts = scripts.add( elem );
+				else {
+					// Remove any inner scripts for later evaluation
+					if ( elem.nodeType == 1 )
+						scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+					// Inject the elements into the document
+					callback.call( obj, elem );
+				}
+			});
+
+			scripts.each( evalScript );
+		});
+	}
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+	if ( elem.src )
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+
+	else
+		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+	if ( elem.parentNode )
+		elem.parentNode.removeChild( elem );
+}
+
+function now(){
+	return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+	// copy reference to target object
+	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+	// Handle a deep copy situation
+	if ( target.constructor == Boolean ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target != "object" && typeof target != "function" )
+		target = {};
+
+	// extend jQuery itself if only one argument is passed
+	if ( length == i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ )
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null )
+			// Extend the base object
+			for ( var name in options ) {
+				var src = target[ name ], copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy )
+					continue;
+
+				// Recurse if we're merging object values
+				if ( deep && copy && typeof copy == "object" && !copy.nodeType )
+					target[ name ] = jQuery.extend( deep, 
+						// Never move original objects, clone them
+						src || ( copy.length != null ? [ ] : { } )
+					, copy );
+
+				// Don't bring in undefined values
+				else if ( copy !== undefined )
+					target[ name ] = copy;
+
+			}
+
+	// Return the modified object
+	return target;
+};
+
+var expando = "jQuery" + now(), uuid = 0, windowData = {},
+	// exclude the following css properties to add px
+	exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+	// cache defaultView
+	defaultView = document.defaultView || {};
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		window.$ = _$;
+
+		if ( deep )
+			window.jQuery = _jQuery;
+
+		return jQuery;
+	},
+
+	// See test/unit/core.js for details concerning this function.
+	isFunction: function( fn ) {
+		return !!fn && typeof fn != "string" && !fn.nodeName &&
+			fn.constructor != Array && /^[\s[]?function/.test( fn + "" );
+	},
+
+	// check if an element is in a (or is an) XML document
+	isXMLDoc: function( elem ) {
+		return elem.documentElement && !elem.body ||
+			elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+	},
+
+	// Evalulates a script in a global context
+	globalEval: function( data ) {
+		data = jQuery.trim( data );
+
+		if ( data ) {
+			// Inspired by code by Andrea Giammarchi
+			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+			var head = document.getElementsByTagName("head")[0] || document.documentElement,
+				script = document.createElement("script");
+
+			script.type = "text/javascript";
+			if ( jQuery.browser.msie )
+				script.text = data;
+			else
+				script.appendChild( document.createTextNode( data ) );
+
+			// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+			// This arises when a base node is used (#2709).
+			head.insertBefore( script, head.firstChild );
+			head.removeChild( script );
+		}
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+	},
+
+	cache: {},
+
+	data: function( elem, name, data ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// Compute a unique ID for the element
+		if ( !id )
+			id = elem[ expando ] = ++uuid;
+
+		// Only generate the data cache if we're
+		// trying to access or manipulate it
+		if ( name && !jQuery.cache[ id ] )
+			jQuery.cache[ id ] = {};
+
+		// Prevent overriding the named cache with undefined values
+		if ( data !== undefined )
+			jQuery.cache[ id ][ name ] = data;
+
+		// Return the named cache data, or the ID for the element
+		return name ?
+			jQuery.cache[ id ][ name ] :
+			id;
+	},
+
+	removeData: function( elem, name ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// If we want to remove a specific section of the element's data
+		if ( name ) {
+			if ( jQuery.cache[ id ] ) {
+				// Remove the section of cache data
+				delete jQuery.cache[ id ][ name ];
+
+				// If we've removed all the data, remove the element's cache
+				name = "";
+
+				for ( name in jQuery.cache[ id ] )
+					break;
+
+				if ( !name )
+					jQuery.removeData( elem );
+			}
+
+		// Otherwise, we want to remove all of the element's data
+		} else {
+			// Clean up the element expando
+			try {
+				delete elem[ expando ];
+			} catch(e){
+				// IE has trouble directly removing the expando
+				// but it's ok with using removeAttribute
+				if ( elem.removeAttribute )
+					elem.removeAttribute( expando );
+			}
+
+			// Completely remove the data cache
+			delete jQuery.cache[ id ];
+		}
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		var name, i = 0, length = object.length;
+
+		if ( args ) {
+			if ( length == undefined ) {
+				for ( name in object )
+					if ( callback.apply( object[ name ], args ) === false )
+						break;
+			} else
+				for ( ; i < length; )
+					if ( callback.apply( object[ i++ ], args ) === false )
+						break;
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( length == undefined ) {
+				for ( name in object )
+					if ( callback.call( object[ name ], name, object[ name ] ) === false )
+						break;
+			} else
+				for ( var value = object[0];
+					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+		}
+
+		return object;
+	},
+
+	prop: function( elem, value, type, i, name ) {
+		// Handle executable functions
+		if ( jQuery.isFunction( value ) )
+			value = value.call( elem, i );
+
+		// Handle passing in a number to a CSS property
+		return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+			value + "px" :
+			value;
+	},
+
+	className: {
+		// internal only, use addClass("class")
+		add: function( elem, classNames ) {
+			jQuery.each((classNames || "").split(/\s+/), function(i, className){
+				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+					elem.className += (elem.className ? " " : "") + className;
+			});
+		},
+
+		// internal only, use removeClass("class")
+		remove: function( elem, classNames ) {
+			if (elem.nodeType == 1)
+				elem.className = classNames != undefined ?
+					jQuery.grep(elem.className.split(/\s+/), function(className){
+						return !jQuery.className.has( classNames, className );
+					}).join(" ") :
+					"";
+		},
+
+		// internal only, use hasClass("class")
+		has: function( elem, className ) {
+			return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( var name in options )
+			elem.style[ name ] = old[ name ];
+	},
+
+	css: function( elem, name, force ) {
+		if ( name == "width" || name == "height" ) {
+			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+			function getWH() {
+				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+				var padding = 0, border = 0;
+				jQuery.each( which, function() {
+					padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+					border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+				});
+				val -= Math.round(padding + border);
+			}
+
+			if ( jQuery(elem).is(":visible") )
+				getWH();
+			else
+				jQuery.swap( elem, props, getWH );
+
+			return Math.max(0, val);
+		}
+
+		return jQuery.curCSS( elem, name, force );
+	},
+
+	curCSS: function( elem, name, force ) {
+		var ret, style = elem.style;
+
+		// A helper method for determining if an element's values are broken
+		function color( elem ) {
+			if ( !jQuery.browser.safari )
+				return false;
+
+			// defaultView is cached
+			var ret = defaultView.getComputedStyle( elem, null );
+			return !ret || ret.getPropertyValue("color") == "";
+		}
+
+		// We need to handle opacity special in IE
+		if ( name == "opacity" && jQuery.browser.msie ) {
+			ret = jQuery.attr( style, "opacity" );
+
+			return ret == "" ?
+				"1" :
+				ret;
+		}
+		// Opera sometimes will give the wrong display answer, this fixes it, see #2037
+		if ( jQuery.browser.opera && name == "display" ) {
+			var save = style.outline;
+			style.outline = "0 solid black";
+			style.outline = save;
+		}
+
+		// Make sure we're using the right name for getting the float value
+		if ( name.match( /float/i ) )
+			name = styleFloat;
+
+		if ( !force && style && style[ name ] )
+			ret = style[ name ];
+
+		else if ( defaultView.getComputedStyle ) {
+
+			// Only "float" is needed here
+			if ( name.match( /float/i ) )
+				name = "float";
+
+			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+			var computedStyle = defaultView.getComputedStyle( elem, null );
+
+			if ( computedStyle && !color( elem ) )
+				ret = computedStyle.getPropertyValue( name );
+
+			// If the element isn't reporting its values properly in Safari
+			// then some display: none elements are involved
+			else {
+				var swap = [], stack = [], a = elem, i = 0;
+
+				// Locate all of the parent display: none elements
+				for ( ; a && color(a); a = a.parentNode )
+					stack.unshift(a);
+
+				// Go through and make them visible, but in reverse
+				// (It would be better if we knew the exact display type that they had)
+				for ( ; i < stack.length; i++ )
+					if ( color( stack[ i ] ) ) {
+						swap[ i ] = stack[ i ].style.display;
+						stack[ i ].style.display = "block";
+					}
+
+				// Since we flip the display style, we have to handle that
+				// one special, otherwise get the value
+				ret = name == "display" && swap[ stack.length - 1 ] != null ?
+					"none" :
+					( computedStyle && computedStyle.getPropertyValue( name ) ) || "";
+
+				// Finally, revert the display styles back
+				for ( i = 0; i < swap.length; i++ )
+					if ( swap[ i ] != null )
+						stack[ i ].style.display = swap[ i ];
+			}
+
+			// We should always get a number back from opacity
+			if ( name == "opacity" && ret == "" )
+				ret = "1";
+
+		} else if ( elem.currentStyle ) {
+			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+			// From the awesome hack by Dean Edwards
+			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+			// If we're not dealing with a regular pixel number
+			// but a number that has a weird ending, we need to convert it to pixels
+			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+				// Remember the original values
+				var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+				// Put in the new values to get a computed value out
+				elem.runtimeStyle.left = elem.currentStyle.left;
+				style.left = ret || 0;
+				ret = style.pixelLeft + "px";
+
+				// Revert the changed values
+				style.left = left;
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret;
+	},
+
+	clean: function( elems, context ) {
+		var ret = [];
+		context = context || document;
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if (typeof context.createElement == 'undefined')
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+		jQuery.each(elems, function(i, elem){
+			if ( !elem )
+				return;
+
+			if ( elem.constructor == Number )
+				elem += '';
+
+			// Convert html string into DOM nodes
+			if ( typeof elem == "string" ) {
+				// Fix "XHTML"-style tags in all browsers
+				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+						all :
+						front + "></" + tag + ">";
+				});
+
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+				var wrap =
+					// option or optgroup
+					!tags.indexOf("<opt") &&
+					[ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+					!tags.indexOf("<leg") &&
+					[ 1, "<fieldset>", "</fieldset>" ] ||
+
+					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+					[ 1, "<table>", "</table>" ] ||
+
+					!tags.indexOf("<tr") &&
+					[ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+				 	// <thead> matched above
+					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+					!tags.indexOf("<col") &&
+					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+					// IE can't serialize <link> and <script> tags normally
+					jQuery.browser.msie &&
+					[ 1, "div<div>", "</div>" ] ||
+
+					[ 0, "", "" ];
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + elem + wrap[2];
+
+				// Move to the right depth
+				while ( wrap[0]-- )
+					div = div.lastChild;
+
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( jQuery.browser.msie ) {
+
+					// String was a <table>, *may* have spurious <tbody>
+					var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+						div.firstChild && div.firstChild.childNodes :
+
+						// String was a bare <thead> or <tfoot>
+						wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+							div.childNodes :
+							[];
+
+					for ( var j = tbody.length - 1; j >= 0 ; --j )
+						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+							tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+					// IE completely kills leading whitespace when innerHTML is used
+					if ( /^\s/.test( elem ) )
+						div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+
+				}
+
+				elem = jQuery.makeArray( div.childNodes );
+			}
+
+			if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+				return;
+
+			if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+				ret.push( elem );
+
+			else
+				ret = jQuery.merge( ret, elem );
+
+		});
+
+		return ret;
+	},
+
+	attr: function( elem, name, value ) {
+		// don't set attributes on text and comment nodes
+		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+			return undefined;
+
+		var notxml = !jQuery.isXMLDoc( elem ),
+			// Whether we are setting (or getting)
+			set = value !== undefined,
+			msie = jQuery.browser.msie;
+
+		// Try to normalize/fix the name
+		name = notxml && jQuery.props[ name ] || name;
+
+		// Only do all the following if this is a node (faster for style)
+		// IE elem.getAttribute passes even for style
+		if ( elem.tagName ) {
+
+			// These attributes require special treatment
+			var special = /href|src|style/.test( name );
+
+			// Safari mis-reports the default selected property of a hidden option
+			// Accessing the parent's selectedIndex property fixes it
+			if ( name == "selected" && jQuery.browser.safari )
+				elem.parentNode.selectedIndex;
+
+			// If applicable, access the attribute via the DOM 0 way
+			if ( name in elem && notxml && !special ) {
+				if ( set ){
+					// We can't allow the type property to be changed (since it causes problems in IE)
+					if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+						throw "type property can't be changed";
+
+					elem[ name ] = value;
+				}
+
+				// browsers index elements by id/name on forms, give priority to attributes.
+				if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+					return elem.getAttributeNode( name ).nodeValue;
+
+				return elem[ name ];
+			}
+
+			if ( msie && notxml &&  name == "style" )
+				return jQuery.attr( elem.style, "cssText", value );
+
+			if ( set )
+				// convert the value to a string (all browsers do this but IE) see #1070
+				elem.setAttribute( name, "" + value );
+
+			var attr = msie && notxml && special
+					// Some attributes require a special call on IE
+					? elem.getAttribute( name, 2 )
+					: elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return attr === null ? undefined : attr;
+		}
+
+		// elem is actually elem.style ... set the style
+
+		// IE uses filters for opacity
+		if ( msie && name == "opacity" ) {
+			if ( set ) {
+				// IE has trouble with opacity if it does not have layout
+				// Force it by setting the zoom level
+				elem.zoom = 1;
+
+				// Set the alpha filter to set the opacity
+				elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+					(parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+			}
+
+			return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+				(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+				"";
+		}
+
+		name = name.replace(/-([a-z])/ig, function(all, letter){
+			return letter.toUpperCase();
+		});
+
+		if ( set )
+			elem[ name ] = value;
+
+		return elem[ name ];
+	},
+
+	trim: function( text ) {
+		return (text || "").replace( /^\s+|\s+$/g, "" );
+	},
+
+	makeArray: function( array ) {
+		var ret = [];
+
+		if( array != null ){
+			var i = array.length;
+			//the window, strings and functions also have 'length'
+			if( i == null || array.split || array.setInterval || array.call )
+				ret[0] = array;
+			else
+				while( i )
+					ret[--i] = array[i];
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+		for ( var i = 0, length = array.length; i < length; i++ )
+		// Use === because on IE, window == document
+			if ( array[ i ] === elem )
+				return i;
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		// We have to loop this way because IE & Opera overwrite the length
+		// expando of getElementsByTagName
+		var i = 0, elem, pos = first.length;
+		// Also, we need to make sure that the correct elements are being returned
+		// (IE returns comment nodes in a '*' query)
+		if ( jQuery.browser.msie ) {
+			while ( elem = second[ i++ ] )
+				if ( elem.nodeType != 8 )
+					first[ pos++ ] = elem;
+
+		} else
+			while ( elem = second[ i++ ] )
+				first[ pos++ ] = elem;
+
+		return first;
+	},
+
+	unique: function( array ) {
+		var ret = [], done = {};
+
+		try {
+
+			for ( var i = 0, length = array.length; i < length; i++ ) {
+				var id = jQuery.data( array[ i ] );
+
+				if ( !done[ id ] ) {
+					done[ id ] = true;
+					ret.push( array[ i ] );
+				}
+			}
+
+		} catch( e ) {
+			ret = array;
+		}
+
+		return ret;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [];
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ )
+			if ( !inv != !callback( elems[ i ], i ) )
+				ret.push( elems[ i ] );
+
+		return ret;
+	},
+
+	map: function( elems, callback ) {
+		var ret = [];
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			var value = callback( elems[ i ], i );
+
+			if ( value != null )
+				ret[ ret.length ] = value;
+		}
+
+		return ret.concat.apply( [], ret );
+	}
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+	safari: /webkit/.test( userAgent ),
+	opera: /opera/.test( userAgent ),
+	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+	"styleFloat" :
+	"cssFloat";
+
+jQuery.extend({
+	// Check to see if the W3C box model is being used
+	boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+
+	props: {
+		"for": "htmlFor",
+		"class": "className",
+		"float": styleFloat,
+		cssFloat: styleFloat,
+		styleFloat: styleFloat,
+		readonly: "readOnly",
+		maxlength: "maxLength",
+		cellspacing: "cellSpacing",
+		rowspan: "rowSpan"
+	}
+});
+
+jQuery.each({
+	parent: function(elem){return elem.parentNode;},
+	parents: function(elem){return jQuery.dir(elem,"parentNode");},
+	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+	children: function(elem){return jQuery.sibling(elem.firstChild);},
+	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = jQuery.map( this, fn );
+
+		if ( selector && typeof selector == "string" )
+			ret = jQuery.multiFilter( selector, ret );
+
+		return this.pushStack( jQuery.unique( ret ) );
+	};
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function(name, original){
+	jQuery.fn[ name ] = function() {
+		var args = arguments;
+
+		return this.each(function(){
+			for ( var i = 0, length = args.length; i < length; i++ )
+				jQuery( args[ i ] )[ original ]( this );
+		});
+	};
+});
+
+jQuery.each({
+	removeAttr: function( name ) {
+		jQuery.attr( this, name, "" );
+		if (this.nodeType == 1)
+			this.removeAttribute( name );
+	},
+
+	addClass: function( classNames ) {
+		jQuery.className.add( this, classNames );
+	},
+
+	removeClass: function( classNames ) {
+		jQuery.className.remove( this, classNames );
+	},
+
+	toggleClass: function( classNames ) {
+		jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+	},
+
+	remove: function( selector ) {
+		if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+			// Prevent memory leaks
+			jQuery( "*", this ).add(this).each(function(){
+				jQuery.event.remove(this);
+				jQuery.removeData(this);
+			});
+			if (this.parentNode)
+				this.parentNode.removeChild( this );
+		}
+	},
+
+	empty: function() {
+		// Remove element nodes and prevent memory leaks
+		jQuery( ">*", this ).remove();
+
+		// Remove any remaining nodes
+		while ( this.firstChild )
+			this.removeChild( this.firstChild );
+	}
+}, function(name, fn){
+	jQuery.fn[ name ] = function(){
+		return this.each( fn, arguments );
+	};
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+	var type = name.toLowerCase();
+
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		return this[0] == window ?
+			// Opera reports document.body.client[Width/Height] properly in both quirks and standards
+			jQuery.browser.opera && document.body[ "client" + name ] ||
+
+			// Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+			jQuery.browser.safari && window[ "inner" + name ] ||
+
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+
+			// Get document width or height
+			this[0] == document ?
+				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+				Math.max(
+					Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
+					Math.max(document.body["offset" + name], document.documentElement["offset" + name])
+				) :
+
+				// Get or set width or height on the element
+				size == undefined ?
+					// Get width or height on the element
+					(this.length ? jQuery.css( this[0], type ) : null) :
+
+					// Set the width or height on the element (default to pixels if value is unitless)
+					this.css( type, size.constructor == String ? size : size + "px" );
+	};
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+	return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+		"(?:[\\w*_-]|\\\\.)" :
+		"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+	quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+	quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+	quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+	expr: {
+		"": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},
+		"#": function(a,i,m){return a.getAttribute("id")==m[2];},
+		":": {
+			// Position Checks
+			lt: function(a,i,m){return i<m[3]-0;},
+			gt: function(a,i,m){return i>m[3]-0;},
+			nth: function(a,i,m){return m[3]-0==i;},
+			eq: function(a,i,m){return m[3]-0==i;},
+			first: function(a,i){return i==0;},
+			last: function(a,i,m,r){return i==r.length-1;},
+			even: function(a,i){return i%2==0;},
+			odd: function(a,i){return i%2;},
+
+			// Child Checks
+			"first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},
+			"last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},
+			"only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},
+
+			// Parent Checks
+			parent: function(a){return a.firstChild;},
+			empty: function(a){return !a.firstChild;},
+
+			// Text Check
+			contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},
+
+			// Visibility
+			visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},
+			hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},
+
+			// Form attributes
+			enabled: function(a){return !a.disabled;},
+			disabled: function(a){return a.disabled;},
+			checked: function(a){return a.checked;},
+			selected: function(a){return a.selected||jQuery.attr(a,"selected");},
+
+			// Form elements
+			text: function(a){return "text"==a.type;},
+			radio: function(a){return "radio"==a.type;},
+			checkbox: function(a){return "checkbox"==a.type;},
+			file: function(a){return "file"==a.type;},
+			password: function(a){return "password"==a.type;},
+			submit: function(a){return "submit"==a.type;},
+			image: function(a){return "image"==a.type;},
+			reset: function(a){return "reset"==a.type;},
+			button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");},
+			input: function(a){return /input|select|textarea|button/i.test(a.nodeName);},
+
+			// :has()
+			has: function(a,i,m){return jQuery.find(m[3],a).length;},
+
+			// :header
+			header: function(a){return /h\d/i.test(a.nodeName);},
+
+			// :animated
+			animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}
+		}
+	},
+
+	// The regular expressions that power the parsing engine
+	parse: [
+		// Match: [@value='test'], [@foo]
+		/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+		// Match: :contains('foo')
+		/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+		// Match: :even, :last-child, #id, .class
+		new RegExp("^([:.#]*)(" + chars + "+)")
+	],
+
+	multiFilter: function( expr, elems, not ) {
+		var old, cur = [];
+
+		while ( expr && expr != old ) {
+			old = expr;
+			var f = jQuery.filter( expr, elems, not );
+			expr = f.t.replace(/^\s*,\s*/, "" );
+			cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+		}
+
+		return cur;
+	},
+
+	find: function( t, context ) {
+		// Quickly handle non-string expressions
+		if ( typeof t != "string" )
+			return [ t ];
+
+		// check to make sure context is a DOM element or a document
+		if ( context && context.nodeType != 1 && context.nodeType != 9)
+			return [ ];
+
+		// Set the correct context (if none is provided)
+		context = context || document;
+
+		// Initialize the search
+		var ret = [context], done = [], last, nodeName;
+
+		// Continue while a selector expression exists, and while
+		// we're no longer looping upon ourselves
+		while ( t && last != t ) {
+			var r = [];
+			last = t;
+
+			t = jQuery.trim(t);
+
+			var foundToken = false,
+
+			// An attempt at speeding up child selectors that
+			// point to a specific element tag
+				re = quickChild,
+
+				m = re.exec(t);
+
+			if ( m ) {
+				nodeName = m[1].toUpperCase();
+
+				// Perform our own iteration and filter
+				for ( var i = 0; ret[i]; i++ )
+					for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+						if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+							r.push( c );
+
+				ret = r;
+				t = t.replace( re, "" );
+				if ( t.indexOf(" ") == 0 ) continue;
+				foundToken = true;
+			} else {
+				re = /^([>+~])\s*(\w*)/i;
+
+				if ( (m = re.exec(t)) != null ) {
+					r = [];
+
+					var merge = {};
+					nodeName = m[2].toUpperCase();
+					m = m[1];
+
+					for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+						var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+						for ( ; n; n = n.nextSibling )
+							if ( n.nodeType == 1 ) {
+								var id = jQuery.data(n);
+
+								if ( m == "~" && merge[id] ) break;
+
+								if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+									if ( m == "~" ) merge[id] = true;
+									r.push( n );
+								}
+
+								if ( m == "+" ) break;
+							}
+					}
+
+					ret = r;
+
+					// And remove the token
+					t = jQuery.trim( t.replace( re, "" ) );
+					foundToken = true;
+				}
+			}
+
+			// See if there's still an expression, and that we haven't already
+			// matched a token
+			if ( t && !foundToken ) {
+				// Handle multiple expressions
+				if ( !t.indexOf(",") ) {
+					// Clean the result set
+					if ( context == ret[0] ) ret.shift();
+
+					// Merge the result sets
+					done = jQuery.merge( done, ret );
+
+					// Reset the context
+					r = ret = [context];
+
+					// Touch up the selector string
+					t = " " + t.substr(1,t.length);
+
+				} else {
+					// Optimize for the case nodeName#idName
+					var re2 = quickID;
+					var m = re2.exec(t);
+
+					// Re-organize the results, so that they're consistent
+					if ( m ) {
+						m = [ 0, m[2], m[3], m[1] ];
+
+					} else {
+						// Otherwise, do a traditional filter check for
+						// ID, class, and element selectors
+						re2 = quickClass;
+						m = re2.exec(t);
+					}
+
+					m[2] = m[2].replace(/\\/g, "");
+
+					var elem = ret[ret.length-1];
+
+					// Try to do a global search by ID, where we can
+					if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+						// Optimization for HTML document case
+						var oid = elem.getElementById(m[2]);
+
+						// Do a quick check for the existence of the actual ID attribute
+						// to avoid selecting by the name attribute in IE
+						// also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+						if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+							oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+						// Do a quick check for node name (where applicable) so
+						// that div#foo searches will be really fast
+						ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+					} else {
+						// We need to find all descendant elements
+						for ( var i = 0; ret[i]; i++ ) {
+							// Grab the tag name being searched for
+							var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+							// Handle IE7 being really dumb about <object>s
+							if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+								tag = "param";
+
+							r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+						}
+
+						// It's faster to filter by class and be done with it
+						if ( m[1] == "." )
+							r = jQuery.classFilter( r, m[2] );
+
+						// Same with ID filtering
+						if ( m[1] == "#" ) {
+							var tmp = [];
+
+							// Try to find the element with the ID
+							for ( var i = 0; r[i]; i++ )
+								if ( r[i].getAttribute("id") == m[2] ) {
+									tmp = [ r[i] ];
+									break;
+								}
+
+							r = tmp;
+						}
+
+						ret = r;
+					}
+
+					t = t.replace( re2, "" );
+				}
+
+			}
+
+			// If a selector string still exists
+			if ( t ) {
+				// Attempt to filter it
+				var val = jQuery.filter(t,r);
+				ret = r = val.r;
+				t = jQuery.trim(val.t);
+			}
+		}
+
+		// An error occurred with the selector;
+		// just return an empty set instead
+		if ( t )
+			ret = [];
+
+		// Remove the root context
+		if ( ret && context == ret[0] )
+			ret.shift();
+
+		// And combine the results
+		done = jQuery.merge( done, ret );
+
+		return done;
+	},
+
+	classFilter: function(r,m,not){
+		m = " " + m + " ";
+		var tmp = [];
+		for ( var i = 0; r[i]; i++ ) {
+			var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+			if ( !not && pass || not && !pass )
+				tmp.push( r[i] );
+		}
+		return tmp;
+	},
+
+	filter: function(t,r,not) {
+		var last;
+
+		// Look for common filter expressions
+		while ( t && t != last ) {
+			last = t;
+
+			var p = jQuery.parse, m;
+
+			for ( var i = 0; p[i]; i++ ) {
+				m = p[i].exec( t );
+
+				if ( m ) {
+					// Remove what we just matched
+					t = t.substring( m[0].length );
+
+					m[2] = m[2].replace(/\\/g, "");
+					break;
+				}
+			}
+
+			if ( !m )
+				break;
+
+			// :not() is a special case that can be optimized by
+			// keeping it out of the expression list
+			if ( m[1] == ":" && m[2] == "not" )
+				// optimize if only one selector found (most common case)
+				r = isSimple.test( m[3] ) ?
+					jQuery.filter(m[3], r, true).r :
+					jQuery( r ).not( m[3] );
+
+			// We can get a big speed boost by filtering by class here
+			else if ( m[1] == "." )
+				r = jQuery.classFilter(r, m[2], not);
+
+			else if ( m[1] == "[" ) {
+				var tmp = [], type = m[3];
+
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+
+					if ( z == null || /href|src|selected/.test(m[2]) )
+						z = jQuery.attr(a,m[2]) || '';
+
+					if ( (type == "" && !!z ||
+						 type == "=" && z == m[5] ||
+						 type == "!=" && z != m[5] ||
+						 type == "^=" && z && !z.indexOf(m[5]) ||
+						 type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+						 (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+							tmp.push( a );
+				}
+
+				r = tmp;
+
+			// We can get a speed boost by handling nth-child here
+			} else if ( m[1] == ":" && m[2] == "nth-child" ) {
+				var merge = {}, tmp = [],
+					// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+					test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+						m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+						!/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+					// calculate the numbers (first)n+(last) including if they are negative
+					first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+
+				// loop through all the elements left in the jQuery object
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+					if ( !merge[id] ) {
+						var c = 1;
+
+						for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+							if ( n.nodeType == 1 )
+								n.nodeIndex = c++;
+
+						merge[id] = true;
+					}
+
+					var add = false;
+
+					if ( first == 0 ) {
+						if ( node.nodeIndex == last )
+							add = true;
+					} else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+						add = true;
+
+					if ( add ^ not )
+						tmp.push( node );
+				}
+
+				r = tmp;
+
+			// Otherwise, find the expression to execute
+			} else {
+				var fn = jQuery.expr[ m[1] ];
+				if ( typeof fn == "object" )
+					fn = fn[ m[2] ];
+
+				if ( typeof fn == "string" )
+					fn = eval("false||function(a,i){return " + fn + ";}");
+
+				// Execute it against the current filter
+				r = jQuery.grep( r, function(elem, i){
+					return fn(elem, i, m, r);
+				}, not );
+			}
+		}
+
+		// Return an array of filtered elements (r)
+		// and the modified expression string (t)
+		return { r: r, t: t };
+	},
+
+	dir: function( elem, dir ){
+		var matched = [],
+			cur = elem[dir];
+		while ( cur && cur != document ) {
+			if ( cur.nodeType == 1 )
+				matched.push( cur );
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	nth: function(cur,result,dir,elem){
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] )
+			if ( cur.nodeType == 1 && ++num == result )
+				break;
+
+		return cur;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType == 1 && n != elem )
+				r.push( n );
+		}
+
+		return r;
+	}
+});
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function(elem, types, handler, data) {
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		// For whatever reason, IE has trouble passing the window object
+		// around, causing it to be cloned in the process
+		if ( jQuery.browser.msie && elem.setInterval )
+			elem = window;
+
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid )
+			handler.guid = this.guid++;
+
+		// if data is passed, bind to handler
+		if( data != undefined ) {
+			// Create temporary function pointer to original handler
+			var fn = handler;
+
+			// Create unique handler function, wrapped around original handler
+			handler = this.proxy( fn, function() {
+				// Pass arguments and context to original handler
+				return fn.apply(this, arguments);
+			});
+
+			// Store data in unique handler
+			handler.data = data;
+		}
+
+		// Init the element's event structure
+		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+				// Handle the second event of a trigger and when
+				// an event is called after a page has unloaded
+				if ( typeof jQuery != "undefined" && !jQuery.event.triggered )
+					return jQuery.event.handle.apply(arguments.callee.elem, arguments);
+			});
+		// Add elem as a property of the handle function
+		// This is to prevent a memory leak with non-native
+		// event in IE.
+		handle.elem = elem;
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		jQuery.each(types.split(/\s+/), function(index, type) {
+			// Namespaced event handlers
+			var parts = type.split(".");
+			type = parts[0];
+			handler.type = parts[1];
+
+			// Get the current list of functions bound to this event
+			var handlers = events[type];
+
+			// Init the event handler queue
+			if (!handlers) {
+				handlers = events[type] = {};
+
+				// Check for a special event handler
+				// Only use addEventListener/attachEvent if the special
+				// events handler returns false
+				if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+					// Bind the global event handler to the element
+					if (elem.addEventListener)
+						elem.addEventListener(type, handle, false);
+					else if (elem.attachEvent)
+						elem.attachEvent("on" + type, handle);
+				}
+			}
+
+			// Add the function to the element's handler list
+			handlers[handler.guid] = handler;
+
+			// Keep track of which events have been used, for global triggering
+			jQuery.event.global[type] = true;
+		});
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	guid: 1,
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function(elem, types, handler) {
+		// don't do events on text and comment nodes
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		var events = jQuery.data(elem, "events"), ret, index;
+
+		if ( events ) {
+			// Unbind all events for the element
+			if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") )
+				for ( var type in events )
+					this.remove( elem, type + (types || "") );
+			else {
+				// types is actually an event object here
+				if ( types.type ) {
+					handler = types.handler;
+					types = types.type;
+				}
+
+				// Handle multiple events seperated by a space
+				// jQuery(...).unbind("mouseover mouseout", fn);
+				jQuery.each(types.split(/\s+/), function(index, type){
+					// Namespaced event handlers
+					var parts = type.split(".");
+					type = parts[0];
+
+					if ( events[type] ) {
+						// remove the given handler for the given type
+						if ( handler )
+							delete events[type][handler.guid];
+
+						// remove all handlers for the given type
+						else
+							for ( handler in events[type] )
+								// Handle the removal of namespaced events
+								if ( !parts[1] || events[type][handler].type == parts[1] )
+									delete events[type][handler];
+
+						// remove generic event handler if no more handlers exist
+						for ( ret in events[type] ) break;
+						if ( !ret ) {
+							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+								if (elem.removeEventListener)
+									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+								else if (elem.detachEvent)
+									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+							}
+							ret = null;
+							delete events[type];
+						}
+					}
+				});
+			}
+
+			// Remove the expando if it's no longer used
+			for ( ret in events ) break;
+			if ( !ret ) {
+				var handle = jQuery.data( elem, "handle" );
+				if ( handle ) handle.elem = null;
+				jQuery.removeData( elem, "events" );
+				jQuery.removeData( elem, "handle" );
+			}
+		}
+	},
+
+	trigger: function(type, data, elem, donative, extra) {
+		// Clone the incoming data, if any
+		data = jQuery.makeArray(data);
+
+		if ( type.indexOf("!") >= 0 ) {
+			type = type.slice(0, -1);
+			var exclusive = true;
+		}
+
+		// Handle a global trigger
+		if ( !elem ) {
+			// Only trigger if we've ever bound an event for it
+			if ( this.global[type] )
+				jQuery("*").add([window, document]).trigger(type, data);
+
+		// Handle triggering a single element
+		} else {
+			// don't do events on text and comment nodes
+			if ( elem.nodeType == 3 || elem.nodeType == 8 )
+				return undefined;
+
+			var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+				// Check to see if we need to provide a fake event, or not
+				event = !data[0] || !data[0].preventDefault;
+
+			// Pass along a fake event
+			if ( event ) {
+				data.unshift({
+					type: type,
+					target: elem,
+					preventDefault: function(){},
+					stopPropagation: function(){},
+					timeStamp: now()
+				});
+				data[0][expando] = true; // no need to fix fake event
+			}
+
+			// Enforce the right trigger type
+			data[0].type = type;
+			if ( exclusive )
+				data[0].exclusive = true;
+
+			// Trigger the event, it is assumed that "handle" is a function
+			var handle = jQuery.data(elem, "handle");
+			if ( handle )
+				val = handle.apply( elem, data );
+
+			// Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+			if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+				val = false;
+
+			// Extra functions don't get the custom event object
+			if ( event )
+				data.shift();
+
+			// Handle triggering of extra function
+			if ( extra && jQuery.isFunction( extra ) ) {
+				// call the extra function and tack the current return value on the end for possible inspection
+				ret = extra.apply( elem, val == null ? data : data.concat( val ) );
+				// if anything is returned, give it precedence and have it overwrite the previous value
+				if (ret !== undefined)
+					val = ret;
+			}
+
+			// Trigger the native events (except for clicks on links)
+			if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+				this.triggered = true;
+				try {
+					elem[ type ]();
+				// prevent IE from throwing an error for some hidden elements
+				} catch (e) {}
+			}
+
+			this.triggered = false;
+		}
+
+		return val;
+	},
+
+	handle: function(event) {
+		// returned undefined or false
+		var val, ret, namespace, all, handlers;
+
+		event = arguments[0] = jQuery.event.fix( event || window.event );
+
+		// Namespaced event handlers
+		namespace = event.type.split(".");
+		event.type = namespace[0];
+		namespace = namespace[1];
+		// Cache this now, all = true means, any handler
+		all = !namespace && !event.exclusive;
+
+		handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+		for ( var j in handlers ) {
+			var handler = handlers[j];
+
+			// Filter the functions by class
+			if ( all || handler.type == namespace ) {
+				// Pass in a reference to the handler function itself
+				// So that we can later remove it
+				event.handler = handler;
+				event.data = handler.data;
+
+				ret = handler.apply( this, arguments );
+
+				if ( val !== false )
+					val = ret;
+
+				if ( ret === false ) {
+					event.preventDefault();
+					event.stopPropagation();
+				}
+			}
+		}
+
+		return val;
+	},
+
+	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" "),
+
+	fix: function(event) {
+		if ( event[expando] == true )
+			return event;
+
+		// store a copy of the original event object
+		// and "clone" to set read-only properties
+		var originalEvent = event;
+		event = { originalEvent: originalEvent };
+
+		for ( var i = this.props.length, prop; i; ){
+			prop = this.props[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Mark it as fixed
+		event[expando] = true;
+
+		// add preventDefault and stopPropagation since
+		// they will not work on the clone
+		event.preventDefault = function() {
+			// if preventDefault exists run it on the original event
+			if (originalEvent.preventDefault)
+				originalEvent.preventDefault();
+			// otherwise set the returnValue property of the original event to false (IE)
+			originalEvent.returnValue = false;
+		};
+		event.stopPropagation = function() {
+			// if stopPropagation exists run it on the original event
+			if (originalEvent.stopPropagation)
+				originalEvent.stopPropagation();
+			// otherwise set the cancelBubble property of the original event to true (IE)
+			originalEvent.cancelBubble = true;
+		};
+
+		// Fix timeStamp
+		event.timeStamp = event.timeStamp || now();
+
+		// Fix target property, if necessary
+		if ( !event.target )
+			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+		// check if target is a textnode (safari)
+		if ( event.target.nodeType == 3 )
+			event.target = event.target.parentNode;
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement )
+			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var doc = document.documentElement, body = document.body;
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+		}
+
+		// Add which for key events
+		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+			event.which = event.charCode || event.keyCode;
+
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey )
+			event.metaKey = event.ctrlKey;
+
+		// Add which for click: 1 == left; 2 == middle; 3 == right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button )
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+		return event;
+	},
+
+	proxy: function( fn, proxy ){
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+		// So proxy can be declared as an argument
+		return proxy;
+	},
+
+	special: {
+		ready: {
+			setup: function() {
+				// Make sure the ready event is setup
+				bindReady();
+				return;
+			},
+
+			teardown: function() { return; }
+		},
+
+		mouseenter: {
+			setup: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+				return true;
+			},
+
+			teardown: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+				return true;
+			},
+
+			handler: function(event) {
+				// If we actually just moused on to a sub-element, ignore it
+				if ( withinElement(event, this) ) return true;
+				// Execute the right handlers by setting the event type to mouseenter
+				event.type = "mouseenter";
+				return jQuery.event.handle.apply(this, arguments);
+			}
+		},
+
+		mouseleave: {
+			setup: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+				return true;
+			},
+
+			teardown: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+				return true;
+			},
+
+			handler: function(event) {
+				// If we actually just moused on to a sub-element, ignore it
+				if ( withinElement(event, this) ) return true;
+				// Execute the right handlers by setting the event type to mouseleave
+				event.type = "mouseleave";
+				return jQuery.event.handle.apply(this, arguments);
+			}
+		}
+	}
+};
+
+jQuery.fn.extend({
+	bind: function( type, data, fn ) {
+		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+			jQuery.event.add( this, type, fn || data, fn && data );
+		});
+	},
+
+	one: function( type, data, fn ) {
+		var one = jQuery.event.proxy( fn || data, function(event) {
+			jQuery(this).unbind(event, one);
+			return (fn || data).apply( this, arguments );
+		});
+		return this.each(function(){
+			jQuery.event.add( this, type, one, fn && data);
+		});
+	},
+
+	unbind: function( type, fn ) {
+		return this.each(function(){
+			jQuery.event.remove( this, type, fn );
+		});
+	},
+
+	trigger: function( type, data, fn ) {
+		return this.each(function(){
+			jQuery.event.trigger( type, data, this, true, fn );
+		});
+	},
+
+	triggerHandler: function( type, data, fn ) {
+		return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments, i = 1;
+
+		// link all the functions, so any of them can unbind this click handler
+		while( i < args.length )
+			jQuery.event.proxy( fn, args[i++] );
+
+		return this.click( jQuery.event.proxy( fn, function(event) {
+			// Figure out which function to execute
+			this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+			// Make sure that clicks stop
+			event.preventDefault();
+
+			// and execute the function
+			return args[ this.lastToggle++ ].apply( this, arguments ) || false;
+		}));
+	},
+
+	hover: function(fnOver, fnOut) {
+		return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+	},
+
+	ready: function(fn) {
+		// Attach the listeners
+		bindReady();
+
+		// If the DOM is already ready
+		if ( jQuery.isReady )
+			// Execute the function immediately
+			fn.call( document, jQuery );
+
+		// Otherwise, remember the function for later
+		else
+			// Add the function to the wait list
+			jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+
+		return this;
+	}
+});
+
+jQuery.extend({
+	isReady: false,
+	readyList: [],
+	// Handle when the DOM is ready
+	ready: function() {
+		// Make sure that the DOM is not already loaded
+		if ( !jQuery.isReady ) {
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If there are functions bound, to execute
+			if ( jQuery.readyList ) {
+				// Execute all of them
+				jQuery.each( jQuery.readyList, function(){
+					this.call( document );
+				});
+
+				// Reset the list of functions
+				jQuery.readyList = null;
+			}
+
+			// Trigger any bound ready events
+			jQuery(document).triggerHandler("ready");
+		}
+	}
+});
+
+var readyBound = false;
+
+function bindReady(){
+	if ( readyBound ) return;
+	readyBound = true;
+
+	// Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+	if ( document.addEventListener && !jQuery.browser.opera)
+		// Use the handy event callback
+		document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+
+	// If IE is used and is not in a frame
+	// Continually check to see if the document is ready
+	if ( jQuery.browser.msie && window == top ) (function(){
+		if (jQuery.isReady) return;
+		try {
+			// If IE is used, use the trick by Diego Perini
+			// http://javascript.nwbox.com/IEContentLoaded/
+			document.documentElement.doScroll("left");
+		} catch( error ) {
+			setTimeout( arguments.callee, 0 );
+			return;
+		}
+		// and execute any waiting functions
+		jQuery.ready();
+	})();
+
+	if ( jQuery.browser.opera )
+		document.addEventListener( "DOMContentLoaded", function () {
+			if (jQuery.isReady) return;
+			for (var i = 0; i < document.styleSheets.length; i++)
+				if (document.styleSheets[i].disabled) {
+					setTimeout( arguments.callee, 0 );
+					return;
+				}
+			// and execute any waiting functions
+			jQuery.ready();
+		}, false);
+
+	if ( jQuery.browser.safari ) {
+		var numStyles;
+		(function(){
+			if (jQuery.isReady) return;
+			if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+			if ( numStyles === undefined )
+				numStyles = jQuery("style, link[rel=stylesheet]").length;
+			if ( document.styleSheets.length != numStyles ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+			// and execute any waiting functions
+			jQuery.ready();
+		})();
+	}
+
+	// A fallback to window.onload, that will always work
+	jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+	"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
+	"submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+	// Handle event binding
+	jQuery.fn[name] = function(fn){
+		return fn ? this.bind(name, fn) : this.trigger(name);
+	};
+});
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event, elem) {
+	// Check if mouse(over|out) are still within the same parent element
+	var parent = event.relatedTarget;
+	// Traverse up the tree
+	while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
+	// Return true if we actually just moused on to a sub-element
+	return parent == elem;
+};
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery(window).bind("unload", function() {
+	jQuery("*").add(document).unbind();
+});
+jQuery.fn.extend({
+	// Keep a copy of the old load
+	_load: jQuery.fn.load,
+
+	load: function( url, params, callback ) {
+		if ( typeof url != 'string' )
+			return this._load( url );
+
+		var off = url.indexOf(" ");
+		if ( off >= 0 ) {
+			var selector = url.slice(off, url.length);
+			url = url.slice(0, off);
+		}
+
+		callback = callback || function(){};
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params )
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else if( typeof params == 'object' ) {
+				params = jQuery.param( params );
+				type = "POST";
+			}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			complete: function(res, status){
+				// If successful, inject the HTML into all the matched elements
+				if ( status == "success" || status == "notmodified" )
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div/>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						res.responseText );
+
+				self.each( callback, [res.responseText, status, res] );
+			}
+		});
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param(this.serializeArray());
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			return jQuery.nodeName(this, "form") ?
+				jQuery.makeArray(this.elements) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				(this.checked || /select|textarea/i.test(this.nodeName) ||
+					/text|hidden|password/i.test(this.type));
+		})
+		.map(function(i, elem){
+			var val = jQuery(this).val();
+			return val == null ? null :
+				val.constructor == Array ?
+					jQuery.map( val, function(val, i){
+						return {name: elem.name, value: val};
+					}) :
+					{name: elem.name, value: val};
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+	jQuery.fn[o] = function(f){
+		return this.bind(o, f);
+	};
+});
+
+var jsc = now();
+
+jQuery.extend({
+	get: function( url, data, callback, type ) {
+		// shift arguments if data argument was ommited
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = null;
+		}
+
+		return jQuery.ajax({
+			type: "GET",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get(url, null, callback, "script");
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get(url, data, callback, "json");
+	},
+
+	post: function( url, data, callback, type ) {
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = {};
+		}
+
+		return jQuery.ajax({
+			type: "POST",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	ajaxSetup: function( settings ) {
+		jQuery.extend( jQuery.ajaxSettings, settings );
+	},
+
+	ajaxSettings: {
+		url: location.href,
+		global: true,
+		type: "GET",
+		timeout: 0,
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		data: null,
+		username: null,
+		password: null,
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			script: "text/javascript, application/javascript",
+			json: "application/json, text/javascript",
+			text: "text/plain",
+			_default: "*/*"
+		}
+	},
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+
+	ajax: function( s ) {
+		// Extend the settings, but re-extend 's' so that it can be
+		// checked again later (in the test suite, specifically)
+		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+		var jsonp, jsre = /=\?(&|$)/g, status, data,
+			type = s.type.toUpperCase();
+
+		// convert data if not already a string
+		if ( s.data && s.processData && typeof s.data != "string" )
+			s.data = jQuery.param(s.data);
+
+		// Handle JSONP Parameter Callbacks
+		if ( s.dataType == "jsonp" ) {
+			if ( type == "GET" ) {
+				if ( !s.url.match(jsre) )
+					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+			} else if ( !s.data || !s.data.match(jsre) )
+				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+			s.dataType = "json";
+		}
+
+		// Build temporary JSONP function
+		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+			jsonp = "jsonp" + jsc++;
+
+			// Replace the =? sequence both in the query string and the data
+			if ( s.data )
+				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+			s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+			// We need to make sure
+			// that a JSONP style response is executed properly
+			s.dataType = "script";
+
+			// Handle JSONP-style loading
+			window[ jsonp ] = function(tmp){
+				data = tmp;
+				success();
+				complete();
+				// Garbage collect
+				window[ jsonp ] = undefined;
+				try{ delete window[ jsonp ]; } catch(e){}
+				if ( head )
+					head.removeChild( script );
+			};
+		}
+
+		if ( s.dataType == "script" && s.cache == null )
+			s.cache = false;
+
+		if ( s.cache === false && type == "GET" ) {
+			var ts = now();
+			// try replacing _= if it is there
+			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+			// if nothing was replaced, add timestamp to the end
+			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+		}
+
+		// If data is available, append data to url for get requests
+		if ( s.data && type == "GET" ) {
+			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+			// IE likes to send both get and post data, prevent this
+			s.data = null;
+		}
+
+		// Watch for a new set of requests
+		if ( s.global && ! jQuery.active++ )
+			jQuery.event.trigger( "ajaxStart" );
+
+		// Matches an absolute URL, and saves the domain
+		var remote = /^(?:\w+:)?\/\/([^\/?#]+)/;
+
+		// If we're requesting a remote document
+		// and trying to load JSON or Script with a GET
+		if ( s.dataType == "script" && type == "GET"
+				&& remote.test(s.url) && remote.exec(s.url)[1] != location.host ){
+			var head = document.getElementsByTagName("head")[0];
+			var script = document.createElement("script");
+			script.src = s.url;
+			if (s.scriptCharset)
+				script.charset = s.scriptCharset;
+
+			// Handle Script loading
+			if ( !jsonp ) {
+				var done = false;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function(){
+					if ( !done && (!this.readyState ||
+							this.readyState == "loaded" || this.readyState == "complete") ) {
+						done = true;
+						success();
+						complete();
+						head.removeChild( script );
+					}
+				};
+			}
+
+			head.appendChild(script);
+
+			// We handle everything using the script element injection
+			return undefined;
+		}
+
+		var requestDone = false;
+
+		// Create the request object; Microsoft failed to properly
+		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+		var xhr = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+		// Open the socket
+		// Passing null username, generates a login popup on Opera (#2865)
+		if( s.username )
+			xhr.open(type, s.url, s.async, s.username, s.password);
+		else
+			xhr.open(type, s.url, s.async);
+
+		// Need an extra try/catch for cross domain requests in Firefox 3
+		try {
+			// Set the correct header, if data is being sent
+			if ( s.data )
+				xhr.setRequestHeader("Content-Type", s.contentType);
+
+			// Set the If-Modified-Since header, if ifModified mode.
+			if ( s.ifModified )
+				xhr.setRequestHeader("If-Modified-Since",
+					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+			// Set header so the called script knows that it's an XMLHttpRequest
+			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+			// Set the Accepts header for the server, depending on the dataType
+			xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+				s.accepts[ s.dataType ] + ", */*" :
+				s.accepts._default );
+		} catch(e){}
+
+		// Allow custom headers/mimetypes
+		if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
+			// cleanup active request counter
+			s.global && jQuery.active--;
+			// close opended socket
+			xhr.abort();
+			return false;
+		}
+
+		if ( s.global )
+			jQuery.event.trigger("ajaxSend", [xhr, s]);
+
+		// Wait for a response to come back
+		var onreadystatechange = function(isTimeout){
+			// The transfer is complete and the data is available, or the request timed out
+			if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+				requestDone = true;
+
+				// clear poll interval
+				if (ival) {
+					clearInterval(ival);
+					ival = null;
+				}
+
+				status = isTimeout == "timeout" ? "timeout" :
+					!jQuery.httpSuccess( xhr ) ? "error" :
+					s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
+					"success";
+
+				if ( status == "success" ) {
+					// Watch for, and catch, XML document parse errors
+					try {
+						// process the data (runs the xml through httpData regardless of callback)
+						data = jQuery.httpData( xhr, s.dataType, s.dataFilter );
+					} catch(e) {
+						status = "parsererror";
+					}
+				}
+
+				// Make sure that the request was successful or notmodified
+				if ( status == "success" ) {
+					// Cache Last-Modified header, if ifModified mode.
+					var modRes;
+					try {
+						modRes = xhr.getResponseHeader("Last-Modified");
+					} catch(e) {} // swallow exception thrown by FF if header is not available
+
+					if ( s.ifModified && modRes )
+						jQuery.lastModified[s.url] = modRes;
+
+					// JSONP handles its own success callback
+					if ( !jsonp )
+						success();
+				} else
+					jQuery.handleError(s, xhr, status);
+
+				// Fire the complete handlers
+				complete();
+
+				// Stop memory leaks
+				if ( s.async )
+					xhr = null;
+			}
+		};
+
+		if ( s.async ) {
+			// don't attach the handler to the request, just poll it instead
+			var ival = setInterval(onreadystatechange, 13);
+
+			// Timeout checker
+			if ( s.timeout > 0 )
+				setTimeout(function(){
+					// Check to see if the request is still happening
+					if ( xhr ) {
+						// Cancel the request
+						xhr.abort();
+
+						if( !requestDone )
+							onreadystatechange( "timeout" );
+					}
+				}, s.timeout);
+		}
+
+		// Send the data
+		try {
+			xhr.send(s.data);
+		} catch(e) {
+			jQuery.handleError(s, xhr, null, e);
+		}
+
+		// firefox 1.5 doesn't fire statechange for sync requests
+		if ( !s.async )
+			onreadystatechange();
+
+		function success(){
+			// If a local callback was specified, fire it and pass it the data
+			if ( s.success )
+				s.success( data, status );
+
+			// Fire the global callback
+			if ( s.global )
+				jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
+		}
+
+		function complete(){
+			// Process result
+			if ( s.complete )
+				s.complete(xhr, status);
+
+			// The request was completed
+			if ( s.global )
+				jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+			// Handle the global AJAX counter
+			if ( s.global && ! --jQuery.active )
+				jQuery.event.trigger( "ajaxStop" );
+		}
+
+		// return XMLHttpRequest to allow aborting the request etc.
+		return xhr;
+	},
+
+	handleError: function( s, xhr, status, e ) {
+		// If a local callback was specified, fire it
+		if ( s.error ) s.error( xhr, status, e );
+
+		// Fire the global callback
+		if ( s.global )
+			jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Determines if an XMLHttpRequest was successful or not
+	httpSuccess: function( xhr ) {
+		try {
+			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+			return !xhr.status && location.protocol == "file:" ||
+				( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223 ||
+				jQuery.browser.safari && xhr.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function( xhr, url ) {
+		try {
+			var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			return xhr.status == 304 || xhrRes == jQuery.lastModified[url] ||
+				jQuery.browser.safari && xhr.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	httpData: function( xhr, type, filter ) {
+		var ct = xhr.getResponseHeader("content-type"),
+			xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+			data = xml ? xhr.responseXML : xhr.responseText;
+
+		if ( xml && data.documentElement.tagName == "parsererror" )
+			throw "parsererror";
+			
+		// Allow a pre-filtering function to sanitize the response
+		if( filter )
+			data = filter( data, type );
+
+		// If the type is "script", eval it in global context
+		if ( type == "script" )
+			jQuery.globalEval( data );
+
+		// Get the JavaScript object, if JSON is used.
+		if ( type == "json" )
+			data = eval("(" + data + ")");
+
+		return data;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a ) {
+		var s = [ ];
+
+		function add( key, value ){
+			s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+		};
+
+		// If an array was passed in, assume that it is an array
+		// of form elements
+		if ( a.constructor == Array || a.jquery )
+			// Serialize the form elements
+			jQuery.each( a, function(){
+				add( this.name, this.value );
+			});
+
+		// Otherwise, assume that it's an object of key/value pairs
+		else
+			// Serialize the key/values
+			for ( var j in a )
+				// If the value is an array then the key names need to be repeated
+				if ( a[j] && a[j].constructor == Array )
+					jQuery.each( a[j], function(){
+						add( j, this );
+					});
+				else
+					add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );
+
+		// Return the resulting serialization
+		return s.join("&").replace(/%20/g, "+");
+	}
+
+});
+jQuery.fn.extend({
+	show: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "show", width: "show", opacity: "show"
+			}, speed, callback) :
+
+			this.filter(":hidden").each(function(){
+				this.style.display = this.oldblock || "";
+				if ( jQuery.css(this,"display") == "none" ) {
+					var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+					this.style.display = elem.css("display");
+					// handle an edge condition where css is - div { display:none; } or similar
+					if (this.style.display == "none")
+						this.style.display = "block";
+					elem.remove();
+				}
+			}).end();
+	},
+
+	hide: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "hide", width: "hide", opacity: "hide"
+			}, speed, callback) :
+
+			this.filter(":visible").each(function(){
+				this.oldblock = this.oldblock || jQuery.css(this,"display");
+				this.style.display = "none";
+			}).end();
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2 ){
+		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+			this._toggle.apply( this, arguments ) :
+			fn ?
+				this.animate({
+					height: "toggle", width: "toggle", opacity: "toggle"
+				}, fn, fn2) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+				});
+	},
+
+	slideDown: function(speed,callback){
+		return this.animate({height: "show"}, speed, callback);
+	},
+
+	slideUp: function(speed,callback){
+		return this.animate({height: "hide"}, speed, callback);
+	},
+
+	slideToggle: function(speed, callback){
+		return this.animate({height: "toggle"}, speed, callback);
+	},
+
+	fadeIn: function(speed, callback){
+		return this.animate({opacity: "show"}, speed, callback);
+	},
+
+	fadeOut: function(speed, callback){
+		return this.animate({opacity: "hide"}, speed, callback);
+	},
+
+	fadeTo: function(speed,to,callback){
+		return this.animate({opacity: to}, speed, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		return this[ optall.queue === false ? "each" : "queue" ](function(){
+			if ( this.nodeType != 1)
+				return false;
+
+			var opt = jQuery.extend({}, optall), p,
+				hidden = jQuery(this).is(":hidden"), self = this;
+
+			for ( p in prop ) {
+				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+					return opt.complete.call(this);
+
+				if ( p == "height" || p == "width" ) {
+					// Store display property
+					opt.display = jQuery.css(this, "display");
+
+					// Make sure that nothing sneaks out
+					opt.overflow = this.style.overflow;
+				}
+			}
+
+			if ( opt.overflow != null )
+				this.style.overflow = "hidden";
+
+			opt.curAnim = jQuery.extend({}, prop);
+
+			jQuery.each( prop, function(name, val){
+				var e = new jQuery.fx( self, opt, name );
+
+				if ( /toggle|show|hide/.test(val) )
+					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+				else {
+					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+						start = e.cur(true) || 0;
+
+					if ( parts ) {
+						var end = parseFloat(parts[2]),
+							unit = parts[3] || "px";
+
+						// We need to compute starting value
+						if ( unit != "px" ) {
+							self.style[ name ] = (end || 1) + unit;
+							start = ((end || 1) / e.cur(true)) * start;
+							self.style[ name ] = start + unit;
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] )
+							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+						e.custom( start, end, unit );
+					} else
+						e.custom( start, val, "" );
+				}
+			});
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+
+	queue: function(type, fn){
+		if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+			fn = type;
+			type = "fx";
+		}
+
+		if ( !type || (typeof type == "string" && !fn) )
+			return queue( this[0], type );
+
+		return this.each(function(){
+			if ( fn.constructor == Array )
+				queue(this, type, fn);
+			else {
+				queue(this, type).push( fn );
+
+				if ( queue(this, type).length == 1 )
+					fn.call(this);
+			}
+		});
+	},
+
+	stop: function(clearQueue, gotoEnd){
+		var timers = jQuery.timers;
+
+		if (clearQueue)
+			this.queue([]);
+
+		this.each(function(){
+			// go in reverse order so anything added to the queue during the loop is ignored
+			for ( var i = timers.length - 1; i >= 0; i-- )
+				if ( timers[i].elem == this ) {
+					if (gotoEnd)
+						// force the next step to be the last
+						timers[i](true);
+					timers.splice(i, 1);
+				}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if (!gotoEnd)
+			this.dequeue();
+
+		return this;
+	}
+
+});
+
+var queue = function( elem, type, array ) {
+	if ( elem ){
+
+		type = type || "fx";
+
+		var q = jQuery.data( elem, type + "queue" );
+
+		if ( !q || array )
+			q = jQuery.data( elem, type + "queue", jQuery.makeArray(array) );
+
+	}
+	return q;
+};
+
+jQuery.fn.dequeue = function(type){
+	type = type || "fx";
+
+	return this.each(function(){
+		var q = queue(this, type);
+
+		q.shift();
+
+		if ( q.length )
+			q[0].call( this );
+	});
+};
+
+jQuery.extend({
+
+	speed: function(speed, easing, fn) {
+		var opt = speed && speed.constructor == Object ? speed : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && easing.constructor != Function && easing
+		};
+
+		opt.duration = (opt.duration && opt.duration.constructor == Number ?
+			opt.duration :
+			jQuery.fx.speeds[opt.duration]) || jQuery.fx.speeds.def;
+
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function(){
+			if ( opt.queue !== false )
+				jQuery(this).dequeue();
+			if ( jQuery.isFunction( opt.old ) )
+				opt.old.call( this );
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+
+	timers: [],
+	timerId: null,
+
+	fx: function( elem, options, prop ){
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		if ( !options.orig )
+			options.orig = {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+
+	// Simple function for setting a style value
+	update: function(){
+		if ( this.options.step )
+			this.options.step.call( this.elem, this.now, this );
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+		// Set display property to block for height/width animations
+		if ( this.prop == "height" || this.prop == "width" )
+			this.elem.style.display = "block";
+	},
+
+	// Get the current size
+	cur: function(force){
+		if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+			return this.elem[ this.prop ];
+
+		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+	},
+
+	// Start an animation from one number to another
+	custom: function(from, to, unit){
+		this.startTime = now();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || "px";
+		this.now = this.start;
+		this.pos = this.state = 0;
+		this.update();
+
+		var self = this;
+		function t(gotoEnd){
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		jQuery.timers.push(t);
+
+		if ( jQuery.timerId == null ) {
+			jQuery.timerId = setInterval(function(){
+				var timers = jQuery.timers;
+
+				for ( var i = 0; i < timers.length; i++ )
+					if ( !timers[i]() )
+						timers.splice(i--, 1);
+
+				if ( !timers.length ) {
+					clearInterval( jQuery.timerId );
+					jQuery.timerId = null;
+				}
+			}, 13);
+		}
+	},
+
+	// Simple 'show' function
+	show: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		this.custom(0, this.cur());
+
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		if ( this.prop == "width" || this.prop == "height" )
+			this.elem.style[this.prop] = "1px";
+
+		// Start by showing the element
+		jQuery(this.elem).show();
+	},
+
+	// Simple 'hide' function
+	hide: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function(gotoEnd){
+		var t = now();
+
+		if ( gotoEnd || t > this.options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			this.options.curAnim[ this.prop ] = true;
+
+			var done = true;
+			for ( var i in this.options.curAnim )
+				if ( this.options.curAnim[i] !== true )
+					done = false;
+
+			if ( done ) {
+				if ( this.options.display != null ) {
+					// Reset the overflow
+					this.elem.style.overflow = this.options.overflow;
+
+					// Reset the display
+					this.elem.style.display = this.options.display;
+					if ( jQuery.css(this.elem, "display") == "none" )
+						this.elem.style.display = "block";
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( this.options.hide )
+					this.elem.style.display = "none";
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( this.options.hide || this.options.show )
+					for ( var p in this.options.curAnim )
+						jQuery.attr(this.elem.style, p, this.options.orig[p]);
+			}
+
+			if ( done )
+				// Execute the complete function
+				this.options.complete.call( this.elem );
+
+			return false;
+		} else {
+			var n = t - this.startTime;
+			this.state = n / this.options.duration;
+
+			// Perform the easing function, defaults to swing
+			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+			this.now = this.start + ((this.end - this.start) * this.pos);
+
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+
+};
+
+jQuery.extend( jQuery.fx, {
+	speeds:{
+		slow: 600,
+ 		fast: 200,
+ 		// Default speed
+ 		def: 400
+	},
+	step: {
+		scrollLeft: function(fx){
+			fx.elem.scrollLeft = fx.now;
+		},
+
+		scrollTop: function(fx){
+			fx.elem.scrollTop = fx.now;
+		},
+
+		opacity: function(fx){
+			jQuery.attr(fx.elem.style, "opacity", fx.now);
+		},
+
+		_default: function(fx){
+			fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+		}
+	}
+});
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+	var left = 0, top = 0, elem = this[0], results;
+
+	if ( elem ) with ( jQuery.browser ) {
+		var parent       = elem.parentNode,
+		    offsetChild  = elem,
+		    offsetParent = elem.offsetParent,
+		    doc          = elem.ownerDocument,
+		    safari2      = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
+		    css          = jQuery.curCSS,
+		    fixed        = css(elem, "position") == "fixed";
+
+		// Use getBoundingClientRect if available
+		if ( !(mozilla && elem == document.body) && elem.getBoundingClientRect ) {
+			var box = elem.getBoundingClientRect();
+
+			// Add the document scroll offsets
+			add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+				box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+
+			// IE adds the HTML element's border, by default it is medium which is 2px
+			// IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+			// IE 7 standards mode, the border is always 2px
+			// This border/offset is typically represented by the clientLeft and clientTop properties
+			// However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+			// Therefore this method will be off by 2px in IE while in quirksmode
+			add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+
+		// Otherwise loop through the offsetParents and parentNodes
+		} else {
+
+			// Initial element offsets
+			add( elem.offsetLeft, elem.offsetTop );
+
+			// Get parent offsets
+			while ( offsetParent ) {
+				// Add offsetParent offsets
+				add( offsetParent.offsetLeft, offsetParent.offsetTop );
+
+				// Mozilla and Safari > 2 does not include the border on offset parents
+				// However Mozilla adds the border for table or table cells
+				if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+					border( offsetParent );
+
+				// Add the document scroll offsets if position is fixed on any offsetParent
+				if ( !fixed && css(offsetParent, "position") == "fixed" )
+					fixed = true;
+
+				// Set offsetChild to previous offsetParent unless it is the body element
+				offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+				// Get next offsetParent
+				offsetParent = offsetParent.offsetParent;
+			}
+
+			// Get parent scroll offsets
+			while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+				// Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+				if ( !/^inline|table.*$/i.test(css(parent, "display")) )
+					// Subtract parent scroll offsets
+					add( -parent.scrollLeft, -parent.scrollTop );
+
+				// Mozilla does not add the border for a parent that has overflow != visible
+				if ( mozilla && css(parent, "overflow") != "visible" )
+					border( parent );
+
+				// Get next parent
+				parent = parent.parentNode;
+			}
+
+			// Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+			// Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+			if ( (safari2 && (fixed || css(offsetChild, "position") == "absolute")) ||
+				(mozilla && css(offsetChild, "position") != "absolute") )
+					add( -doc.body.offsetLeft, -doc.body.offsetTop );
+
+			// Add the document scroll offsets if position is fixed
+			if ( fixed )
+				add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+					Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		}
+
+		// Return an object with top and left properties
+		results = { top: top, left: left };
+	}
+
+	function border(elem) {
+		add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
+	}
+
+	function add(l, t) {
+		left += parseInt(l, 10) || 0;
+		top += parseInt(t, 10) || 0;
+	}
+
+	return results;
+};
+
+
+jQuery.fn.extend({
+	position: function() {
+		var left = 0, top = 0, results;
+
+		if ( this[0] ) {
+			// Get *real* offsetParent
+			var offsetParent = this.offsetParent(),
+
+			// Get correct offsets
+			offset       = this.offset(),
+			parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+			// Subtract element margins
+			// note: when an element has margin: auto the offsetLeft and marginLeft 
+			// are the same in Safari causing offset.left to incorrectly be 0
+			offset.top  -= num( this, 'marginTop' );
+			offset.left -= num( this, 'marginLeft' );
+
+			// Add offsetParent borders
+			parentOffset.top  += num( offsetParent, 'borderTopWidth' );
+			parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+			// Subtract the two offsets
+			results = {
+				top:  offset.top  - parentOffset.top,
+				left: offset.left - parentOffset.left
+			};
+		}
+
+		return results;
+	},
+
+	offsetParent: function() {
+		var offsetParent = this[0].offsetParent;
+		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+			offsetParent = offsetParent.offsetParent;
+		return jQuery(offsetParent);
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+	var method = 'scroll' + name;
+	
+	jQuery.fn[ method ] = function(val) {
+		if (!this[0]) return;
+
+		return val != undefined ?
+
+			// Set the scroll offset
+			this.each(function() {
+				this == window || this == document ?
+					window.scrollTo(
+						!i ? val : jQuery(window).scrollLeft(),
+						 i ? val : jQuery(window).scrollTop()
+					) :
+					this[ method ] = val;
+			}) :
+
+			// Return the scroll offset
+			this[0] == window || this[0] == document ?
+				self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+					jQuery.boxModel && document.documentElement[ method ] ||
+					document.body[ method ] :
+				this[0][ method ];
+	};
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+	var tl = i ? "Left"  : "Top",  // top or left
+		br = i ? "Right" : "Bottom"; // bottom or right
+
+	// innerHeight and innerWidth
+	jQuery.fn["inner" + name] = function(){
+		return this[ name.toLowerCase() ]() +
+			num(this, "padding" + tl) +
+			num(this, "padding" + br);
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn["outer" + name] = function(margin) {
+		return this["inner" + name]() +
+			num(this, "border" + tl + "Width") +
+			num(this, "border" + br + "Width") +
+			(margin ?
+				num(this, "margin" + tl) + num(this, "margin" + br) : 0);
+	};
+
+});})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/templates/django_extensions/widgets/foreignkey_searchinput.html	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,60 @@
+{% load i18n %}
+<input type="text" id="lookup_{{ name }}" value="{{ label }}" style="display:none;" />
+<a href="{{ related_url }}{{ url }}" class="related-lookup" id="lookup_id_{{ name }}" onclick="return showRelatedObjectLookupPopup(this);">
+  <img src="{{ admin_media_prefix }}img/admin/selector-search.gif" width="16" height="16" alt="{% trans "Lookup" %}" />
+</a>
+<script type="text/javascript">
+$(document).ready(function() {
+    // Show lookup input
+    $("#lookup_{{ name }}").show();
+    function reset() {
+        $('#id_{{ name }}').val('');
+        $('#lookup_{{ name }}').val('');
+    };
+    function lookup(query) {
+        $.get('{{ search_path }}', {
+            'search_fields': '{{ search_fields }}',
+            'app_label': '{{ app_label }}',
+            'model_name': '{{ model_name }}',
+            'object_pk': query,
+        }, function(data){
+            $('#lookup_{{ name }}').val(data);
+            {{ name }}_value = query;
+        });
+    };
+    $('#id_{{ name }}').bind(($.browser.opera ? "keypress" : "keyup"), function(event) {
+        if ($(this).val()) {
+            if (event.keyCode == 27) {
+                reset();
+            } else {
+                lookup($(this).val());
+            };
+        };
+    });
+    $('#lookup_{{ name }}').autocomplete('{{ search_path }}', {
+        extraParams: {
+            'search_fields': '{{ search_fields }}',
+            'app_label': '{{ app_label }}',
+            'model_name': '{{ model_name }}',
+        },
+    }).result(function(event, data, formatted) {
+        if (data) {
+            $('#id_{{ name }}').val(data[1]);
+        }
+    }).keyup(function(event){
+        if (event.keyCode == 27) {
+            reset();
+        };
+    });
+    var {{ name }}_value = $('#id_{{ name }}').val();
+    function check() {
+        {{ name }}_check = $('#id_{{ name }}').val();
+        if ({{ name }}_check) {
+            if ({{ name }}_check != {{ name }}_value) {
+                lookup({{ name }}_check);
+            }
+        }
+    }
+    timeout = window.setInterval(check, 300);
+});
+</script>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/templatetags/syntax_color.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,77 @@
+r"""
+Template filter for rendering a string with syntax highlighting.
+It relies on Pygments to accomplish this.
+
+Some standard usage examples (from within Django templates).
+Coloring a string with the Python lexer:
+
+    {% load syntax_color %}
+    {{ code_string|colorize:"python" }}
+
+You may use any lexer in Pygments. The complete list of which
+can be found [on the Pygments website][1].
+
+[1]: http://pygments.org/docs/lexers/
+
+You may also have Pygments attempt to guess the correct lexer for
+a particular string. However, if may not be able to choose a lexer,
+in which case it will simply return the string unmodified. This is
+less efficient compared to specifying the lexer to use.
+
+    {{ code_string|colorize }}
+
+You may also render the syntax highlighed text with line numbers.
+
+    {% load syntax_color %}
+    {{ some_code|colorize_table:"html+django" }}
+    {{ let_pygments_pick_for_this_code|colorize_table }}
+
+Please note that before you can load the ``syntax_color`` template filters
+you will need to add the ``django_extensions.utils`` application to the
+``INSTALLED_APPS``setting in your project's ``settings.py`` file.
+"""
+
+__author__ = 'Will Larson <lethain@gmail.com>'
+
+
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.utils.safestring import mark_safe
+from pygments import highlight
+from pygments.formatters import HtmlFormatter
+from pygments.lexers import get_lexer_by_name,guess_lexer,ClassNotFound
+
+register = template.Library()
+
+def generate_pygments_css(path=None):
+    if path is None:
+        import os
+        path = os.path.join(os.getcwd(),'pygments.css')
+    f = open(path,'w')
+    f.write(HtmlFormatter().get_style_defs('.highlight'))
+    f.close()
+
+
+def get_lexer(value,arg):
+    if arg is None:
+        return guess_lexer(value)
+    return get_lexer_by_name(arg)
+
+@register.filter(name='colorize')
+@stringfilter
+def colorize(value, arg=None):
+    try:
+        return mark_safe(highlight(value,get_lexer(value,arg),HtmlFormatter()))
+    except ClassNotFound:
+        return value
+
+
+@register.filter(name='colorize_table')
+@stringfilter
+def colorize_table(value,arg=None):
+    try:
+        return mark_safe(highlight(value,get_lexer(value,arg),HtmlFormatter(linenos='table')))
+    except ClassNotFound:
+        return value
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/templatetags/truncate_letters.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,21 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+
+register = template.Library()
+
+def truncateletters(value, arg):
+    """
+    Truncates a string after a certain number of letters
+    
+    Argument: Number of letters to truncate after
+    """
+    from django_extensions.utils.text import truncate_letters
+    try:
+        length = int(arg)
+    except ValueError: # invalid literal for int()
+        return value # Fail silently
+    return truncate_letters(value, length)
+
+truncateletters.is_safe = True
+truncateletters = stringfilter(truncateletters)
+register.filter(truncateletters)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/utils/text.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,13 @@
+from django.utils.encoding import force_unicode
+from django.utils.functional import allow_lazy
+
+def truncate_letters(s, num):
+    """ truncates a string to a number of letters, similar to truncate_words """
+    s = force_unicode(s)
+    length = int(num)
+    if len(s)>length:
+        s = s[:length]
+    if not s.endswith('...'):
+        s += '...'
+    return s
+truncate_letters = allow_lazy(truncate_letters, unicode)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django_extensions/utils/uuid.py	Wed Jan 20 12:37:40 2010 +0100
@@ -0,0 +1,541 @@
+r"""UUID objects (universally unique identifiers) according to RFC 4122.
+
+This module provides immutable UUID objects (class UUID) and the functions
+uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5
+UUIDs as specified in RFC 4122.
+
+If all you want is a unique ID, you should probably call uuid1() or uuid4().
+Note that uuid1() may compromise privacy since it creates a UUID containing
+the computer's network address.  uuid4() creates a random UUID.
+
+Typical usage:
+
+    >>> import uuid
+
+    # make a UUID based on the host ID and current time
+    >>> uuid.uuid1()
+    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
+
+    # make a UUID using an MD5 hash of a namespace UUID and a name
+    >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
+    UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')
+
+    # make a random UUID
+    >>> uuid.uuid4()
+    UUID('16fd2706-8baf-433b-82eb-8c7fada847da')
+
+    # make a UUID using a SHA-1 hash of a namespace UUID and a name
+    >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
+    UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')
+
+    # make a UUID from a string of hex digits (braces and hyphens ignored)
+    >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')
+
+    # convert a UUID to a string of hex digits in standard form
+    >>> str(x)
+    '00010203-0405-0607-0809-0a0b0c0d0e0f'
+
+    # get the raw 16 bytes of the UUID
+    >>> x.bytes
+    '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
+
+    # make a UUID from a 16-byte string
+    >>> uuid.UUID(bytes=x.bytes)
+    UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
+"""
+
+__author__ = 'Ka-Ping Yee <ping@zesty.ca>'
+
+RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
+    'reserved for NCS compatibility', 'specified in RFC 4122',
+    'reserved for Microsoft compatibility', 'reserved for future definition']
+
+class UUID(object):
+    """Instances of the UUID class represent UUIDs as specified in RFC 4122.
+    UUID objects are immutable, hashable, and usable as dictionary keys.
+    Converting a UUID to a string with str() yields something in the form
+    '12345678-1234-1234-1234-123456789abc'.  The UUID constructor accepts
+    five possible forms: a similar string of hexadecimal digits, or a tuple
+    of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and
+    48-bit values respectively) as an argument named 'fields', or a string
+    of 16 bytes (with all the integer fields in big-endian order) as an
+    argument named 'bytes', or a string of 16 bytes (with the first three
+    fields in little-endian order) as an argument named 'bytes_le', or a
+    single 128-bit integer as an argument named 'int'.
+
+    UUIDs have these read-only attributes:
+
+        bytes       the UUID as a 16-byte string (containing the six
+                    integer fields in big-endian byte order)
+
+        bytes_le    the UUID as a 16-byte string (with time_low, time_mid,
+                    and time_hi_version in little-endian byte order)
+
+        fields      a tuple of the six integer fields of the UUID,
+                    which are also available as six individual attributes
+                    and two derived attributes:
+
+            time_low                the first 32 bits of the UUID
+            time_mid                the next 16 bits of the UUID
+            time_hi_version         the next 16 bits of the UUID
+            clock_seq_hi_variant    the next 8 bits of the UUID
+            clock_seq_low           the next 8 bits of the UUID
+            node                    the last 48 bits of the UUID
+
+            time                    the 60-bit timestamp
+            clock_seq               the 14-bit sequence number
+
+        hex         the UUID as a 32-character hexadecimal string
+
+        int         the UUID as a 128-bit integer
+
+        urn         the UUID as a URN as specified in RFC 4122
+
+        variant     the UUID variant (one of the constants RESERVED_NCS,
+                    RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE)
+
+        version     the UUID version number (1 through 5, meaningful only
+                    when the variant is RFC_4122)
+    """
+
+    def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
+                       int=None, version=None):
+        r"""Create a UUID from either a string of 32 hexadecimal digits,
+        a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
+        in little-endian order as the 'bytes_le' argument, a tuple of six
+        integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
+        8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
+        the 'fields' argument, or a single 128-bit integer as the 'int'
+        argument.  When a string of hex digits is given, curly braces,
+        hyphens, and a URN prefix are all optional.  For example, these
+        expressions all yield the same UUID:
+
+        UUID('{12345678-1234-5678-1234-567812345678}')
+        UUID('12345678123456781234567812345678')
+        UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
+        UUID(bytes='\x12\x34\x56\x78'*4)
+        UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
+                      '\x12\x34\x56\x78\x12\x34\x56\x78')
+        UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
+        UUID(int=0x12345678123456781234567812345678)
+
+        Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
+        be given.  The 'version' argument is optional; if given, the resulting
+        UUID will have its variant and version set according to RFC 4122,
+        overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
+        """
+
+        if [hex, bytes, bytes_le, fields, int].count(None) != 4:
+            raise TypeError('need one of hex, bytes, bytes_le, fields, or int')
+        if hex is not None:
+            hex = hex.replace('urn:', '').replace('uuid:', '')
+            hex = hex.strip('{}').replace('-', '')
+            if len(hex) != 32:
+                raise ValueError('badly formed hexadecimal UUID string')
+            int = long(hex, 16)
+        if bytes_le is not None:
+            if len(bytes_le) != 16:
+                raise ValueError('bytes_le is not a 16-char string')
+            bytes = (bytes_le[3] + bytes_le[2] + bytes_le[1] + bytes_le[0] +
+                     bytes_le[5] + bytes_le[4] + bytes_le[7] + bytes_le[6] +
+                     bytes_le[8:])
+        if bytes is not None:
+            if len(bytes) != 16:
+                raise ValueError('bytes is not a 16-char string')
+            int = long(('%02x'*16) % tuple(map(ord, bytes)), 16)
+        if fields is not None:
+            if len(fields) != 6:
+                raise ValueError('fields is not a 6-tuple')
+            (time_low, time_mid, time_hi_version,
+             clock_seq_hi_variant, clock_seq_low, node) = fields
+            if not 0 <= time_low < 1<<32L:
+                raise ValueError('field 1 out of range (need a 32-bit value)')
+            if not 0 <= time_mid < 1<<16L:
+                raise ValueError('field 2 out of range (need a 16-bit value)')
+            if not 0 <= time_hi_version < 1<<16L:
+                raise ValueError('field 3 out of range (need a 16-bit value)')
+            if not 0 <= clock_seq_hi_variant < 1<<8L:
+                raise ValueError('field 4 out of range (need an 8-bit value)')
+            if not 0 <= clock_seq_low < 1<<8L:
+                raise ValueError('field 5 out of range (need an 8-bit value)')
+            if not 0 <= node < 1<<48L:
+                raise ValueError('field 6 out of range (need a 48-bit value)')
+            clock_seq = (clock_seq_hi_variant << 8L) | clock_seq_low
+            int = ((time_low << 96L) | (time_mid << 80L) |
+                   (time_hi_version << 64L) | (clock_seq << 48L) | node)
+        if int is not None:
+            if not 0 <= int < 1<<128L:
+                raise ValueError('int is out of range (need a 128-bit value)')
+        if version is not None:
+            if not 1 <= version <= 5:
+                raise ValueError('illegal version number')
+            # Set the variant to RFC 4122.
+            int &= ~(0xc000 << 48L)
+            int |= 0x8000 << 48L
+            # Set the version number.
+            int &= ~(0xf000 << 64L)
+            int |= version << 76L
+        self.__dict__['int'] = int
+
+    def __cmp__(self, other):
+        if isinstance(other, UUID):
+            return cmp(self.int, other.int)
+        return NotImplemented
+
+    def __hash__(self):
+        return hash(self.int)
+
+    def __int__(self):
+        return self.int
+
+    def __repr__(self):
+        return 'UUID(%r)' % str(self)
+
+    def __setattr__(self, name, value):
+        raise TypeError('UUID objects are immutable')
+
+    def __str__(self):
+        hex = '%032x' % self.int
+        return '%s-%s-%s-%s-%s' % (
+            hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:])
+
+    def get_bytes(self):
+        bytes = ''
+        for shift in range(0, 128, 8):
+            bytes = chr((self.int >> shift) & 0xff) + bytes
+        return bytes
+
+    bytes = property(get_bytes)
+
+    def get_bytes_le(self):
+        bytes = self.bytes
+        return (bytes[3] + bytes[2] + bytes[1] + bytes[0] +
+                bytes[5] + bytes[4] + bytes[7] + bytes[6] + bytes[8:])
+
+    bytes_le = property(get_bytes_le)
+
+    def get_fields(self):
+        return (self.time_low, self.time_mid, self.time_hi_version,
+                self.clock_seq_hi_variant, self.clock_seq_low, self.node)
+
+    fields = property(get_fields)
+
+    def get_time_low(self):
+        return self.int >> 96L
+
+    time_low = property(get_time_low)
+
+    def get_time_mid(self):
+        return (self.int >> 80L) & 0xffff
+
+    time_mid = property(get_time_mid)
+
+    def get_time_hi_version(self):
+        return (self.int >> 64L) & 0xffff
+
+    time_hi_version = property(get_time_hi_version)
+
+    def get_clock_seq_hi_variant(self):
+        return (self.int >> 56L) & 0xff
+
+    clock_seq_hi_variant = property(get_clock_seq_hi_variant)
+
+    def get_clock_seq_low(self):
+        return (self.int >> 48L) & 0xff
+
+    clock_seq_low = property(get_clock_seq_low)
+
+    def get_time(self):
+        return (((self.time_hi_version & 0x0fffL) << 48L) |
+                (self.time_mid << 32L) | self.time_low)
+
+    time = property(get_time)
+
+    def get_clock_seq(self):
+        return (((self.clock_seq_hi_variant & 0x3fL) << 8L) |
+                self.clock_seq_low)
+
+    clock_seq = property(get_clock_seq)
+
+    def get_node(self):
+        return self.int & 0xffffffffffff
+
+    node = property(get_node)
+
+    def get_hex(self):
+        return '%032x' % self.int
+
+    hex = property(get_hex)
+
+    def get_urn(self):
+        return 'urn:uuid:' + str(self)
+
+    urn = property(get_urn)
+
+    def get_variant(self):
+        if not self.int & (0x8000 << 48L):
+            return RESERVED_NCS
+        elif not self.int & (0x4000 << 48L):
+            return RFC_4122
+        elif not self.int & (0x2000 << 48L):
+            return RESERVED_MICROSOFT
+        else:
+            return RESERVED_FUTURE
+
+    variant = property(get_variant)
+
+    def get_version(self):
+        # The version bits are only meaningful for RFC 4122 UUIDs.
+        if self.variant == RFC_4122:
+            return int((self.int >> 76L) & 0xf)
+
+    version = property(get_version)
+
+def _find_mac(command, args, hw_identifiers, get_index):
+    import os
+    for dir in ['', '/sbin/', '/usr/sbin']:
+        executable = os.path.join(dir, command)
+        if not os.path.exists(executable):
+            continue
+
+        try:
+            # LC_ALL to get English output, 2>/dev/null to
+            # prevent output on stderr
+            cmd = 'LC_ALL=C %s %s 2>/dev/null' % (executable, args)
+            pipe = os.popen(cmd)
+        except IOError:
+            continue
+
+        for line in pipe:
+            words = line.lower().split()
+            for i in range(len(words)):
+                if words[i] in hw_identifiers:
+                    return int(words[get_index(i)].replace(':', ''), 16)
+    return None
+
+def _ifconfig_getnode():
+    """Get the hardware address on Unix by running ifconfig."""
+
+    # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes.
+    for args in ('', '-a', '-av'):
+        mac = _find_mac('ifconfig', args, ['hwaddr', 'ether'], lambda i: i+1)
+        if mac:
+            return mac
+
+    import socket
+    ip_addr = socket.gethostbyname(socket.gethostname())
+
+    # Try getting the MAC addr from arp based on our IP address (Solaris).
+    mac = _find_mac('arp', '-an', [ip_addr], lambda i: -1)
+    if mac:
+        return mac
+
+    # This might work on HP-UX.
+    mac = _find_mac('lanscan', '-ai', ['lan0'], lambda i: 0)
+    if mac:
+        return mac
+
+    return None
+
+def _ipconfig_getnode():
+    """Get the hardware address on Windows by running ipconfig.exe."""
+    import os, re
+    dirs = ['', r'c:\windows\system32', r'c:\winnt\system32']
+    try:
+        import ctypes
+        buffer = ctypes.create_string_buffer(300)
+        ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300)
+        dirs.insert(0, buffer.value.decode('mbcs'))
+    except:
+        pass
+    for dir in dirs:
+        try:
+            pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all')
+        except IOError:
+            continue
+        for line in pipe:
+            value = line.split(':')[-1].strip().lower()
+            if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value):
+                return int(value.replace('-', ''), 16)
+
+def _netbios_getnode():
+    """Get the hardware address on Windows using NetBIOS calls.
+    See http://support.microsoft.com/kb/118623 for details."""
+    import win32wnet, netbios
+    ncb = netbios.NCB()
+    ncb.Command = netbios.NCBENUM
+    ncb.Buffer = adapters = netbios.LANA_ENUM()
+    adapters._pack()
+    if win32wnet.Netbios(ncb) != 0:
+        return
+    adapters._unpack()
+    for i in range(adapters.length):
+        ncb.Reset()
+        ncb.Command = netbios.NCBRESET
+        ncb.Lana_num = ord(adapters.lana[i])
+        if win32wnet.Netbios(ncb) != 0:
+            continue
+        ncb.Reset()
+        ncb.Command = netbios.NCBASTAT
+        ncb.Lana_num = ord(adapters.lana[i])
+        ncb.Callname = '*'.ljust(16)
+        ncb.Buffer = status = netbios.ADAPTER_STATUS()
+        if win32wnet.Netbios(ncb) != 0:
+            continue
+        status._unpack()
+        bytes = map(ord, status.adapter_address)
+        return ((bytes[0]<<40L) + (bytes[1]<<32L) + (bytes[2]<<24L) +
+                (bytes[3]<<16L) + (bytes[4]<<8L) + bytes[5])
+
+# Thanks to Thomas Heller for ctypes and for his help with its use here.
+
+# If ctypes is available, use it to find system routines for UUID generation.
+_uuid_generate_random = _uuid_generate_time = _UuidCreate = None
+try:
+    import ctypes, ctypes.util
+    _buffer = ctypes.create_string_buffer(16)
+
+    # The uuid_generate_* routines are provided by libuuid on at least
+    # Linux and FreeBSD, and provided by libc on Mac OS X.
+    for libname in ['uuid', 'c']:
+        try:
+            lib = ctypes.CDLL(ctypes.util.find_library(libname))
+        except:
+            continue
+        if hasattr(lib, 'uuid_generate_random'):
+            _uuid_generate_random = lib.uuid_generate_random
+        if hasattr(lib, 'uuid_generate_time'):
+            _uuid_generate_time = lib.uuid_generate_time
+
+    # On Windows prior to 2000, UuidCreate gives a UUID containing the
+    # hardware address.  On Windows 2000 and later, UuidCreate makes a
+    # random UUID and UuidCreateSequential gives a UUID containing the
+    # hardware address.  These routines are provided by the RPC runtime.
+    # NOTE:  at least on Tim's WinXP Pro SP2 desktop box, while the last
+    # 6 bytes returned by UuidCreateSequential are fixed, they don't appear
+    # to bear any relationship to the MAC address of any network device
+    # on the box.
+    try:
+        lib = ctypes.windll.rpcrt4
+    except:
+        lib = None
+    _UuidCreate = getattr(lib, 'UuidCreateSequential',
+                          getattr(lib, 'UuidCreate', None))
+except:
+    pass
+
+def _unixdll_getnode():
+    """Get the hardware address on Unix using ctypes."""
+    _uuid_generate_time(_buffer)
+    return UUID(bytes=_buffer.raw).node
+
+def _windll_getnode():
+    """Get the hardware address on Windows using ctypes."""
+    if _UuidCreate(_buffer) == 0:
+        return UUID(bytes=_buffer.raw).node
+
+def _random_getnode():
+    """Get a random node ID, with eighth bit set as suggested by RFC 4122."""
+    import random
+    return random.randrange(0, 1<<48L) | 0x010000000000L
+
+_node = None
+
+def getnode():
+    """Get the hardware address as a 48-bit positive integer.
+
+    The first time this runs, it may launch a separate program, which could
+    be quite slow.  If all attempts to obtain the hardware address fail, we
+    choose a random 48-bit number with its eighth bit set to 1 as recommended
+    in RFC 4122.
+    """
+
+    global _node
+    if _node is not None:
+        return _node
+
+    import sys
+    if sys.platform == 'win32':
+        getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode]
+    else:
+        getters = [_unixdll_getnode, _ifconfig_getnode]
+
+    for getter in getters + [_random_getnode]:
+        try:
+            _node = getter()
+        except:
+            continue
+        if _node is not None:
+            return _node
+
+_last_timestamp = None
+
+def uuid1(node=None, clock_seq=None):
+    """Generate a UUID from a host ID, sequence number, and the current time.
+    If 'node' is not given, getnode() is used to obtain the hardware
+    address.  If 'clock_seq' is given, it is used as the sequence number;
+    otherwise a random 14-bit sequence number is chosen."""
+
+    # When the system provides a version-1 UUID generator, use it (but don't
+    # use UuidCreate here because its UUIDs don't conform to RFC 4122).
+    if _uuid_generate_time and node is clock_seq is None:
+        _uuid_generate_time(_buffer)
+        return UUID(bytes=_buffer.raw)
+
+    global _last_timestamp
+    import time
+    nanoseconds = int(time.time() * 1e9)
+    # 0x01b21dd213814000 is the number of 100-ns intervals between the
+    # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
+    timestamp = int(nanoseconds/100) + 0x01b21dd213814000L
+    if timestamp <= _last_timestamp:
+        timestamp = _last_timestamp + 1
+    _last_timestamp = timestamp
+    if clock_seq is None:
+        import random
+        clock_seq = random.randrange(1<<14L) # instead of stable storage
+    time_low = timestamp & 0xffffffffL
+    time_mid = (timestamp >> 32L) & 0xffffL
+    time_hi_version = (timestamp >> 48L) & 0x0fffL
+    clock_seq_low = clock_seq & 0xffL
+    clock_seq_hi_variant = (clock_seq >> 8L) & 0x3fL
+    if node is None:
+        node = getnode()
+    return UUID(fields=(time_low, time_mid, time_hi_version,
+                        clock_seq_hi_variant, clock_seq_low, node), version=1)
+
+def uuid3(namespace, name):
+    """Generate a UUID from the MD5 hash of a namespace UUID and a name."""
+    import md5
+    hash = md5.md5(namespace.bytes + name).digest()
+    return UUID(bytes=hash[:16], version=3)
+
+def uuid4():
+    """Generate a random UUID."""
+
+    # When the system provides a version-4 UUID generator, use it.
+    if _uuid_generate_random:
+        _uuid_generate_random(_buffer)
+        return UUID(bytes=_buffer.raw)
+
+    # Otherwise, get randomness from urandom or the 'random' module.
+    try:
+        import os
+        return UUID(bytes=os.urandom(16), version=4)
+    except:
+        import random
+        bytes = [chr(random.randrange(256)) for i in range(16)]
+        return UUID(bytes=bytes, version=4)
+
+def uuid5(namespace, name):
+    """Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
+    import sha
+    hash = sha.sha(namespace.bytes + name).digest()
+    return UUID(bytes=hash[:16], version=5)
+
+# The following standard UUIDs are for use with uuid3() or uuid5().
+
+NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8')
+NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')
+NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')