diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..983e4eb3f39f1f2300eff631cf0e425686306bfe
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "helios"]
+	path = helios
+	url = git@github.com:benadida/helios-django-app.git
+[submodule "auth"]
+	path = auth
+	url = git@github.com:benadida/auth-django-app.git
diff --git a/appengine_django/__init__.py b/appengine_django/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d1d0d874a582dc443f0aadeb99d5c7653151395
--- /dev/null
+++ b/appengine_django/__init__.py
@@ -0,0 +1,549 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Support for integrating a Django project with the appengine infrastructure.
+
+This requires Django 1.0beta1 or greater.
+
+This module enables you to use the Django manage.py utility and *some* of it's
+subcommands. View the help of manage.py for exact details.
+
+Additionally this module takes care of initialising the datastore (and a test
+datastore) so that the Django test infrastructure can be used for your
+appengine project.
+
+To use this module add the following two lines to your main.py and manage.py
+scripts at the end of your imports:
+  from appengine_django import InstallAppengineHelperForDjango
+  InstallAppengineHelperForDjango()
+
+If you would like to use a version of Django other than that provided by the
+system all you need to do is include it in a directory just above this helper,
+eg:
+  appengine_django/__init__.py        -  This file
+  django/...                          - your private copy of Django.
+"""
+
+import logging
+import os
+import re
+import sys
+import unittest
+import zipfile
+
+
+DIR_PATH = os.path.abspath(os.path.dirname(__file__))
+PARENT_DIR = os.path.dirname(DIR_PATH)
+if PARENT_DIR.endswith(".zip"):
+  # Check for appengine_django itself being in a zipfile.
+  PARENT_DIR = os.path.dirname(PARENT_DIR)
+
+# Add this project to the start of sys path to enable direct imports.
+sys.path = [PARENT_DIR,] + sys.path
+
+# Try to import the appengine code from the system path.
+try:
+  from google.appengine.api import apiproxy_stub_map
+except ImportError, e:
+  # Not on the system path. Build a list of alternative paths where it may be.
+  # First look within the project for a local copy, then look for where the Mac
+  # OS SDK installs it.
+  paths = [os.path.join(PARENT_DIR, '.google_appengine'),
+           os.path.join(PARENT_DIR, 'google_appengine'),
+           '/usr/local/google_appengine']
+  # Then if on windows, look for where the Windows SDK installed it.
+  for path in os.environ.get('PATH', '').split(';'):
+    path = path.rstrip('\\')
+    if path.endswith('google_appengine'):
+      paths.append(path)
+  try:
+    from win32com.shell import shell
+    from win32com.shell import shellcon
+    id_list = shell.SHGetSpecialFolderLocation(
+        0, shellcon.CSIDL_PROGRAM_FILES)
+    program_files = shell.SHGetPathFromIDList(id_list)
+    paths.append(os.path.join(program_files, 'Google',
+                              'google_appengine'))
+  except ImportError, e:
+    # Not windows.
+    pass
+  # Loop through all possible paths and look for the SDK dir.
+  SDK_PATH = None
+  for sdk_path in paths:
+    if os.path.exists(sdk_path):
+      SDK_PATH = os.path.realpath(sdk_path)
+      break
+  if SDK_PATH is None:
+    # The SDK could not be found in any known location.
+    sys.stderr.write("The Google App Engine SDK could not be found!\n")
+    sys.stderr.write("See README for installation instructions.\n")
+    sys.exit(1)
+  if SDK_PATH == os.path.join(PARENT_DIR, 'google_appengine'):
+    logging.warn('Loading the SDK from the \'google_appengine\' subdirectory '
+                 'is now deprecated!')
+    logging.warn('Please move the SDK to a subdirectory named '
+                 '\'.google_appengine\' instead.')
+    logging.warn('See README for further details.')
+  # Add the SDK and the libraries within it to the system path.
+  EXTRA_PATHS = [
+      SDK_PATH,
+      os.path.join(SDK_PATH, 'lib', 'antlr3'),
+      os.path.join(SDK_PATH, 'lib', 'django'),
+      os.path.join(SDK_PATH, 'lib', 'webob'),
+      os.path.join(SDK_PATH, 'lib', 'yaml', 'lib'),
+  ]
+  # Add SDK paths at the start of sys.path, but after the local directory which
+  # was added to the start of sys.path on line 50 above. The local directory
+  # must come first to allow the local imports to override the SDK and
+  # site-packages directories.
+  sys.path = sys.path[0:1] + EXTRA_PATHS + sys.path[1:]
+  from google.appengine.api import apiproxy_stub_map
+
+# Try to import Django 1.0 through App Engine
+try:
+  from google.appengine.dist import use_library
+  use_library('django', '1.0')
+except ImportError:
+  pass
+
+# Look for a zipped copy of Django.
+have_django_zip = False
+django_zip_path = os.path.join(PARENT_DIR, 'django.zip')
+if os.path.exists(django_zip_path):
+  have_django_zip = True
+  sys.path.insert(1, django_zip_path)
+
+# Remove the standard version of Django if a local copy has been provided.
+if have_django_zip or os.path.exists(os.path.join(PARENT_DIR, 'django')):
+  for k in [k for k in sys.modules if k.startswith('django')]:
+    del sys.modules[k]
+
+# Must set this env var *before* importing any more of Django.
+os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+
+from django import VERSION
+from django.conf import settings
+
+from google.appengine.api import yaml_errors
+
+# Flags made available this module
+appid = None
+have_appserver = False
+
+# Hide everything other than the flags above and the install function.
+__all__ = ("appid", "have_appserver", "have_django_zip",
+           "django_zip_path", "InstallAppengineHelperForDjango")
+
+
+INCOMPATIBLE_COMMANDS = ["adminindex", "createcachetable", "dbshell",
+                         "inspectdb", "runfcgi", "syncdb", "validate"]
+
+
+def LoadAppengineEnvironment():
+  """Loads the appengine environment.
+
+  Returns:
+    This function has no return value, but it sets the following parameters on
+    this package:
+    - appid: The name of the application.
+    - have_appserver: Boolean parameter which is True if the code is being run
+        from within the appserver environment.
+  """
+  global appid, have_appserver
+
+  # Detect if we are running under an appserver.
+  have_appserver = False
+  stub = apiproxy_stub_map.apiproxy.GetStub("datastore_v3")
+  if stub:
+    have_appserver = True
+
+  # Load the application identifier.
+  if have_appserver:
+    appid = os.environ.get("APPLICATION_ID", "unknown")
+  else:
+    # Running as manage.py script, read from config file.
+    try:
+      from google.appengine.tools import dev_appserver
+      appconfig, unused_matcher = dev_appserver.LoadAppConfig(PARENT_DIR, {})
+      appid = appconfig.application
+    except (ImportError, yaml_errors.EventListenerYAMLError), e:
+      logging.warn("Could not read the Application ID from app.yaml. "
+                   "This may break things in unusual ways!")
+      # Something went wrong.
+      appid = "unknown"
+
+  logging.debug("Loading application '%s' %s an appserver" %
+                (appid, have_appserver and "with" or "without"))
+
+
+def InstallAppengineDatabaseBackend():
+  """Installs the appengine database backend into Django.
+
+  The appengine database lives in the db/ subdirectory of this package, but is
+  known as "appengine" to Django. This function installs the module where
+  Django expects to find its database backends.
+  """
+  from appengine_django import db
+  sys.modules['django.db.backends.appengine'] = db
+  logging.debug("Installed appengine database backend")
+
+
+def InstallGoogleMemcache():
+  """Installs the Google memcache into Django.
+
+  By default django tries to import standard memcache module.
+  Because appengine memcache is API compatible with Python memcache module,
+  we can trick Django to think it is installed and to use it.
+  
+  Now you can use CACHE_BACKEND = 'memcached://' in settings.py. IP address
+  and port number are not required.
+  """
+  from google.appengine.api import memcache
+  sys.modules['memcache'] = memcache
+  logging.debug("Installed App Engine memcache backend")
+
+
+def InstallDjangoModuleReplacements():
+  """Replaces internal Django modules with App Engine compatible versions."""
+
+  # Replace the session module with a partial replacement overlay using
+  # __path__ so that portions not replaced will fall through to the original
+  # implementation. 
+  try:
+    from django.contrib import sessions
+    orig_path = sessions.__path__[0]
+    sessions.__path__.insert(0, os.path.join(DIR_PATH, 'sessions'))
+    from django.contrib.sessions import backends
+    backends.__path__.append(os.path.join(orig_path, 'backends'))
+  except ImportError:
+    logging.debug("No Django session support available")
+
+  # Replace incompatible dispatchers.
+  import django.core.signals
+  import django.db
+  import django.dispatch.dispatcher
+
+  # Rollback occurs automatically on Google App Engine. Disable the Django
+  # rollback handler.
+  try:
+    # pre 1.0
+    from django.dispatch import errors
+    CheckedException = errors.DispatcherKeyError
+    def _disconnectSignal():
+      django.dispatch.dispatcher.disconnect(
+          django.db._rollback_on_exception,
+          django.core.signals.got_request_exception)
+  except ImportError:
+    CheckedException = KeyError
+    def _disconnectSignal():
+      django.core.signals.got_request_exception.disconnect(
+          django.db._rollback_on_exception)
+
+  try:
+    _disconnectSignal()
+  except CheckedException, e:
+    logging.debug("Django rollback handler appears to be already disabled.")
+
+def PatchDjangoSerializationModules():
+  """Monkey patches the Django serialization modules.
+
+  The standard Django serialization modules to not correctly handle the
+  datastore models provided by this package. This method installs replacements
+  for selected modules and methods to give Django the capability to correctly
+  serialize and deserialize datastore models.
+  """
+  # These can't be imported until InstallAppengineDatabaseBackend has run.
+  from django.core.serializers import python
+  from appengine_django.serializer.python import Deserializer
+  if not hasattr(settings, "SERIALIZATION_MODULES"):
+    settings.SERIALIZATION_MODULES = {}
+  base_module = "appengine_django"
+  settings.SERIALIZATION_MODULES["xml"] = "%s.serializer.xml" % base_module
+  python.Deserializer = Deserializer
+  PatchDeserializedObjectClass()
+  DisableModelValidation()
+  logging.debug("Installed appengine json and python serialization modules")
+
+
+def PatchDeserializedObjectClass():
+  """Patches the DeserializedObject class.
+
+  The default implementation calls save directly on the django Model base
+  class to avoid pre-save handlers. The model class provided by this package
+  is not derived from the Django Model class and therefore must be called
+  directly.
+
+  Additionally we need to clear the internal _parent attribute as it may
+  contain a FakeParent class that is used to deserialize instances without
+  needing to load the parent instance itself. See the PythonDeserializer for
+  more details.
+  """
+  # This can't be imported until InstallAppengineDatabaseBackend has run.
+  from django.core.serializers import base
+  class NewDeserializedObject(base.DeserializedObject):
+    def save(self, save_m2m=True):
+      self.object.save()
+      self.object._parent = None
+  base.DeserializedObject = NewDeserializedObject
+  logging.debug("Replacement DeserializedObject class installed")
+
+def DisableModelValidation():
+  """Disables Django's model validation routines.
+
+  The model validation is primarily concerned with validating foreign key
+  references. There is no equivalent checking code for datastore References at
+  this time.
+
+  Validation needs to be disabled or serialization/deserialization will fail.
+  """
+  from django.core.management import validation
+  validation.get_validation_errors = lambda x, y=0: 0
+  logging.debug("Django SQL model validation disabled")
+
+def CleanupDjangoSettings():
+  """Removes incompatible entries from the django settings module."""
+
+  # Ensure this module is installed as an application.
+  apps = getattr(settings, "INSTALLED_APPS", ())
+  found = False
+  for app in apps:
+    if app.endswith("appengine_django"):
+      found = True
+      break
+  if not found:
+    logging.warn("appengine_django module is not listed as an application!")
+    apps += ("appengine_django",)
+    setattr(settings, "INSTALLED_APPS", apps)
+    logging.info("Added 'appengine_django' as an application")
+
+  # Ensure the database backend is appropriately configured.
+  dbe = getattr(settings, "DATABASE_ENGINE", "")
+  if dbe != "appengine":
+    settings.DATABASE_ENGINE = "appengine"
+    logging.warn("DATABASE_ENGINE is not configured as 'appengine'. "
+                 "Value overriden!")
+  for var in ["NAME", "USER", "PASSWORD", "HOST", "PORT"]:
+    val = getattr(settings, "DATABASE_%s" % var, "")
+    if val:
+      setattr(settings, "DATABASE_%s" % var, "")
+      logging.warn("DATABASE_%s should be blank. Value overriden!")
+
+  # Remove incompatible middleware modules.
+  mw_mods = list(getattr(settings, "MIDDLEWARE_CLASSES", ()))
+  disallowed_middleware_mods = (
+    'django.middleware.doc.XViewMiddleware',)
+  for modname in mw_mods[:]:
+    if modname in disallowed_middleware_mods:
+      # Currently only the CommonMiddleware has been ported.  As other base
+      # modules are converted, remove from the disallowed_middleware_mods
+      # tuple.
+      mw_mods.remove(modname)
+      logging.warn("Middleware module '%s' is not compatible. Removed!" %
+                   modname)
+  setattr(settings, "MIDDLEWARE_CLASSES", tuple(mw_mods))
+
+  # Remove incompatible application modules
+  app_mods = list(getattr(settings, "INSTALLED_APPS", ()))
+  disallowed_apps = (
+    'django.contrib.contenttypes',
+    'django.contrib.sites',)
+  for app in app_mods[:]:
+    if app in disallowed_apps:
+      app_mods.remove(app)
+      logging.warn("Application module '%s' is not compatible. Removed!" % app)
+  setattr(settings, "INSTALLED_APPS", tuple(app_mods))
+
+  # Remove incompatible session backends.
+  session_backend = getattr(settings, "SESSION_ENGINE", "")
+  if session_backend.endswith("file"):
+    logging.warn("File session backend is not compatible. Overriden "
+                 "to use db backend!")
+    setattr(settings, "SESSION_ENGINE", "django.contrib.sessions.backends.db")
+
+
+def ModifyAvailableCommands():
+  """Removes incompatible commands and installs replacements where possible."""
+  if have_appserver:
+    # Commands are not used when running from an appserver.
+    return
+  from django.core import management
+  project_directory = os.path.join(__path__[0], "../")
+  if have_django_zip:
+    FindCommandsInZipfile.orig = management.find_commands
+    management.find_commands = FindCommandsInZipfile
+  management.get_commands()
+  # Replace startapp command which is set by previous call to get_commands().
+  from appengine_django.management.commands.startapp import ProjectCommand
+  management._commands['startapp'] = ProjectCommand(project_directory) 
+  RemoveCommands(management._commands)
+  logging.debug("Removed incompatible Django manage.py commands")
+
+
+def FindCommandsInZipfile(management_dir):
+    """
+    Given a path to a management directory, returns a list of all the command
+    names that are available.
+
+    This implementation also works when Django is loaded from a zip.
+
+    Returns an empty list if no commands are defined.
+    """
+    zip_marker = ".zip%s" % os.sep
+    if zip_marker not in management_dir:
+      return FindCommandsInZipfile.orig(management_dir)
+
+    # Django is sourced from a zipfile, ask zip module for a list of files.
+    filename, path = management_dir.split(zip_marker)
+    zipinfo = zipfile.ZipFile("%s.zip" % filename)
+
+    # Add commands directory to management path.
+    path = os.path.join(path, "commands")
+
+    # The zipfile module returns paths in the format of the operating system
+    # that created the zipfile! This may not match the path to the zipfile
+    # itself. Convert operating system specific characters to a standard
+    # character (#) to compare paths to work around this.
+    path_normalise = re.compile(r"[/\\]")
+    path = path_normalise.sub("#", path)
+    def _IsCmd(t):
+      """Returns true if t matches the criteria for a command module."""
+      filename = os.path.basename(t)
+      t = path_normalise.sub("#", t)
+      if not t.startswith(path):
+        return False
+      if filename.startswith("_") or not t.endswith(".py"):
+        return False
+      return True
+
+    return [os.path.basename(f)[:-3] for f in zipinfo.namelist() if _IsCmd(f)]
+
+
+def RemoveCommands(command_dict):
+  """Removes incompatible commands from the specified command dictionary."""
+  for cmd in command_dict.keys():
+    if cmd.startswith("sql"):
+      del command_dict[cmd]
+    elif cmd in INCOMPATIBLE_COMMANDS:
+      del command_dict[cmd]
+
+
+def InstallReplacementImpModule():
+  """Install a replacement for the imp module removed by the appserver.
+
+  This is only to find mangement modules provided by applications.
+  """
+  if not have_appserver:
+    return
+  modname = 'appengine_django.replacement_imp'
+  imp_mod = __import__(modname, {}, [], [''])
+  sys.modules['imp'] = imp_mod
+  logging.debug("Installed replacement imp module")
+
+
+def InstallAppengineHelperForDjango():
+  """Installs and Patches all of the classes/methods required for integration.
+
+  If the variable DEBUG_APPENGINE_DJANGO is set in the environment verbose
+  logging of the actions taken will be enabled.
+  """
+  # Adding this again here to solve a problem that happens when context
+  # switching from webapp.template to django.template.
+  # TODO(elsigh): Maybe there is a deeper, fixable problem somewhere?
+  os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+
+  if VERSION < (1, 0, None):
+    logging.error("Django 1.0 or greater is required!")
+    sys.exit(1)
+
+  if os.getenv("DEBUG_APPENGINE_DJANGO"):
+    logging.getLogger().setLevel(logging.DEBUG)
+  else:
+    logging.getLogger().setLevel(logging.INFO)
+  logging.debug("Loading the Google App Engine Helper for Django...")
+
+  # Force Django to reload its settings.
+  settings._target = None
+
+  LoadAppengineEnvironment()
+  InstallReplacementImpModule()
+  InstallAppengineDatabaseBackend()
+  InstallModelForm()
+  InstallGoogleMemcache()
+  InstallDjangoModuleReplacements()
+  PatchDjangoSerializationModules()
+  CleanupDjangoSettings()
+  ModifyAvailableCommands()
+  InstallGoogleSMTPConnection()
+  InstallAuthentication()
+
+  logging.debug("Successfully loaded the Google App Engine Helper for Django.")
+
+
+def InstallGoogleSMTPConnection():
+  from appengine_django import mail as gmail
+  from django.core import mail
+  logging.debug("Installing Google Email Adapter for Django")
+  mail.SMTPConnection = gmail.GoogleSMTPConnection
+  mail.mail_admins = gmail.mail_admins
+  mail.mail_managers = gmail.mail_managers
+
+
+def InstallAuthentication():
+  if "django.contrib.auth" not in settings.INSTALLED_APPS:
+    return
+  try:
+    from appengine_django.auth import models as helper_models
+    from django.contrib.auth import models
+    models.User = helper_models.User
+    models.Group = helper_models.Group
+    models.Permission = helper_models.Permission
+    models.Message = helper_models.Message
+    from django.contrib.auth import middleware as django_middleware
+    from appengine_django.auth.middleware import AuthenticationMiddleware
+    django_middleware.AuthenticationMiddleware = AuthenticationMiddleware
+    from django.contrib.auth import decorators as django_decorators
+    from appengine_django.auth.decorators import login_required
+    django_decorators.login_required = login_required
+    from django.contrib import auth as django_auth
+    from django.contrib.auth import tests as django_tests
+    django_auth.suite = unittest.TestSuite
+    django_tests.suite = unittest.TestSuite
+    logging.debug("Installing authentication framework")
+  except ImportError:
+    logging.debug("No Django authentication support available")
+
+
+def InstallModelForm():
+  """Replace Django ModelForm with the AppEngine ModelForm."""
+  # This MUST happen as early as possible, but after any auth model patching.
+  from google.appengine.ext.db import djangoforms as aeforms
+  try:
+    # pre 1.0
+    from django import newforms as forms
+  except ImportError:
+    from django import forms
+
+  forms.ModelForm = aeforms.ModelForm
+
+  # Extend ModelForm with support for EmailProperty
+  # TODO: This should be submitted to the main App Engine SDK.
+  from google.appengine.ext.db import EmailProperty
+  def get_form_field(self, **kwargs):
+    """Return a Django form field appropriate for an email property."""
+    defaults = {'form_class': forms.EmailField}
+    defaults.update(kwargs)
+    return super(EmailProperty, self).get_form_field(**defaults)
+  EmailProperty.get_form_field = get_form_field
diff --git a/appengine_django/auth/__init__.py b/appengine_django/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2db207407af006d1609defe407a7efdb1c99e2f
--- /dev/null
+++ b/appengine_django/auth/__init__.py
@@ -0,0 +1,25 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Authentication module that mimics the behavior of Django's authentication
+implementation.
+
+Limitations:
+ - all user permissions methods are not available (requires contenttypes)
+"""
+
+from django.template import add_to_builtins
+
+add_to_builtins('appengine_django.auth.templatetags')
diff --git a/appengine_django/auth/decorators.py b/appengine_django/auth/decorators.py
new file mode 100644
index 0000000000000000000000000000000000000000..d897c24713068fb70041c94d537f07fb1ce8a086
--- /dev/null
+++ b/appengine_django/auth/decorators.py
@@ -0,0 +1,31 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Decorators for the authentication framework."""
+
+from django.http import HttpResponseRedirect
+
+from google.appengine.api import users
+
+
+def login_required(function):
+  """Implementation of Django's login_required decorator.
+  
+  The login redirect URL is always set to request.path
+  """
+  def login_required_wrapper(request, *args, **kw):
+    if request.user.is_authenticated():
+      return function(request, *args, **kw)
+    return HttpResponseRedirect(users.create_login_url(request.path))
+  return login_required_wrapper
diff --git a/appengine_django/auth/middleware.py b/appengine_django/auth/middleware.py
new file mode 100644
index 0000000000000000000000000000000000000000..a727e4703dff68520e85874509c6c24b6336fd31
--- /dev/null
+++ b/appengine_django/auth/middleware.py
@@ -0,0 +1,36 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from django.contrib.auth.models import AnonymousUser
+
+from google.appengine.api import users
+
+from appengine_django.auth.models import User
+
+
+class LazyUser(object):
+  def __get__(self, request, obj_type=None):
+    if not hasattr(request, '_cached_user'):
+      user = users.get_current_user()
+      if user:
+        request._cached_user = User.get_djangouser_for_user(user)
+      else:
+        request._cached_user = AnonymousUser()
+    return request._cached_user
+
+
+class AuthenticationMiddleware(object):
+  def process_request(self, request):
+    request.__class__.user = LazyUser()
+    return None
diff --git a/appengine_django/auth/models.py b/appengine_django/auth/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..d93e2404438f60269fe3385ca1a453e807b0d88d
--- /dev/null
+++ b/appengine_django/auth/models.py
@@ -0,0 +1,172 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+App Engine compatible models for the Django authentication framework.
+"""
+
+from django.core import mail
+from django.core.exceptions import ImproperlyConfigured
+from django.db import models
+from django.utils.encoding import smart_str
+import urllib
+
+from django.db.models.manager import EmptyManager
+
+from google.appengine.api import users
+from google.appengine.ext import db
+
+from appengine_django.models import BaseModel
+
+
+class User(BaseModel):
+  """A model with the same attributes and methods as a Django user model.
+
+  The model has two additions. The first addition is a 'user' attribute which
+  references a App Engine user. The second is the 'get_djangouser_for_user'
+  classmethod that should be used to retrieve a DjangoUser instance from a App
+  Engine user object.
+  """
+  user = db.UserProperty(required=True)
+  username = db.StringProperty(required=True)
+  first_name = db.StringProperty()
+  last_name = db.StringProperty()
+  email = db.EmailProperty()
+  password = db.StringProperty()
+  is_staff = db.BooleanProperty(default=False, required=True)
+  is_active = db.BooleanProperty(default=True, required=True)
+  is_superuser = db.BooleanProperty(default=False, required=True)
+  last_login = db.DateTimeProperty(auto_now_add=True, required=True)
+  date_joined = db.DateTimeProperty(auto_now_add=True, required=True)
+  groups = EmptyManager()
+  user_permissions = EmptyManager()
+
+  def __unicode__(self):
+    return self.username
+
+  def __str__(self):
+    return unicode(self).encode('utf-8')
+
+  @classmethod
+  def get_djangouser_for_user(cls, user):
+    query = cls.all().filter("user =", user)
+    if query.count() == 0:
+      django_user = cls(user=user, email=user.email(), username=user.nickname())
+      django_user.save()
+    else:
+      django_user = query.get()
+    return django_user
+
+  def set_password(self, raw_password):
+    raise NotImplementedError
+
+  def check_password(self, raw_password):
+    raise NotImplementedError
+
+  def set_unusable_password(self):
+    raise NotImplementedError
+
+  def has_usable_password(self):
+    raise NotImplementedError
+
+  def get_group_permissions(self):
+    return self.user_permissions
+
+  def get_all_permissions(self):
+    return self.user_permissions
+
+  def has_perm(self, perm):
+    return False
+
+  def has_perms(self, perm_list):
+    return False
+
+  def has_module_perms(self, module):
+    return False
+
+  def get_and_delete_messages(self):
+    """Gets and deletes messages for this user"""
+    msgs = []
+    for msg in self.message_set:
+      msgs.append(msg)
+      msg.delete()
+    return msgs
+
+  def is_anonymous(self):
+    """Always return False"""
+    return False
+
+  def is_authenticated(self):
+    """Always return True"""
+    return True
+
+  def get_absolute_url(self):
+    return "/users/%s/" % urllib.quote(smart_str(self.username))
+
+  def get_full_name(self):
+    full_name = u'%s %s' % (self.first_name, self.last_name)
+    return full_name.strip()
+
+  def email_user(self, subject, message, from_email):
+    """Sends an email to this user.
+
+    According to the App Engine email API the from_email must be the
+    email address of a registered administrator for the application.
+    """
+    mail.send_mail(subject,
+                   message,
+                   from_email,
+                   [self.email])
+
+  def get_profile(self):
+    """
+    Returns site-specific profile for this user. Raises
+    SiteProfileNotAvailable if this site does not allow profiles.
+
+    When using the App Engine authentication framework, users are created
+    automatically.
+    """
+    from django.contrib.auth.models import SiteProfileNotAvailable
+    if not hasattr(self, '_profile_cache'):
+      from django.conf import settings
+      if not hasattr(settings, "AUTH_PROFILE_MODULE"):
+        raise SiteProfileNotAvailable
+      try:
+        app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
+        model = models.get_model(app_label, model_name)
+        self._profile_cache = model.all().filter("user =", self).get()
+        if not self._profile_cache:
+          raise model.DoesNotExist
+      except (ImportError, ImproperlyConfigured):
+        raise SiteProfileNotAvailable
+    return self._profile_cache
+
+
+class Group(BaseModel):
+  """Group model not fully implemented yet."""
+  # TODO: Implement this model, requires contenttypes
+  name = db.StringProperty()
+  permissions = EmptyManager()
+
+
+class Message(BaseModel):
+  """User message model"""
+  user = db.ReferenceProperty(User)
+  message = db.TextProperty()
+
+
+class Permission(BaseModel):
+  """Permission model not fully implemented yet."""
+  # TODO: Implement this model, requires contenttypes
+  name = db.StringProperty()
diff --git a/appengine_django/auth/templatetags.py b/appengine_django/auth/templatetags.py
new file mode 100644
index 0000000000000000000000000000000000000000..82378905bf2842f3a54ba771728d5e142114a3a3
--- /dev/null
+++ b/appengine_django/auth/templatetags.py
@@ -0,0 +1,62 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Template tags for the auth module. These are inserted into Django as "built-in"
+tags so you do not need to use the load statement in your template to get
+access to them.
+"""
+
+from django.template import Library
+from django.template import Node
+
+from google.appengine.api import users
+
+
+class AuthLoginUrlsNode(Node):
+  """Template node that creates an App Engine login or logout URL.
+
+  If create_login_url is True the App Engine's login URL is rendered into
+  the template, otherwise the logout URL.
+  """
+  def __init__(self, create_login_url, redirect):
+    self.redirect = redirect
+    self.create_login_url = create_login_url
+
+  def render(self, context):
+    if self.create_login_url:
+      return users.create_login_url(self.redirect)
+    else:
+      return users.create_logout_url(self.redirect)
+
+
+def auth_login_urls(parser, token):
+  """Template tag registered as 'auth_login_url' and 'auth_logout_url'
+  when the module is imported.
+
+  Both tags take an optional argument that specifies the redirect URL and
+  defaults to '/'.
+  """
+  bits = list(token.split_contents())
+  if len(bits) == 2:
+    redirect = bits[1]
+  else:
+    redirect = "/"
+  login = bits[0] == "auth_login_url"
+  return AuthLoginUrlsNode(login, redirect)
+
+
+register = Library()
+register.tag("auth_login_url", auth_login_urls)
+register.tag("auth_logout_url", auth_login_urls)
diff --git a/appengine_django/auth/tests.py b/appengine_django/auth/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..20aecfa4bb88c871b246a4ca3bcf832e5cafdc26
--- /dev/null
+++ b/appengine_django/auth/tests.py
@@ -0,0 +1,58 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+BASIC_TESTS = """
+>>> from google.appengine.api import users
+>>> from models import User, AnonymousUser
+>>> appengine_user = users.User("test@example.com")
+>>> django_user = User.get_djangouser_for_user(appengine_user)
+>>> django_user.email == appengine_user.email()
+True
+>>> django_user.username == appengine_user.nickname()
+True
+>>> django_user.user == appengine_user
+True
+
+>>> django_user.username = 'test2'
+>>> key = django_user.save()
+>>> django_user.username == 'test2'
+True
+
+>>> django_user2 = User.get_djangouser_for_user(appengine_user)
+>>> django_user2 == django_user
+True
+
+>>> django_user.is_authenticated()
+True
+>>> django_user.is_staff
+False
+>>> django_user.is_active
+True
+
+>>> a = AnonymousUser()
+>>> a.is_authenticated()
+False
+>>> a.is_staff
+False
+>>> a.is_active
+False
+>>> a.groups.all()
+[]
+>>> a.user_permissions.all()
+[]
+
+
+"""
+
+__test__ = {'BASIC_TESTS': BASIC_TESTS}
diff --git a/appengine_django/conf/app_template/__init__.py b/appengine_django/conf/app_template/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/appengine_django/conf/app_template/models.py b/appengine_django/conf/app_template/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d7c5d0ea42b6067c512dc55bc3172fda5304d01
--- /dev/null
+++ b/appengine_django/conf/app_template/models.py
@@ -0,0 +1,4 @@
+from appengine_django.models import BaseModel
+from google.appengine.ext import db
+
+# Create your models here.
diff --git a/appengine_django/conf/app_template/views.py b/appengine_django/conf/app_template/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..60f00ef0ef347811e7b0c0921b7fda097acd9fcc
--- /dev/null
+++ b/appengine_django/conf/app_template/views.py
@@ -0,0 +1 @@
+# Create your views here.
diff --git a/appengine_django/db/__init__.py b/appengine_django/db/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..619bc789db6217d72668b1e8f2346c86d03b29db
--- /dev/null
+++ b/appengine_django/db/__init__.py
@@ -0,0 +1,20 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Explicitly set the name of this package to "appengine".
+#
+# The rationale for this is so that Django can refer to the database as
+# "appengine" even though at a filesystem level it appears as the "db" package
+# within the appengine_django package.
+__name__ = "appengine"
diff --git a/appengine_django/db/base.py b/appengine_django/db/base.py
new file mode 100755
index 0000000000000000000000000000000000000000..8a90182cf2f304d773a8af8dd2c197d0a1aa9b8d
--- /dev/null
+++ b/appengine_django/db/base.py
@@ -0,0 +1,150 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module looks after initialising the appengine api stubs."""
+
+import logging
+import os
+
+from appengine_django import appid
+from appengine_django import have_appserver
+from appengine_django.db.creation import DatabaseCreation
+
+
+from django.db.backends import BaseDatabaseWrapper
+from django.db.backends import BaseDatabaseFeatures
+from django.db.backends import BaseDatabaseOperations
+
+
+def get_datastore_paths():
+  """Returns a tuple with the path to the datastore and history file.
+
+  The datastore is stored in the same location as dev_appserver uses by
+  default, but the name is altered to be unique to this project so multiple
+  Django projects can be developed on the same machine in parallel.
+
+  Returns:
+    (datastore_path, history_path)
+  """
+  from google.appengine.tools import dev_appserver_main
+  datastore_path = dev_appserver_main.DEFAULT_ARGS['datastore_path']
+  history_path = dev_appserver_main.DEFAULT_ARGS['history_path']
+  datastore_path = datastore_path.replace("dev_appserver", "django_%s" % appid)
+  history_path = history_path.replace("dev_appserver", "django_%s" % appid)
+  return datastore_path, history_path
+
+
+def get_test_datastore_paths(inmemory=True):
+  """Returns a tuple with the path to the test datastore and history file.
+
+  If inmemory is true, (None, None) is returned to request an in-memory
+  datastore. If inmemory is false the path returned will be similar to the path
+  returned by get_datastore_paths but with a different name.
+
+  Returns:
+    (datastore_path, history_path)
+  """
+  if inmemory:
+    return None, None
+  datastore_path, history_path = get_datastore_paths()
+  datastore_path = datastore_path.replace("datastore", "testdatastore")
+  history_path = history_path.replace("datastore", "testdatastore")
+  return datastore_path, history_path
+
+
+def destroy_datastore(datastore_path, history_path):
+  """Destroys the appengine datastore at the specified paths."""
+  for path in [datastore_path, history_path]:
+    if not path: continue
+    try:
+      os.remove(path)
+    except OSError, e:
+      if e.errno != 2:
+        logging.error("Failed to clear datastore: %s" % e)
+
+
+class DatabaseError(Exception):
+  """Stub class for database errors. Required by Django"""
+  pass
+
+
+class IntegrityError(Exception):
+  """Stub class for database integrity errors. Required by Django"""
+  pass
+
+
+class DatabaseFeatures(BaseDatabaseFeatures):
+  """Stub class to provide the feaures member expected by Django"""
+  pass
+
+
+class DatabaseOperations(BaseDatabaseOperations):
+  """Stub class to provide the options member expected by Django"""
+  pass
+
+
+class DatabaseWrapper(BaseDatabaseWrapper):
+  """App Engine database definition for Django.
+
+  This "database" backend does not support any of the standard backend
+  operations. The only task that it performs is to setup the api stubs required
+  by the appengine libraries if they have not already been initialised by an
+  appserver.
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(DatabaseWrapper, self).__init__(*args, **kwargs)
+    self.features = DatabaseFeatures()
+    self.ops = DatabaseOperations()
+    self.creation = DatabaseCreation(self)
+    self.use_test_datastore = kwargs.get("use_test_datastore", False)
+    self.test_datastore_inmemory = kwargs.get("test_datastore_inmemory", True)
+    if have_appserver:
+      return
+    self._setup_stubs()
+
+  def _get_paths(self):
+    if self.use_test_datastore:
+      return get_test_datastore_paths(self.test_datastore_inmemory)
+    else:
+      return get_datastore_paths()
+
+  def _setup_stubs(self):
+    # If this code is being run without an appserver (eg. via a django
+    # commandline flag) then setup a default stub environment.
+    from google.appengine.tools import dev_appserver_main
+    args = dev_appserver_main.DEFAULT_ARGS.copy()
+    args['datastore_path'], args['history_path'] = self._get_paths()
+    from google.appengine.tools import dev_appserver
+    dev_appserver.SetupStubs(appid, **args)
+    if self.use_test_datastore:
+      logging.debug("Configured API stubs for the test datastore")
+    else:
+      logging.debug("Configured API stubs for the development datastore")
+
+  def flush(self):
+    """Helper function to remove the current datastore and re-open the stubs"""
+    destroy_datastore(*self._get_paths())
+    self._setup_stubs()
+
+  def close(self):
+    pass
+
+  def _commit(self):
+    pass
+
+  def cursor(self, *args):
+    pass
diff --git a/appengine_django/db/creation.py b/appengine_django/db/creation.py
new file mode 100755
index 0000000000000000000000000000000000000000..0e0e27775a4efcd9ddc49f969d60af8cac40f68a
--- /dev/null
+++ b/appengine_django/db/creation.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+
+from django.conf import settings
+from django.db.backends.creation import BaseDatabaseCreation
+
+
+class DatabaseCreation(BaseDatabaseCreation):
+
+  def create_test_db(self, *args, **kw):
+    """Destroys the test datastore. A new store will be recreated on demand"""
+    settings.DATABASE_SUPPORTS_TRANSACTIONS = False
+    self.destroy_test_db()
+    self.connection.use_test_datastore = True
+    self.connection.flush()
+
+
+  def destroy_test_db(self, *args, **kw):
+    """Destroys the test datastore files."""
+    from appengine_django.db.base import destroy_datastore
+    from appengine_django.db.base import get_test_datastore_paths
+    destroy_datastore(*get_test_datastore_paths())
+    logging.debug("Destroyed test datastore")
diff --git a/appengine_django/mail.py b/appengine_django/mail.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf3e2ddf99540fd77e8b1da36e8284d953af6e4e
--- /dev/null
+++ b/appengine_django/mail.py
@@ -0,0 +1,95 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This module replaces the Django mail implementation with a version that sends
+email via the mail API provided by Google App Engine.
+
+Multipart / HTML email is not yet supported.
+"""
+
+import logging
+
+from django.core import mail
+from django.core.mail import SMTPConnection
+from django.conf import settings
+
+from google.appengine.api import mail as gmail
+
+
+class GoogleSMTPConnection(SMTPConnection):
+  def __init__(self, host=None, port=None, username=None, password=None,
+               use_tls=None, fail_silently=False):
+    self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
+    self.fail_silently = fail_silently
+    self.connection = None
+
+  def open(self):
+    self.connection = True
+
+  def close(self):
+    pass
+
+  def _send(self, email_message):
+    """A helper method that does the actual sending."""
+    if not email_message.to:
+      return False
+    try:
+      if (isinstance(email_message,gmail.EmailMessage)):
+        e = message
+      elif (isinstance(email_message,mail.EmailMessage)):
+        e = gmail.EmailMessage(sender=email_message.from_email,
+                               to=email_message.to,
+                               subject=email_message.subject,
+                               body=email_message.body)
+        if email_message.extra_headers.get('Reply-To', None):
+            e.reply_to = email_message.extra_headers['Reply-To']
+        if email_message.bcc:
+            e.bcc = list(email_message.bcc)
+        #TODO - add support for html messages and attachments...
+      e.send()
+    except:
+      if not self.fail_silently:
+          raise
+      return False
+    return True
+
+
+def mail_admins(subject, message, fail_silently=False):
+    """Sends a message to the admins, as defined by the ADMINS setting."""
+    _mail_group(settings.ADMINS, subject, message, fail_silently)
+
+
+def mail_managers(subject, message, fail_silently=False):
+    """Sends a message to the managers, as defined by the MANAGERS setting."""
+    _mail_group(settings.MANAGERS, subject, message, fail_silently)
+
+
+def _mail_group(group, subject, message, fail_silently=False):
+    """Sends a message to an administrative group."""
+    if group:
+      mail.send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+                     settings.SERVER_EMAIL, [a[1] for a in group],
+                     fail_silently)
+      return
+    # If the group had no recipients defined, default to the App Engine admins.
+    try:
+      gmail.send_mail_to_admins(settings.SERVER_EMAIL,
+                                settings.EMAIL_SUBJECT_PREFIX + subject,
+                                message)
+    except:
+      if not fail_silently:
+        raise
diff --git a/appengine_django/management/__init__.py b/appengine_django/management/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/appengine_django/management/commands/__init__.py b/appengine_django/management/commands/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/appengine_django/management/commands/console.py b/appengine_django/management/commands/console.py
new file mode 100755
index 0000000000000000000000000000000000000000..2c4069706e8cdd45670230bcb922676b1d950c63
--- /dev/null
+++ b/appengine_django/management/commands/console.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import code
+import getpass
+import os
+import sys
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+
+from google.appengine.ext.remote_api import remote_api_stub
+
+
+def auth_func():
+  return raw_input('Username:'), getpass.getpass('Password:')
+
+class Command(BaseCommand):
+  """ Start up an interactive console backed by your app using remote_api """
+  
+  help = 'Start up an interactive console backed by your app using remote_api.'
+
+  def run_from_argv(self, argv):
+    app_id = argv[2]
+    if len(argv) > 3:
+      host = argv[3]
+    else:
+      host = '%s.appspot.com' % app_id
+
+    remote_api_stub.ConfigureRemoteDatastore(app_id, 
+                                             '/remote_api',
+                                             auth_func,
+                                             host)
+      
+    code.interact('App Engine interactive console for %s' % (app_id,), 
+                  None,
+                  locals())
diff --git a/appengine_django/management/commands/flush.py b/appengine_django/management/commands/flush.py
new file mode 100755
index 0000000000000000000000000000000000000000..c5f3f8c23036c778f6aae206a6a4f06c2532680a
--- /dev/null
+++ b/appengine_django/management/commands/flush.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import sys
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+    """Overrides the default Django flush command.
+    """
+    help = 'Clears the current datastore and loads the initial fixture data.'
+
+    def run_from_argv(self, argv):
+      from django.db import connection
+      connection.flush()
+      from django.core.management import call_command
+      call_command('loaddata', 'initial_data')
+
+    def handle(self, *args, **kwargs):
+      self.run_from_argv(None)
diff --git a/appengine_django/management/commands/reset.py b/appengine_django/management/commands/reset.py
new file mode 100755
index 0000000000000000000000000000000000000000..126f38634086a0de7e3a838d1ba832df0618c105
--- /dev/null
+++ b/appengine_django/management/commands/reset.py
@@ -0,0 +1,32 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+import sys
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+    """Overrides the default Django reset command.
+    """
+    help = 'Clears the current datastore.'
+
+    def run_from_argv(self, argv):
+      from django.db import connection
+      connection.flush()
diff --git a/appengine_django/management/commands/rollback.py b/appengine_django/management/commands/rollback.py
new file mode 100755
index 0000000000000000000000000000000000000000..6ce9e4eaca1d64a5c0e087bcf019381ad37689c8
--- /dev/null
+++ b/appengine_django/management/commands/rollback.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import sys
+import logging
+
+from django.core.management.base import BaseCommand
+
+
+def run_appcfg():
+  # import this so that we run through the checks at the beginning
+  # and report the appropriate errors
+  import appcfg
+
+  # We don't really want to use that one though, it just executes this one
+  from google.appengine.tools import appcfg
+  
+  # Reset the logging level to WARN as appcfg will spew tons of logs on INFO
+  logging.getLogger().setLevel(logging.WARN)
+
+  # Note: if we decide to change the name of this command to something other
+  #       than 'rollback' we will have to munge the args to replace whatever
+  #       we called it with 'rollback'
+  new_args = sys.argv[:]
+  new_args.append('.')
+  appcfg.main(new_args)
+
+
+class Command(BaseCommand):
+  """Calls the appcfg.py's rollback command for the current project.
+
+  Any additional arguments are passed directly to appcfg.py.
+  """
+  help = 'Calls appcfg.py rollback for the current project.'
+  args = '[any appcfg.py options]'
+
+  def run_from_argv(self, argv):
+    run_appcfg()
diff --git a/appengine_django/management/commands/runserver.py b/appengine_django/management/commands/runserver.py
new file mode 100755
index 0000000000000000000000000000000000000000..07d080d1f9e2a3668701e0788c5f029b1fa45869
--- /dev/null
+++ b/appengine_django/management/commands/runserver.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+import sys
+
+from appengine_django.db.base import get_datastore_paths
+
+from django.core.management.base import BaseCommand
+
+
+def start_dev_appserver():
+  """Starts the appengine dev_appserver program for the Django project.
+
+  The appserver is run with default parameters. If you need to pass any special
+  parameters to the dev_appserver you will have to invoke it manually.
+  """
+  from google.appengine.tools import dev_appserver_main
+  progname = sys.argv[0]
+  args = []
+  # hack __main__ so --help in dev_appserver_main works OK.
+  sys.modules['__main__'] = dev_appserver_main
+  # Set bind ip/port if specified.
+  if len(sys.argv) > 2:
+    addrport = sys.argv[2]
+    try:
+      addr, port = addrport.split(":")
+    except ValueError:
+      addr, port = None, addrport
+    if not port.isdigit():
+      print "Error: '%s' is not a valid port number." % port
+      sys.exit(1)
+  else:
+    addr, port = None, "8000"
+  if addr:
+    args.extend(["--address", addr])
+  if port:
+    args.extend(["--port", port])
+  # Add email settings
+  from django.conf import settings
+  args.extend(['--smtp_host', settings.EMAIL_HOST,
+               '--smtp_port', str(settings.EMAIL_PORT),
+               '--smtp_user', settings.EMAIL_HOST_USER,
+               '--smtp_password', settings.EMAIL_HOST_PASSWORD])
+
+  # Allow skipped files so we don't die
+  args.extend(['--allow_skipped_files'])
+
+  # Pass the application specific datastore location to the server.
+  p = get_datastore_paths()
+  args.extend(["--datastore_path", p[0], "--history_path", p[1]])
+
+  # Append the current working directory to the arguments.
+  dev_appserver_main.main([progname] + args + [os.getcwdu()])
+
+
+class Command(BaseCommand):
+    """Overrides the default Django runserver command.
+
+    Instead of starting the default Django development server this command
+    fires up a copy of the full fledged appengine dev_appserver that emulates
+    the live environment your application will be deployed to.
+    """
+    help = 'Runs a copy of the appengine development server.'
+    args = '[optional port number, or ipaddr:port]'
+
+    def run_from_argv(self, argv):
+      start_dev_appserver()
diff --git a/appengine_django/management/commands/startapp.py b/appengine_django/management/commands/startapp.py
new file mode 100644
index 0000000000000000000000000000000000000000..2648cbddfc5f8669165b3d937a294579b70f6b04
--- /dev/null
+++ b/appengine_django/management/commands/startapp.py
@@ -0,0 +1,43 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+
+import django
+from django.core.management.commands import startapp
+
+import appengine_django
+
+
+class Command(startapp.Command):
+  def handle_label(self, *args, **kwds):
+    """Temporary adjust django.__path__ to load app templates from the
+    helpers directory.
+    """
+    old_path = django.__path__
+    django.__path__ = appengine_django.__path__
+    startapp.Command.handle_label(self, *args, **kwds)
+    django.__path__ = old_path
+
+
+class ProjectCommand(Command):
+  def __init__(self, project_directory):
+    super(ProjectCommand, self).__init__()
+    self.project_directory = project_directory
+
+  def handle_label(self, app_name, **options):
+    super(ProjectCommand, self).handle_label(app_name, self.project_directory,
+                                             **options)
+
diff --git a/appengine_django/management/commands/testserver.py b/appengine_django/management/commands/testserver.py
new file mode 100755
index 0000000000000000000000000000000000000000..5a50a7084122a31c2679ec8701967b382be6dabd
--- /dev/null
+++ b/appengine_django/management/commands/testserver.py
@@ -0,0 +1,74 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+import sys
+
+from appengine_django.db.base import destroy_datastore
+from appengine_django.db.base import get_test_datastore_paths
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+  """Overrides the default Django testserver command.
+
+  Instead of starting the default Django development server this command fires
+  up a copy of the full fledged appengine dev_appserver.
+
+  The appserver is always initialised with a blank datastore with the specified
+  fixtures loaded into it.
+  """
+  help = 'Runs the development server with data from the given fixtures.'
+
+  def run_from_argv(self, argv):
+    fixtures = argv[2:]
+
+    # Ensure an on-disk test datastore is used.
+    from django.db import connection
+    connection.use_test_datastore = True
+    connection.test_datastore_inmemory = False
+
+    # Flush any existing test datastore.
+    connection.flush()
+
+    # Load the fixtures.
+    from django.core.management import call_command
+    call_command('loaddata', 'initial_data')
+    if fixtures:
+      call_command('loaddata', *fixtures)
+
+    # Build new arguments for dev_appserver.
+    datastore_path, history_path = get_test_datastore_paths(False)
+    new_args = argv[0:1]
+    new_args.extend(['--datastore_path', datastore_path])
+    new_args.extend(['--history_path', history_path])
+    new_args.extend([os.getcwdu()])
+
+    # Add email settings
+    from django.conf import settings
+    new_args.extend(['--smtp_host', settings.EMAIL_HOST,
+                     '--smtp_port', str(settings.EMAIL_PORT),
+                     '--smtp_user', settings.EMAIL_HOST_USER,
+                     '--smtp_password', settings.EMAIL_HOST_PASSWORD])
+
+    # Allow skipped files so we don't die
+    new_args.extend(['--allow_skipped_files'])
+
+    # Start the test dev_appserver.
+    from google.appengine.tools import dev_appserver_main
+    dev_appserver_main.main(new_args)
diff --git a/appengine_django/management/commands/update.py b/appengine_django/management/commands/update.py
new file mode 100755
index 0000000000000000000000000000000000000000..e489d5d09607b10ca16a2a393aa065a89e55d5ea
--- /dev/null
+++ b/appengine_django/management/commands/update.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import sys
+import logging
+
+from django.core.management.base import BaseCommand
+
+
+def run_appcfg():
+  # import this so that we run through the checks at the beginning
+  # and report the appropriate errors
+  import appcfg
+
+  # We don't really want to use that one though, it just executes this one
+  from google.appengine.tools import appcfg
+
+  # Reset the logging level to WARN as appcfg will spew tons of logs on INFO
+  logging.getLogger().setLevel(logging.WARN)
+  
+  # Note: if we decide to change the name of this command to something other
+  #       than 'update' we will have to munge the args to replace whatever
+  #       we called it with 'update'
+  new_args = sys.argv[:]
+  new_args.append('.')
+  appcfg.main(new_args)
+
+class Command(BaseCommand):
+  """Calls the appcfg.py's update command for the current project.
+
+  Any additional arguments are passed directly to appcfg.py.
+  """
+  help = 'Calls appcfg.py update for the current project.'
+  args = '[any appcfg.py options]'
+
+  def run_from_argv(self, argv):
+    run_appcfg()
diff --git a/appengine_django/management/commands/vacuum_indexes.py b/appengine_django/management/commands/vacuum_indexes.py
new file mode 100755
index 0000000000000000000000000000000000000000..ab276b414b7d7fb318d9e16114c9c109049655b2
--- /dev/null
+++ b/appengine_django/management/commands/vacuum_indexes.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import sys
+import logging
+
+from django.core.management.base import BaseCommand
+
+
+def run_appcfg():
+  # import this so that we run through the checks at the beginning
+  # and report the appropriate errors
+  import appcfg
+
+  # We don't really want to use that one though, it just executes this one
+  from google.appengine.tools import appcfg
+
+  # Reset the logging level to WARN as appcfg will spew tons of logs on INFO
+  logging.getLogger().setLevel(logging.WARN)
+
+  # Note: if we decide to change the name of this command to something other
+  #       than 'vacuum_indexes' we will have to munge the args to replace whatever
+  #       we called it with 'vacuum_indexes'
+  new_args = sys.argv[:]
+  new_args.append('.')
+  appcfg.main(new_args)
+
+
+class Command(BaseCommand):
+  """Calls the appcfg.py's vacuum_indexes command for the current project.
+
+  Any additional arguments are passed directly to appcfg.py.
+  """
+  help = 'Calls appcfg.py vacuum_indexes for the current project.'
+  args = '[any appcfg.py options]'
+
+  def run_from_argv(self, argv):
+    run_appcfg()
diff --git a/appengine_django/models.py b/appengine_django/models.py
new file mode 100755
index 0000000000000000000000000000000000000000..0b9f6dcb546e6de40176e503861b400b58f41e83
--- /dev/null
+++ b/appengine_django/models.py
@@ -0,0 +1,182 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import types
+
+from google.appengine.ext import db
+
+from django import VERSION
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.fields import Field
+from django.db.models.options import Options
+from django.db.models.loading import register_models, get_model
+
+
+class ModelManager(object):
+  """Replacement for the default Django model manager."""
+
+  def __init__(self, owner):
+    self.owner = owner
+
+  def __getattr__(self, name):
+    """Pass all attribute requests through to the real model"""
+    return getattr(self.owner, name)
+
+
+class ModelOptions(object):
+  """Replacement for the default Django options class.
+
+  This class sits at ._meta of each model. The primary information supplied by
+  this class that needs to be stubbed out is the list of fields on the model.
+  """
+
+  def __init__(self, cls):
+    self.object_name = cls.__name__
+    self.module_name = self.object_name.lower()
+    model_module = sys.modules[cls.__module__]
+    self.app_label = model_module.__name__.split('.')[-2]
+    self.abstract = False
+
+  class pk:
+    """Stub the primary key to always be 'key_name'"""
+    name = "key_name"
+
+  def __str__(self):
+    return "%s.%s" % (self.app_label, self.module_name)
+
+  @property
+  def many_to_many(self):
+    """The datastore does not support many to many relationships."""
+    return []
+
+
+class Relation(object):
+  def __init__(self, to):
+    self.field_name = "key_name"
+
+
+def PropertyWrapper(prop):
+  """Wrapper for db.Property to make it look like a Django model Property"""
+  if isinstance(prop, db.Reference):
+    prop.rel = Relation(prop.reference_class)
+  else:
+    prop.rel = None
+  prop.serialize = True
+  return prop
+
+
+class PropertiedClassWithDjango(db.PropertiedClass):
+  """Metaclass for the combined Django + App Engine model class.
+
+  This metaclass inherits from db.PropertiedClass in the appengine library.
+  This metaclass has two additional purposes:
+  1) Register each model class created with Django (the parent class will take
+     care of registering it with the appengine libraries).
+  2) Add the (minimum number) of attributes and methods to make Django believe
+     the class is a normal Django model.
+
+  The resulting classes are still not generally useful as Django classes and
+  are intended to be used by Django only in limited situations such as loading
+  and dumping fixtures.
+  """
+
+  def __new__(cls, name, bases, attrs):
+    """Creates a combined appengine and Django model.
+
+    The resulting model will be known to both the appengine libraries and
+    Django.
+    """
+    if name == 'BaseModel':
+      # This metaclass only acts on subclasses of BaseModel.
+      return super(PropertiedClassWithDjango, cls).__new__(cls, name,
+                                                           bases, attrs)
+
+    new_class = super(PropertiedClassWithDjango, cls).__new__(cls, name,
+                                                              bases, attrs)
+
+    new_class._meta = ModelOptions(new_class)
+    new_class.objects = ModelManager(new_class)
+    new_class._default_manager = new_class.objects
+    new_class.DoesNotExist = types.ClassType('DoesNotExist',
+                                             (ObjectDoesNotExist,), {})
+
+    m = get_model(new_class._meta.app_label, name, False)
+    if m:
+      return m
+
+    register_models(new_class._meta.app_label, new_class)
+    return get_model(new_class._meta.app_label, name, False)
+
+  def __init__(cls, name, bases, attrs):
+    """Initialises the list of Django properties.
+
+    This method takes care of wrapping the properties created by the superclass
+    so that they look like Django properties and installing them into the
+    ._meta object of the class so that Django can find them at the appropriate
+    time.
+    """
+    super(PropertiedClassWithDjango, cls).__init__(name, bases, attrs)
+    if name == 'BaseModel':
+      # This metaclass only acts on subclasses of BaseModel.
+      return
+
+    fields = [PropertyWrapper(p) for p in cls._properties.values()]
+    cls._meta.local_fields = fields
+
+
+class BaseModel(db.Model):
+  """Combined appengine and Django model.
+
+  All models used in the application should derive from this class.
+  """
+  __metaclass__ = PropertiedClassWithDjango
+ 
+  def __eq__(self, other):
+    if not isinstance(other, self.__class__):
+      return False
+    return self._get_pk_val() == other._get_pk_val()
+
+  def __ne__(self, other):
+    return not self.__eq__(other)
+
+  def _get_pk_val(self):
+    """Return the string representation of the model's key"""
+    return unicode(self.key())
+
+  def __repr__(self):
+    """Create a string that can be used to construct an equivalent object.
+
+    e.g. eval(repr(obj)) == obj
+    """
+    # First, creates a dictionary of property names and values. Note that
+    # property values, not property objects, has to be passed in to constructor.
+    def _MakeReprTuple(prop_name):
+      prop = getattr(self.__class__, prop_name)
+      return (prop_name, prop.get_value_for_datastore(self))
+
+    d = dict([_MakeReprTuple(prop_name) for prop_name in self.properties()])
+    return "%s(**%s)" % (self.__class__.__name__, repr(d))
+
+
+class RegistrationTestModel(BaseModel):
+  """Used to check registration with Django is working correctly.
+
+  Django 0.96 only recognises models defined within an applications models
+  module when get_models() is called so this definition must be here rather
+  than within the associated test (tests/model_test.py).
+  """
+  pass
diff --git a/appengine_django/replacement_imp.py b/appengine_django/replacement_imp.py
new file mode 100644
index 0000000000000000000000000000000000000000..330aaf01dc666d4071b7566b0e0cefee3afba721
--- /dev/null
+++ b/appengine_django/replacement_imp.py
@@ -0,0 +1,26 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This file acts as a very minimal replacement for the 'imp' module.
+
+It contains only what Django expects to use and does not actually implement the
+same functionality as the real 'imp' module.
+"""
+
+
+def find_module(name, path=None):
+  """Django needs imp.find_module, but it works fine if nothing is found."""
+  raise ImportError
diff --git a/appengine_django/serializer/__init__.py b/appengine_django/serializer/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/appengine_django/serializer/python.py b/appengine_django/serializer/python.py
new file mode 100755
index 0000000000000000000000000000000000000000..bce16e70e00be1fd64ea6aee224b9e9b279167a7
--- /dev/null
+++ b/appengine_django/serializer/python.py
@@ -0,0 +1,130 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+A Python "serializer", based on the default Django python serializer.
+
+The only customisation is in the deserialization process which needs to take
+special care to resolve the name and parent attributes of the key for each
+entity and also recreate the keys for any references appropriately.
+"""
+
+
+from django.conf import settings
+from django.core.serializers import base
+from django.core.serializers import python
+from django.db import models
+
+from google.appengine.api import datastore_types
+from google.appengine.ext import db
+
+from django.utils.encoding import smart_unicode
+
+Serializer = python.Serializer
+
+
+class FakeParent(object):
+  """Fake parent 'model' like object.
+
+  This class exists to allow a parent object to be provided to a new model
+  without having to load the parent instance itself.
+  """
+
+  def __init__(self, parent_key):
+    self._entity = parent_key
+
+
+def Deserializer(object_list, **options):
+  """Deserialize simple Python objects back into Model instances.
+
+  It's expected that you pass the Python objects themselves (instead of a
+  stream or a string) to the constructor
+  """
+  models.get_apps()
+  for d in object_list:
+    # Look up the model and starting build a dict of data for it.
+    Model = python._get_model(d["model"])
+    data = {}
+    key = resolve_key(Model._meta.module_name, d["pk"])
+    if key.name():
+      data["key_name"] = key.name()
+    parent = None
+    if key.parent():
+      parent = FakeParent(key.parent())
+    m2m_data = {}
+
+    # Handle each field
+    for (field_name, field_value) in d["fields"].iteritems():
+      if isinstance(field_value, str):
+        field_value = smart_unicode(
+            field_value, options.get("encoding",
+                                     settings.DEFAULT_CHARSET),
+            strings_only=True)
+      field = Model.properties()[field_name]
+
+      if isinstance(field, db.Reference):
+        # Resolve foreign key references.
+        data[field.name] = resolve_key(Model._meta.module_name, field_value)
+        if not data[field.name].name():
+          raise base.DeserializationError(u"Cannot load Reference with "
+                                          "unnamed key: '%s'" % field_value)
+      else:
+        data[field.name] = field.validate(field_value)
+    # Create the new model instance with all it's data, but no parent.
+    object = Model(**data)
+    # Now add the parent into the hidden attribute, bypassing the type checks
+    # in the Model's __init__ routine.
+    object._parent = parent
+    # When the deserialized object is saved our replacement DeserializedObject
+    # class will set object._parent to force the real parent model to be loaded
+    # the first time it is referenced.
+    yield base.DeserializedObject(object, m2m_data)
+
+
+def resolve_key(model, key_data):
+  """Creates a Key instance from a some data.
+
+  Args:
+    model: The name of the model this key is being resolved for. Only used in
+      the fourth case below (a plain key_name string).
+    key_data: The data to create a key instance from. May be in four formats:
+      * The str() output of a key instance. Eg. A base64 encoded string.
+      * The repr() output of a key instance. Eg. A string for eval().
+      * A list of arguments to pass to db.Key.from_path.
+      * A single string value, being the key_name of the instance. When this
+        format is used the resulting key has no parent, and is for the model
+        named in the model parameter.
+
+  Returns:
+    An instance of db.Key. If the data cannot be used to create a Key instance
+    an error will be raised.
+  """
+  if isinstance(key_data, list):
+    # The key_data is a from_path sequence.
+    return db.Key.from_path(*key_data)
+  elif isinstance(key_data, basestring):
+    if key_data.find("from_path") != -1:
+      # key_data is encoded in repr(key) format
+      return eval(key_data)
+    else:
+      try:
+        # key_data encoded a str(key) format
+        return db.Key(key_data)
+      except datastore_types.datastore_errors.BadKeyError, e:
+        # Final try, assume it's a plain key name for the model.
+        return db.Key.from_path(model, key_data)
+  else:
+    raise base.DeserializationError(u"Invalid key data: '%s'" % key_data)
diff --git a/appengine_django/serializer/xml.py b/appengine_django/serializer/xml.py
new file mode 100755
index 0000000000000000000000000000000000000000..f67588a527fdd636f4adeda9a83f2abe40f89948
--- /dev/null
+++ b/appengine_django/serializer/xml.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Replaces the default Django XML serializer with one that uses the built in
+ToXml method for each entity.
+"""
+
+import re
+
+from django.conf import settings
+from django.core.serializers import base
+from django.core.serializers import xml_serializer
+from django.db import models
+
+from google.appengine.api import datastore_types
+from google.appengine.ext import db
+
+from python import FakeParent
+
+getInnerText = xml_serializer.getInnerText
+
+
+class Serializer(xml_serializer.Serializer):
+  """A Django Serializer class to convert datastore models to XML.
+
+  This class relies on the ToXml method of the entity behind each model to do
+  the hard work.
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(Serializer, self).__init__(*args, **kwargs)
+    self._objects = []
+
+  def handle_field(self, obj, field):
+    """Fields are not handled individually."""
+    pass
+
+  def handle_fk_field(self, obj, field):
+    """Fields are not handled individually."""
+    pass
+
+  def start_object(self, obj):
+    """Nothing needs to be done to start an object."""
+    pass
+
+  def end_object(self, obj):
+    """Serialize the object to XML and add to the list of objects to output.
+
+    The output of ToXml is manipulated to replace the datastore model name in
+    the "kind" tag with the Django model name (which includes the Django
+    application name) to make importing easier.
+    """
+    xml = obj._entity.ToXml()
+    xml = xml.replace(u"""kind="%s" """ % obj._entity.kind(),
+                      u"""kind="%s" """ % unicode(obj._meta))
+    self._objects.append(xml)
+
+  def getvalue(self):
+    """Wrap the serialized objects with XML headers and return."""
+    str = u"""<?xml version="1.0" encoding="utf-8"?>\n"""
+    str += u"""<django-objects version="1.0">\n"""
+    str += u"".join(self._objects)
+    str += u"""</django-objects>"""
+    return str
+
+
+class Deserializer(xml_serializer.Deserializer):
+  """A Django Deserializer class to convert XML to Django objects.
+
+  This is a fairly manualy and simplistic XML parser, it supports just enough
+  functionality to read the keys and fields for an entity from the XML file and
+  construct a model object.
+  """
+
+  def next(self):
+    """Replacement next method to look for 'entity'.
+
+    The default next implementation exepects 'object' nodes which is not
+    what the entity's ToXml output provides.
+    """
+    for event, node in self.event_stream:
+      if event == "START_ELEMENT" and node.nodeName == "entity":
+        self.event_stream.expandNode(node)
+        return self._handle_object(node)
+    raise StopIteration
+
+  def _handle_object(self, node):
+    """Convert an <entity> node to a DeserializedObject"""
+    Model = self._get_model_from_node(node, "kind")
+    data = {}
+    key = db.Key(node.getAttribute("key"))
+    if key.name():
+      data["key_name"] = key.name()
+    parent = None
+    if key.parent():
+      parent = FakeParent(key.parent())
+    m2m_data = {}
+
+    # Deseralize each field.
+    for field_node in node.getElementsByTagName("property"):
+      # If the field is missing the name attribute, bail (are you
+      # sensing a pattern here?)
+      field_name = field_node.getAttribute("name")
+      if not field_name:
+          raise base.DeserializationError("<field> node is missing the 'name' "
+                                          "attribute")
+      field = Model.properties()[field_name]
+      field_value = getInnerText(field_node).strip()
+
+      if isinstance(field, db.Reference):
+        m = re.match("tag:.*\[(.*)\]", field_value)
+        if not m:
+          raise base.DeserializationError(u"Invalid reference value: '%s'" %
+                                          field_value)
+        key = m.group(1)
+        key_obj = db.Key(key)
+        if not key_obj.name():
+          raise base.DeserializationError(u"Cannot load Reference with "
+                                          "unnamed key: '%s'" % field_value)
+        data[field.name] = key_obj
+      else:
+        data[field.name] = field.validate(field_value)
+
+    # Create the new model instance with all it's data, but no parent.
+    object = Model(**data)
+    # Now add the parent into the hidden attribute, bypassing the type checks
+    # in the Model's __init__ routine.
+    object._parent = parent
+    # When the deserialized object is saved our replacement DeserializedObject
+    # class will set object._parent to force the real parent model to be loaded
+    # the first time it is referenced.
+    return base.DeserializedObject(object, m2m_data)
diff --git a/appengine_django/sessions/__init__.py b/appengine_django/sessions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/appengine_django/sessions/backends/__init__.py b/appengine_django/sessions/backends/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/appengine_django/sessions/backends/db.py b/appengine_django/sessions/backends/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2e3aa40f43bfd13827842e510e304117225afb6
--- /dev/null
+++ b/appengine_django/sessions/backends/db.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from datetime import datetime
+
+from django.contrib.sessions.backends import base
+from django.core.exceptions import SuspiciousOperation
+
+from appengine_django.sessions.models import Session
+
+
+class SessionStore(base.SessionBase):
+  """A key-based session store for Google App Engine."""
+
+  def load(self):
+    session = self._get_session(self.session_key)
+    if session:
+      try:
+        return self.decode(session.session_data)
+      except SuspiciousOperation:
+        # Create a new session_key for extra security.
+        pass
+    self.session_key = self._get_new_session_key()
+    self._session_cache = {}
+    self.save()
+    # Ensure the user is notified via a new cookie.
+    self.modified = True
+    return {}
+
+  def save(self, must_create=False):
+    if must_create and self.exists(self.session_key):
+      raise base.CreateError
+    session = Session(
+        key_name='k:' + self.session_key,
+        session_data = self.encode(self._session),
+        expire_date = self.get_expiry_date())
+    session.put()
+
+  def exists(self, session_key):
+    return Session.get_by_key_name('k:' + session_key) is not None
+
+  def delete(self, session_key=None):
+    if session_key is None:
+      session_key = self._session_key
+    session = self._get_session(session_key=session_key)
+    if session:
+      session.delete()
+
+  def _get_session(self, session_key):
+    session = Session.get_by_key_name('k:' + session_key)
+    if session:
+      if session.expire_date > datetime.now():
+        return session
+      session.delete()
+    return None
+
+  def create(self):
+    while True:
+      self.session_key = self._get_new_session_key()
+      try:
+        # Save immediately to ensure we have a unique entry in the
+        # database.
+        self.save(must_create=True)
+      except base.CreateError:
+        # Key wasn't unique. Try again.
+        continue
+      self.modified = True
+      self._session_cache = {}
+      return
diff --git a/appengine_django/sessions/models.py b/appengine_django/sessions/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..16816448c99d3631c066c9b18be232e79802dea2
--- /dev/null
+++ b/appengine_django/sessions/models.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from google.appengine.ext import db
+
+class Session(db.Model):
+  """Django compatible App Engine Datastore session model."""
+  session_data = db.BlobProperty()
+  expire_date = db.DateTimeProperty()
diff --git a/appengine_django/tests/__init__.py b/appengine_django/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b511f5858a19aec6068a463be1a553c9c49eb813
--- /dev/null
+++ b/appengine_django/tests/__init__.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Loads all the _test.py files into the top level of the package.
+
+This file is a hack around the fact that Django expects the tests "module" to
+be a single tests.py file and cannot handle a tests package inside an
+application.
+
+All _test.py files inside this package are imported and any classes derived
+from unittest.TestCase are then referenced from this file itself so that they
+appear at the top level of the tests "module" that Django will import.
+"""
+
+
+import os
+import re
+import types
+import unittest
+
+TEST_RE = r"^.*_test.py$"
+
+# Search through every file inside this package.
+test_names = []
+test_dir = os.path.dirname( __file__)
+for filename in os.listdir(test_dir):
+  if not re.match(TEST_RE, filename):
+    continue
+  # Import the test file and find all TestClass clases inside it.
+  test_module = __import__('appengine_django.tests.%s' %
+                           filename[:-3], {}, {},
+                           filename[:-3])
+  for name in dir(test_module):
+    item = getattr(test_module, name)
+    if not (isinstance(item, (type, types.ClassType)) and
+            issubclass(item, unittest.TestCase)):
+      continue
+    # Found a test, bring into the module namespace.
+    exec "%s = item" % name
+    test_names.append(name)
+
+# Hide everything other than the test cases from other modules.
+__all__ = test_names
diff --git a/appengine_django/tests/commands_test.py b/appengine_django/tests/commands_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..a02ddbfc0ae28b51e9c33e145e7c3f6907adee84
--- /dev/null
+++ b/appengine_django/tests/commands_test.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Tests that the manage.py commands execute correctly.
+
+These tests only verify that the commands execute and exit with a success code.
+They are intended to catch import exceptions and similar problems, it is left
+up to tests in other modules to verify that the functionality of each command
+works correctly.
+"""
+
+
+import os
+import re
+import signal
+import subprocess
+import tempfile
+import time
+import unittest
+
+from django.db.models import get_models
+
+from google.appengine.ext import db
+from appengine_django.models import BaseModel
+from appengine_django.models import ModelManager
+from appengine_django.models import ModelOptions
+from appengine_django.models import RegistrationTestModel
+
+
+class CommandsTest(unittest.TestCase):
+  """Unit tests for the manage.py commands."""
+
+  # How many seconds to wait for a command to exit.
+  COMMAND_TIMEOUT = 10
+
+  def runCommand(self, command, args=None, int_after=None, input=None):
+    """Helper to run the specified command in a child process.
+
+    Args:
+      command: The name of the command to run.
+      args: List of command arguments to run the command with.
+      int_after: If set to a positive integer, SIGINT will be sent to the
+        running child process after this many seconds to cause an exit. This
+        should be less than the COMMAND_TIMEOUT value (10 seconds).
+      input: A string to write to stdin when the command starts. stdin is
+        closed after the string is written.
+
+    Returns:
+      rc: The integer return code of the process.
+      output: A string containing the childs output.
+    """
+    if not args:
+      args = []
+    start = time.time()
+    int_sent = False
+    fd = subprocess.PIPE
+
+    child = subprocess.Popen(["./manage.py", command] + args, stdin=fd,
+                             stdout=fd, stderr=fd, cwd=os.getcwdu())
+    if input:
+      child.stdin.write(input)
+      child.stdin.close()
+
+    while 1:
+      rc = child.poll()
+      if rc is not None:
+        # Child has exited.
+        break
+      elapsed = time.time() - start
+      if int_after and int_after > 0 and elapsed > int_after and not int_sent:
+        # Sent SIGINT as requested, give child time to exit cleanly.
+        os.kill(child.pid, signal.SIGINT)
+        start = time.time()
+        int_sent = True
+        continue
+      if elapsed < self.COMMAND_TIMEOUT:
+        continue
+      # Command is over time, kill and exit loop.
+      os.kill(child.pid, signal.SIGKILL)
+      time.sleep(2)  # Give time for the signal to be received.
+      break
+
+    # Return status and output.
+    return rc, child.stdout.read(), child.stderr.read()
+
+  def assertCommandSucceeds(self, command, *args, **kwargs):
+    """Asserts that the specified command successfully completes.
+
+    Args:
+      command: The name of the command to run.
+      All other arguments are passed directly through to the runCommand
+      routine.
+
+    Raises:
+      This function does not return anything but will raise assertion errors if
+      the command does not exit successfully.
+    """
+    rc, stdout, stderr = self.runCommand(command, *args, **kwargs)
+    fd, tempname = tempfile.mkstemp()
+    os.write(fd, stdout)
+    os.close(fd)
+    self.assertEquals(0, rc,
+                      "%s did not return successfully (rc: %d): Output in %s" %
+                      (command, rc, tempname))
+    os.unlink(tempname)
+
+  def getCommands(self):
+    """Returns a list of valid commands for manage.py.
+
+    Args:
+      None
+
+    Returns:
+      A list of valid commands for manage.py as read from manage.py's help
+      output.
+    """
+    rc, stdout, stderr = self.runCommand("help")
+    parts = re.split("Available subcommands:", stderr)
+    if len(parts) < 2:
+      return []
+
+    return [t.strip() for t in parts[-1].split("\n") if t.strip()]
+
+  def testDiffSettings(self):
+    """Tests the diffsettings command."""
+    self.assertCommandSucceeds("diffsettings")
+
+  def testDumpData(self):
+    """Tests the dumpdata command."""
+    self.assertCommandSucceeds("dumpdata")
+
+  def testFlush(self):
+    """Tests the flush command."""
+    self.assertCommandSucceeds("flush")
+
+  def testLoadData(self):
+    """Tests the loaddata command."""
+    self.assertCommandSucceeds("loaddata")
+
+  def testLoadData(self):
+    """Tests the loaddata command."""
+    self.assertCommandSucceeds("loaddata")
+
+  def testReset(self):
+    """Tests the reste command."""
+    self.assertCommandSucceeds("reset", ["appengine_django"])
+
+  def testRunserver(self):
+    """Tests the runserver command."""
+    self.assertCommandSucceeds("runserver", int_after=2.0)
+
+  def testShell(self):
+    """Tests the shell command."""
+    self.assertCommandSucceeds("shell", input="exit")
+
+  def testUpdate(self):
+    """Tests that the update command exists.
+
+    Cannot test that it works without mocking out parts of dev_appserver so for
+    now we just assume that if it is present it will work.
+    """
+    cmd_list = self.getCommands()
+    self.assert_("update" in cmd_list)
+
+  def testZipCommandListFiltersCorrectly(self):
+    """When running under a zipfile test that only valid commands are found."""
+    cmd_list = self.getCommands()
+    self.assert_("__init__" not in cmd_list)
+    self.assert_("base" not in cmd_list)
diff --git a/appengine_django/tests/core_test.py b/appengine_django/tests/core_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..e2f51c839315ef666ecfcc956ad49513cca6c700
--- /dev/null
+++ b/appengine_django/tests/core_test.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests that the core module functionality is present and functioning."""
+
+
+import unittest
+
+from django.test import TestCase as DjangoTestCase
+
+from appengine_django import appid
+from appengine_django import have_appserver
+
+
+class AppengineDjangoTest(unittest.TestCase):
+  """Tests that the helper module has been correctly installed."""
+
+  def testAppidProvided(self):
+    """Tests that application ID and configuration has been loaded."""
+    self.assert_(appid is not None)
+
+  def testAppserverDetection(self):
+    """Tests that the appserver detection flag is present and correct."""
+    # It seems highly unlikely that these tests would ever be run from within
+    # an appserver.
+    self.assertEqual(have_appserver, False)
+
+
+class DjangoTestCaseTest(DjangoTestCase):
+  """Tests that the tests can be subclassed from Django's TestCase class."""
+
+  def testPassing(self):
+    """Tests that tests with Django's TestCase class work."""
+    self.assert_(True)
diff --git a/appengine_django/tests/db_test.py b/appengine_django/tests/db_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..452e8f93411266528007339d19107fc41d8afb07
--- /dev/null
+++ b/appengine_django/tests/db_test.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests that the db module correctly initialises the API stubs."""
+
+
+import unittest
+
+from django.db import connection
+from django.db.backends.appengine.base import DatabaseWrapper
+
+from appengine_django import appid
+from appengine_django.db import base
+
+
+class DatastoreTest(unittest.TestCase):
+  """Tests that the datastore stubs have been correctly setup."""
+
+  def testDjangoDBConnection(self):
+    """Tests that the Django DB connection is using our replacement."""
+    self.assert_(isinstance(connection, DatabaseWrapper))
+
+  def testDjangoDBConnectionStubs(self):
+    """Tests that members required by Django are stubbed."""
+    self.assert_(hasattr(connection, "features"))
+    self.assert_(hasattr(connection, "ops"))
+
+  def testDjangoDBErrorClasses(self):
+    """Tests that the error classes required by Django are stubbed."""
+    self.assert_(hasattr(base, "DatabaseError"))
+    self.assert_(hasattr(base, "IntegrityError"))
+
+  def testDatastorePath(self):
+    """Tests that the datastore path contains the app name."""
+    d_path, h_path = base.get_datastore_paths()
+    self.assertNotEqual(-1, d_path.find("django_%s" % appid))
+    self.assertNotEqual(-1, h_path.find("django_%s" % appid))
+
+  def testTestInMemoryDatastorePath(self):
+    """Tests that the test datastore is using the in-memory datastore."""
+    td_path, th_path = base.get_test_datastore_paths()
+    self.assert_(td_path is None)
+    self.assert_(th_path is None)
+
+  def testTestFilesystemDatastorePath(self):
+    """Tests that the test datastore is on the filesystem when requested."""
+    td_path, th_path = base.get_test_datastore_paths(False)
+    self.assertNotEqual(-1, td_path.find("testdatastore"))
+    self.assertNotEqual(-1, th_path.find("testdatastore"))
diff --git a/appengine_django/tests/memcache_test.py b/appengine_django/tests/memcache_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e5f02e28ce9eaf24fe48bae819d304807ba718e
--- /dev/null
+++ b/appengine_django/tests/memcache_test.py
@@ -0,0 +1,43 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Ensures the App Engine memcache API works as Django's memcache backend."""
+
+import unittest
+
+from django.core.cache import get_cache
+from appengine_django import appid
+from appengine_django import have_appserver
+
+
+class AppengineMemcacheTest(unittest.TestCase):
+  """Tests that the memcache backend works."""
+
+  def setUp(self):
+    """Get the memcache cache module so it is available to tests."""
+    self._cache = get_cache("memcached://")
+
+  def testSimpleSetGet(self):
+    """Tests that a simple set/get operation through the cache works."""
+    self._cache.set("test_key", "test_value")
+    self.assertEqual(self._cache.get("test_key"), "test_value")
+
+  def testDelete(self):
+    """Tests that delete removes values from the cache."""
+    self._cache.set("test_key", "test_value")
+    self.assertEqual(self._cache.has_key("test_key"), True)
+    self._cache.delete("test_key")
+    self.assertEqual(self._cache.has_key("test_key"), False)
diff --git a/appengine_django/tests/model_test.py b/appengine_django/tests/model_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..8611d8b31112b4c80cc9ec70ca4248d7b61d40ad
--- /dev/null
+++ b/appengine_django/tests/model_test.py
@@ -0,0 +1,110 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests that the combined appengine and Django models function correctly."""
+
+
+import unittest
+
+from django import VERSION
+from django.db.models import get_models
+from django import forms
+
+from google.appengine.ext.db import djangoforms
+from google.appengine.ext import db
+
+from appengine_django.models import BaseModel
+from appengine_django.models import ModelManager
+from appengine_django.models import ModelOptions
+from appengine_django.models import RegistrationTestModel
+
+
+class TestModelWithProperties(BaseModel):
+  """Test model class for checking property -> Django field setup."""
+  property1 = db.StringProperty()
+  property2 = db.IntegerProperty()
+  property3 = db.Reference()
+
+
+class ModelTest(unittest.TestCase):
+  """Unit tests for the combined model class."""
+
+  def testModelRegisteredWithDjango(self):
+    """Tests that a combined model class has been registered with Django."""
+    self.assert_(RegistrationTestModel in get_models())
+
+  def testDatastoreModelProperties(self):
+    """Tests that a combined model class still has datastore properties."""
+    self.assertEqual(3, len(TestModelWithProperties.properties()))
+
+  def testDjangoModelClass(self):
+    """Tests the parts of a model required by Django are correctly stubbed."""
+    # Django requires model options to be found at ._meta.
+    self.assert_(isinstance(RegistrationTestModel._meta, ModelOptions))
+    # Django requires a manager at .objects
+    self.assert_(isinstance(RegistrationTestModel.objects, ModelManager))
+    # Django requires ._default_manager.
+    self.assert_(hasattr(RegistrationTestModel, "_default_manager"))
+
+  def testDjangoModelFields(self):
+    """Tests that a combined model class has (faked) Django fields."""
+    fields = TestModelWithProperties._meta.local_fields
+    self.assertEqual(3, len(fields))
+    # Check each fake field has the minimal properties that Django needs.
+    for field in fields:
+      # The Django serialization code looks for rel to determine if the field
+      # is a relationship/reference to another model.
+      self.assert_(hasattr(field, "rel"))
+      # serialize is required to tell Django to serialize the field.
+      self.assertEqual(True, field.serialize)
+      if field.name == "property3":
+        # Extra checks for the Reference field.
+        # rel.field_name is used during serialization to find the field in the
+        # other model that this field is related to. This should always be
+        # 'key_name' for appengine models.
+        self.assertEqual("key_name", field.rel.field_name)
+
+  def testDjangoModelOptionsStub(self):
+    """Tests that the options stub has the required properties by Django."""
+    # Django requires object_name and app_label for serialization output.
+    self.assertEqual("RegistrationTestModel",
+                     RegistrationTestModel._meta.object_name)
+    self.assertEqual("appengine_django", RegistrationTestModel._meta.app_label)
+    # The pk.name member is required during serialization for dealing with
+    # related fields.
+    self.assertEqual("key_name", RegistrationTestModel._meta.pk.name)
+    # The many_to_many method is called by Django in the serialization code to
+    # find m2m relationships. m2m is not supported by the datastore.
+    self.assertEqual([], RegistrationTestModel._meta.many_to_many)
+
+  def testDjangoModelManagerStub(self):
+    """Tests that the manager stub acts as Django would expect."""
+    # The serialization code calls model.objects.all() to retrieve all objects
+    # to serialize.
+    self.assertEqual([], list(RegistrationTestModel.objects.all()))
+
+  def testDjangoModelPK(self):
+    """Tests that each model instance has a 'primary key' generated."""
+    obj = RegistrationTestModel(key_name="test")
+    obj.put()
+    pk = obj._get_pk_val()
+    self.assert_(pk)
+    new_obj = RegistrationTestModel.get(pk)
+    self.assertEqual(obj.key(), new_obj.key())
+
+  def testModelFormPatched(self):
+    """Tests that the Django ModelForm is being successfully patched."""
+    self.assertEqual(djangoforms.ModelForm, forms.ModelForm)
diff --git a/appengine_django/tests/serialization_test.py b/appengine_django/tests/serialization_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..39b92ea195491d8f77e708b8d4d81be9c4e1e35b
--- /dev/null
+++ b/appengine_django/tests/serialization_test.py
@@ -0,0 +1,310 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests that the serialization modules are functioning correctly.
+
+In particular, these tests verify that the modifications made to the standard
+Django serialization modules function correctly and that the combined datastore
+and Django models can be dumped and loaded to all of the provided formats.
+"""
+
+
+import os
+import re
+import unittest
+from StringIO import StringIO
+
+from django.core import serializers
+
+from google.appengine.ext import db
+from appengine_django.models import BaseModel
+
+
+class ModelA(BaseModel):
+  description = db.StringProperty()
+
+
+class ModelB(BaseModel):
+  description = db.StringProperty()
+  friend = db.Reference(ModelA)
+
+
+class TestAllFormats(type):
+
+  def __new__(cls, name, bases, attrs):
+    """Extends base test functions to be called for every serialisation format.
+
+    Looks for functions matching 'run.*Test', where the wildcard in the middle
+    matches the desired test name and ensures that a test case is setup to call
+    that function once for every defined serialisation format. The test case
+    that is created will be called 'test<format><name>'. Eg, for the function
+    'runKeyedObjectTest' functions like 'testJsonKeyedObject' will be created.
+    """
+    test_formats = serializers.get_serializer_formats()
+    test_formats.remove("python")  # Python serializer is only used indirectly.
+
+    for func_name in attrs.keys():
+      m = re.match("^run(.*)Test$", func_name)
+      if not m:
+        continue
+      for format in test_formats:
+        test_name = "test%s%s" % (format.title(), m.group(1))
+        test_func = eval("lambda self: getattr(self, \"%s\")(\"%s\")" %
+                         (func_name, format))
+        attrs[test_name] = test_func
+
+    return super(TestAllFormats, cls).__new__(cls, name, bases, attrs)
+
+
+class SerializationTest(unittest.TestCase):
+  """Unit tests for the serialization/deserialization functionality.
+
+  Tests that every loaded serialization format can successfully dump and then
+  reload objects without the objects changing.
+  """
+  __metaclass__ = TestAllFormats
+
+  def compareObjects(self, orig, new, format="unknown"):
+    """Compares two objects to ensure they are identical.
+
+    Args:
+      orig: The original object, must be an instance of db.Model.
+      new: The new object, must be an instance of db.Model.
+      format: The serialization format being tested, used to make error output
+        more helpful.
+
+    Raises:
+      The function has no return value, but will raise assertion errors if the
+      objects do not match correctly.
+    """
+    if orig.key().name():
+      # Only compare object keys when the key is named. Key IDs are not static
+      # and will change between dump/load. If you want stable Keys they need to
+      # be named!
+      self.assertEqual(orig.key(), new.key(),
+                       "keys not equal after %s serialization: %s != %s" %
+                       (format, repr(orig.key()), repr(new.key())))
+
+    for key in orig.properties().keys():
+      oval = getattr(orig, key)
+      nval = getattr(new, key)
+      if isinstance(orig.properties()[key], db.Reference):
+        # Need to compare object keys not the objects themselves.
+        oval = oval.key()
+        nval = nval.key()
+      self.assertEqual(oval, nval, "%s attribute differs after %s "
+                       "serialization: %s != %s" % (key, format, oval, nval))
+
+  def doSerialisationTest(self, format, obj, rel_attr=None, obj_ref=None):
+    """Runs a serialization test on an object for the specified format.
+
+    Args:
+      format: The name of the Django serialization class to use.
+      obj: The object to {,de}serialize, must be an instance of db.Model.
+      rel_attr: Name of the attribute of obj references another model.
+      obj_ref: The expected object reference, must be an instance of db.Model.
+
+    Raises:
+      The function has no return value but raises assertion errors if the
+      object cannot be successfully serialized and then deserialized back to an
+      identical object. If rel_attr and obj_ref are specified the deserialized
+      object must also retain the references from the original object.
+    """
+    serialised = serializers.serialize(format, [obj])
+    # Try and get the object back from the serialized string.
+    result = list(serializers.deserialize(format, StringIO(serialised)))
+    self.assertEqual(1, len(result),
+                     "%s serialization should create 1 object" % format)
+    result[0].save()  # Must save back into the database to get a Key.
+    self.compareObjects(obj, result[0].object, format)
+    if rel_attr and obj_ref:
+      rel = getattr(result[0].object, rel_attr)
+      if callable(rel):
+        rel = rel()
+      self.compareObjects(rel, obj_ref, format)
+
+  def doLookupDeserialisationReferenceTest(self, lookup_dict, format):
+    """Tests the Key reference is loaded OK for a format.
+
+    Args:
+      lookup_dict: A dictionary indexed by format containing serialized strings
+        of the objects to load.
+      format: The format to extract from the dict and deserialize.
+
+    Raises:
+      This function has no return value but raises assertion errors if the
+      string cannot be deserialized correctly or the resulting object does not
+      reference the object correctly.
+    """
+    if format not in lookup_dict:
+      # Check not valid for this format.
+      return
+    obj = ModelA(description="test object", key_name="test")
+    obj.put()
+    s = lookup_dict[format]
+    result = list(serializers.deserialize(format, StringIO(s)))
+    self.assertEqual(1, len(result), "expected 1 object from %s" % format)
+    result[0].save()
+    self.compareObjects(obj, result[0].object.friend, format)
+
+  def doModelKeyDeserialisationReferenceTest(self, lookup_dict, format):
+    """Tests a model with a key can be loaded OK for a format.
+
+    Args:
+      lookup_dict: A dictionary indexed by format containing serialized strings
+        of the objects to load.
+      format: The format to extract from the dict and deserialize.
+
+    Returns:
+      This function has no return value but raises assertion errors if the
+      string cannot be deserialized correctly or the resulting object is not an
+      instance of ModelA with a key named 'test'.
+    """
+    if format not in lookup_dict:
+      # Check not valid for this format.
+      return
+    s = lookup_dict[format]
+    result = list(serializers.deserialize(format, StringIO(s)))
+    self.assertEqual(1, len(result), "expected 1 object from %s" % format)
+    result[0].save()
+    self.assert_(isinstance(result[0].object, ModelA))
+    self.assertEqual("test", result[0].object.key().name())
+
+  # Lookup dicts for the above (doLookupDeserialisationReferenceTest) function.
+  SERIALIZED_WITH_KEY_AS_LIST = {
+      "json": """[{"pk": "agR0ZXN0chMLEgZNb2RlbEIiB21vZGVsYmkM", """
+              """"model": "tests.modelb", "fields": {"description": "test", """
+              """"friend": ["ModelA", "test"] }}]""",
+      "yaml": """- fields: {description: !!python/unicode 'test', friend: """
+              """ [ModelA, test]}\n  model: tests.modelb\n  pk: """
+              """ agR0ZXN0chMLEgZNb2RlbEEiB21vZGVsYWkM\n"""
+  }
+  SERIALIZED_WITH_KEY_REPR = {
+      "json": """[{"pk": "agR0ZXN0chMLEgZNb2RlbEIiB21vZGVsYmkM", """
+              """"model": "tests.modelb", "fields": {"description": "test", """
+              """"friend": "datastore_types.Key.from_path("""
+              """'ModelA', 'test')" }}]""",
+      "yaml": """- fields: {description: !!python/unicode 'test', friend: """
+              """\'datastore_types.Key.from_path("ModelA", "test")\'}\n  """
+              """model: tests.modelb\n  pk: """
+              """ agR0ZXN0chMLEgZNb2RlbEEiB21vZGVsYWkM\n"""
+  }
+
+  # Lookup dict for the doModelKeyDeserialisationReferenceTest function.
+  MK_SERIALIZED_WITH_LIST = {
+      "json": """[{"pk": ["ModelA", "test"], "model": "tests.modela", """
+              """"fields": {}}]""",
+      "yaml": """-\n fields: {description: null}\n model: tests.modela\n """
+              """pk: [ModelA, test]\n"""
+  }
+  MK_SERIALIZED_WITH_KEY_REPR = {
+      "json": """[{"pk": "datastore_types.Key.from_path('ModelA', 'test')", """
+              """"model": "tests.modela", "fields": {}}]""",
+      "yaml": """-\n fields: {description: null}\n model: tests.modela\n """
+              """pk: \'datastore_types.Key.from_path("ModelA", "test")\'\n"""
+  }
+  MK_SERIALIZED_WITH_KEY_AS_TEXT = {
+      "json": """[{"pk": "test", "model": "tests.modela", "fields": {}}]""",
+      "yaml": """-\n fields: {description: null}\n model: tests.modela\n """
+              """pk: test\n"""
+  }
+
+  # Lookup dict for the function.
+  SERIALIZED_WITH_NON_EXISTANT_PARENT = {
+      "json": """[{"pk": "ahhnb29nbGUtYXBwLWVuZ2luZS1kamFuZ29yIgsSBk1vZG"""
+              """VsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw", """
+              """"model": "tests.modela", "fields": """
+              """{"description": null}}]""",
+      "yaml": """- fields: {description: null}\n  """
+              """model: tests.modela\n  """
+              """pk: ahhnb29nbGUtYXBwLWVuZ2luZS1kamFuZ29yIgsSBk1"""
+              """vZGVsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw\n""",
+      "xml":  """<?xml version="1.0" encoding="utf-8"?>\n"""
+              """<django-objects version="1.0">\n"""
+              """<entity kind="tests.modela" key="ahhnb29nbGUtYXBwL"""
+              """WVuZ2luZS1kamFuZ29yIgsSBk1vZGVsQiIGcGFyZW50DA"""
+              """sSBk1vZGVsQSIEdGVzdAw">\n  """
+              """<key>tag:google-app-engine-django.gmail.com,"""
+              """2008-05-13:ModelA[ahhnb29nbGUtYXBwLWVuZ2luZS1kam"""
+              """FuZ29yIgsSBk1vZGVsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw"""
+              """]</key>\n  <property name="description" """
+              """type="null"></property>\n</entity>\n</django-objects>"""
+  }
+
+  # The following functions are all expanded by the metaclass to be run once
+  # for every registered Django serialization module.
+
+  def runKeyedObjectTest(self, format):
+    """Test serialization of a basic object with a named key."""
+    obj = ModelA(description="test object", key_name="test")
+    obj.put()
+    self.doSerialisationTest(format, obj)
+
+  def runObjectWithIdTest(self, format):
+    """Test serialization of a basic object with a numeric ID key."""
+    obj = ModelA(description="test object")
+    obj.put()
+    self.doSerialisationTest(format, obj)
+
+  def runObjectWithReferenceTest(self, format):
+    """Test serialization of an object that references another object."""
+    obj = ModelA(description="test object", key_name="test")
+    obj.put()
+    obj2 = ModelB(description="friend object", friend=obj)
+    obj2.put()
+    self.doSerialisationTest(format, obj2, "friend", obj)
+
+  def runObjectWithParentTest(self, format):
+    """Test serialization of an object that has a parent object reference."""
+    obj = ModelA(description="parent object", key_name="parent")
+    obj.put()
+    obj2 = ModelA(description="child object", key_name="child", parent=obj)
+    obj2.put()
+    self.doSerialisationTest(format, obj2, "parent", obj)
+
+  def runObjectWithNonExistantParentTest(self, format):
+    """Test deserialization of an object referencing a non-existant parent."""
+    self.doModelKeyDeserialisationReferenceTest(
+        self.SERIALIZED_WITH_NON_EXISTANT_PARENT, format)
+
+  def runCreateKeyReferenceFromListTest(self, format):
+    """Tests that a reference specified as a list in json/yaml can be loaded OK."""
+    self.doLookupDeserialisationReferenceTest(self.SERIALIZED_WITH_KEY_AS_LIST,
+                                              format)
+
+  def runCreateKeyReferenceFromReprTest(self, format):
+    """Tests that a reference specified as repr(Key) in can loaded OK."""
+    self.doLookupDeserialisationReferenceTest(self.SERIALIZED_WITH_KEY_REPR,
+                                              format)
+
+  def runCreateModelKeyFromListTest(self, format):
+    """Tests that a model key specified as a list can be loaded OK."""
+    self.doModelKeyDeserialisationReferenceTest(self.MK_SERIALIZED_WITH_LIST,
+                                                format)
+
+  def runCreateModelKeyFromReprTest(self, format):
+    """Tests that a model key specified as a repr(Key) can be loaded OK."""
+    self.doModelKeyDeserialisationReferenceTest(
+        self.MK_SERIALIZED_WITH_KEY_REPR, format)
+
+  def runCreateModelKeyFromTextTest(self, format):
+    """Tests that a reference specified as a plain key_name loads OK."""
+    self.doModelKeyDeserialisationReferenceTest(
+        self.MK_SERIALIZED_WITH_KEY_AS_TEXT, format)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/auth b/auth
new file mode 160000
index 0000000000000000000000000000000000000000..373a8b40b793d0fe58f69ebe807f5f51f077a0db
--- /dev/null
+++ b/auth
@@ -0,0 +1 @@
+Subproject commit 373a8b40b793d0fe58f69ebe807f5f51f077a0db
diff --git a/helios b/helios
new file mode 160000
index 0000000000000000000000000000000000000000..0f70fed4e89092595296d0c84cc1276a51194e3d
--- /dev/null
+++ b/helios
@@ -0,0 +1 @@
+Subproject commit 0f70fed4e89092595296d0c84cc1276a51194e3d
diff --git a/iacr/__init__.py b/iacr/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5946f83bd3fd5d7836484fd751730f0b8c378fb
--- /dev/null
+++ b/iacr/__init__.py
@@ -0,0 +1,9 @@
+"""
+This django app is meant only to connect the pieces of Helios and Auth that are specific to Votwee
+"""
+
+import glue
+
+import helios
+
+helios.TEMPLATE_BASE = "votwee/templates/base.html"
\ No newline at end of file
diff --git a/iacr/glue.py b/iacr/glue.py
new file mode 100644
index 0000000000000000000000000000000000000000..89cc38ba052cf7cd82a8616c47baf3767c70cd7d
--- /dev/null
+++ b/iacr/glue.py
@@ -0,0 +1,19 @@
+"""
+Glue some events together 
+"""
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+import helios.views, helios.signals
+
+import views
+
+def vote_cast_update_status(user, election, cast_vote, **kwargs):
+  pass
+  
+helios.signals.vote_cast.connect(vote_cast_update_status)
+
+def election_tallied(election, **kwargs):
+  pass
+
+helios.signals.election_tallied.connect(election_tallied)
\ No newline at end of file
diff --git a/iacr/media/main.css b/iacr/media/main.css
new file mode 100644
index 0000000000000000000000000000000000000000..5dc67e9f183b8406f92965d4d8745b7163ba7f4d
--- /dev/null
+++ b/iacr/media/main.css
@@ -0,0 +1,69 @@
+
+body {
+  font-family: 'Lucida Grande',sans-serif;
+  background: white;
+  padding: 0px;
+  margin: 0px;
+}
+
+#content {
+  position: absolute;
+  padding: 20px 30px 20px 30px;
+  top: 0px;
+  margin-left: 100px;
+  margin-top: 0px;
+  width: 860px;
+  background: #eee;
+  border-left: 1px solid #666;
+  border-right: 1px solid #666;
+  border-bottom: 1px solid #666;
+}
+
+#header {
+  padding-top: 0px;
+  text-align: center;
+  padding-bottom: 20px;
+}
+
+#footer {
+  border-top: 1px solid #666;
+  bottom: 0px;
+  margin: auto;
+  width: 860px;
+  text-align: center;
+  color: #666;
+  clear: both;
+}
+
+#footer a, #footer a:visited {
+  color: black;
+  text-decoration: none;
+}
+
+#footer a:hover {
+  text-decoration: underline;
+}
+
+#page h2 {
+  background: #fc9;
+  border-bottom: 1px solid #666;
+  padding: 5px 0px 2px 5px;
+}
+
+#election_info {
+  font-size: 16pt;
+}
+
+table.pretty {
+    margin: 1em 1em 1em 2em;
+    background: whitesmoke;
+    border-collapse: collapse;
+    width: 80%;
+}
+
+table.pretty th, td {
+    border: 1px silver solid;
+    padding: 0.3em;
+    vertical-align: top;
+}
+
diff --git a/iacr/templates/base.html b/iacr/templates/base.html
new file mode 100644
index 0000000000000000000000000000000000000000..023ad29ef6e9a7038060e94b033b0b07e17d9385
--- /dev/null
+++ b/iacr/templates/base.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html 
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+    dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"
+    xml:lang="{% firstof LANGUAGE_CODE 'en' %}"
+    lang="{% firstof LANGUAGE_CODE 'en' %}">
+  <head>
+    <title>{% block title %}{% endblock %} - IACR Helios</title>
+    {% block css %}
+      <!--
+          <link rel="stylesheet" type="text/css"  media="screen, projection" href="{{ MEDIA_URL }}combined-{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}.css" />
+      -->
+      <link rel="stylesheet" type="text/css" media="screen" href="/static/main.css">
+
+      <!--[if IE]>
+        <link rel="stylesheet" type="text/css" media="screen, projection" href="{{ MEDIA_URL }}ie.css">
+      <![endif]-->
+    {% endblock %}
+
+    <script language="javascript" src="/static/helios/helios/jquery-1.2.2.min.js"></script>
+
+    {% block js %}
+    {% endblock %}
+
+    {% block extra-head %}{% endblock %}
+  </head>
+
+  <body>
+    <div id="content">
+        <div id="header">
+        {% block header %}
+{% if user %}
+logged in as <b>{{user.name}} ({{user.user_type}})</b>
+[<a href="{% url auth.views.logout %}?return_url={{CURRENT_URL}}">logout</a>]
+{% else %}
+not logged in. [<a href="{% url auth.views.index %}?return_url={{CURRENT_URL}}">login</a>]
+{% endif %}
+        {% endblock %}
+        </div>
+      {% block content %}{% endblock %}
+      <div id="footer">
+      </div>
+    </div>
+    
+  </body>
+</html>
diff --git a/iacr/templates/index.html b/iacr/templates/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..9c747336404d692f4ef201eebb5f90bd377c0791
--- /dev/null
+++ b/iacr/templates/index.html
@@ -0,0 +1,32 @@
+{% extends 'iacr/templates/base.html' %}
+{% block title %}IACR Helios{% endblock %}
+
+{% block content %}
+<h1>Welcome to IACR Helios</h1>
+
+{% if user %}
+<p style="font-size: 1.4em;">
+logged in as <b>{{user.name}}</b>.
+</p>
+
+<p>
+    {{elections|length}} elections administered: 
+{% for e in elections %}
+<a href="{% url helios.views.one_election_view e.uuid %}">{{e.name}}</a>&nbsp;&nbsp;&nbsp;
+{% endfor %}
+</p>
+
+<p>
+    {{elections_registered|length}} elections for which you're registered: 
+{% for e in elections_registered %}
+<a href="{% url helios.views.one_election_view e.uuid %}">{{e.name}}</a>&nbsp;&nbsp;&nbsp;
+{% endfor %}
+</p>
+
+<p>
+    <a href="{% url helios.views.election_new %}">create an election</a>
+</p>
+{% else %}
+<a href="{% url auth.views.index %}">log in</a>
+{% endif %}
+{% endblock %}
diff --git a/iacr/urls.py b/iacr/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4350418f2fddddd1b6d647a5051c0ae0b44cfe7
--- /dev/null
+++ b/iacr/urls.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import *
+
+from views import *
+
+urlpatterns = patterns('',
+  (r'^$', home),
+  (r'^about$', about)
+)
diff --git a/iacr/view_utils.py b/iacr/view_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..e06337af54fe398a70ef04237d642eee399936a5
--- /dev/null
+++ b/iacr/view_utils.py
@@ -0,0 +1,23 @@
+"""
+Utilities for iacr views
+
+Ben Adida (2009-07-18)
+"""
+
+from django.template import Context, Template, loader
+from django.http import HttpResponse, Http404
+from django.shortcuts import render_to_response
+
+from auth.security import get_user
+
+##
+## template abstraction
+##
+def render_template(request, template_name, vars = {}):
+  t = loader.get_template(template_name + '.html')
+  
+  vars_with_user = vars.copy()
+  vars_with_user['user'] = get_user(request)
+  
+  return render_to_response('iacr/templates/%s.html' % template_name, vars_with_user)
+  
diff --git a/iacr/views.py b/iacr/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..8479e4d3bbec7c578acc6e7edd4b3bbaefd101a5
--- /dev/null
+++ b/iacr/views.py
@@ -0,0 +1,30 @@
+"""
+Votwee specific views
+"""
+
+from helios.models import *
+from auth.security import *
+from view_utils import *
+
+import helios.views
+
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseNotAllowed
+
+def home(request):
+  user = get_user(request)
+  if user:
+    elections = Election.get_by_user_as_admin(user)
+    elections_registered = Election.get_by_user_as_voter(user)
+  else:
+    elections = []
+    elections_registered = []
+    
+  return render_template(request, "index", {'elections' : elections, 'elections_registered' : elections_registered})
+  
+def about(request):
+  return HttpResponse(request, "about")
+    
+def election_shortcut(request, election_short_name):
+  election = Election.get_by_short_name(election_short_name)
+  return HttpResponseRedirect(reverse(helios.views.one_election_view, args=[election.uuid]))
\ No newline at end of file
diff --git a/settings.py b/settings.py
index cd44a9e8521799ee659e6f23717bf5530e817eba..d70442d9f3ff0829e37000e0b8de9d6c86a7f324 100644
--- a/settings.py
+++ b/settings.py
@@ -117,7 +117,7 @@ INSTALLED_APPS = (
     'appengine_django',
     'auth',
     'helios',
-    'votwee',
+    'iacr',
 )
 
 
@@ -125,4 +125,4 @@ APPEND_SLASH = False
 DEBUG = True
 TEMPLATE_DEBUG = True
 
-URL_HOST = "http://votwee.com"
\ No newline at end of file
+URL_HOST = "https://iacr-helios.appspot.com"
\ No newline at end of file
diff --git a/urls.py b/urls.py
index 62387888e41d424b33c4efbff57ab05eb0a14bb4..564c2f8f310f3e2d5ff3f48952be9adf04a91bb0 100644
--- a/urls.py
+++ b/urls.py
@@ -5,9 +5,5 @@ from django.contrib import admin
 urlpatterns = patterns('',
     (r'^auth/', include('auth.urls')),
     (r'^helios/', include('helios.urls')),
-    (r'^', include('votwee.urls')),
-
-    # static hack -- production should bypass this route
-    #(r'^static/helios/(?P<path>.*)$', 'django.views.static.serve',
-    #        {'document_root': '/web/votwee/helios/media/helios/'}),
+    (r'^', include('iacr.urls')),
 )
\ No newline at end of file