[cig-commits] r8306 - in cs/portal/trunk: . hello hello/hello hello/hello/site hello/hello/static hello/hello/templates hello/hello/templates/hello hello/hello/templates/registration

leif at geodynamics.org leif at geodynamics.org
Mon Nov 19 17:50:32 PST 2007


Author: leif
Date: 2007-11-19 17:50:31 -0800 (Mon, 19 Nov 2007)
New Revision: 8306

Added:
   cs/portal/trunk/hello/
   cs/portal/trunk/hello/ez_setup.py
   cs/portal/trunk/hello/hello/
   cs/portal/trunk/hello/hello/__init__.py
   cs/portal/trunk/hello/hello/constants.py
   cs/portal/trunk/hello/hello/forms.py
   cs/portal/trunk/hello/hello/middleware.py
   cs/portal/trunk/hello/hello/models.py
   cs/portal/trunk/hello/hello/notifications.py
   cs/portal/trunk/hello/hello/site/
   cs/portal/trunk/hello/hello/site/__init__.py
   cs/portal/trunk/hello/hello/site/manage.py
   cs/portal/trunk/hello/hello/site/settings.py
   cs/portal/trunk/hello/hello/site/urls.py
   cs/portal/trunk/hello/hello/static/
   cs/portal/trunk/hello/hello/static/cig.gif
   cs/portal/trunk/hello/hello/static/input.txt
   cs/portal/trunk/hello/hello/static/style.css
   cs/portal/trunk/hello/hello/templates/
   cs/portal/trunk/hello/hello/templates/404.html
   cs/portal/trunk/hello/hello/templates/500.html
   cs/portal/trunk/hello/hello/templates/hello/
   cs/portal/trunk/hello/hello/templates/hello/base.html
   cs/portal/trunk/hello/hello/templates/hello/home.html
   cs/portal/trunk/hello/hello/templates/hello/parameters.txt
   cs/portal/trunk/hello/hello/templates/hello/register.html
   cs/portal/trunk/hello/hello/templates/hello/registration_form.html
   cs/portal/trunk/hello/hello/templates/hello/root.html
   cs/portal/trunk/hello/hello/templates/hello/settings.html
   cs/portal/trunk/hello/hello/templates/hello/simulation_confirm_delete.html
   cs/portal/trunk/hello/hello/templates/hello/simulation_detail.html
   cs/portal/trunk/hello/hello/templates/hello/simulation_form.html
   cs/portal/trunk/hello/hello/templates/hello/simulation_list.html
   cs/portal/trunk/hello/hello/templates/hello/simulation_list.py
   cs/portal/trunk/hello/hello/templates/hello/simulation_status.html
   cs/portal/trunk/hello/hello/templates/hello/simulations.html
   cs/portal/trunk/hello/hello/templates/hello/splash.html
   cs/portal/trunk/hello/hello/templates/hello/userinfo_form.html
   cs/portal/trunk/hello/hello/templates/registration/
   cs/portal/trunk/hello/hello/templates/registration/login.html
   cs/portal/trunk/hello/hello/templates/registration/pwchange.html
   cs/portal/trunk/hello/hello/templates/registration/pwchange_done.html
   cs/portal/trunk/hello/hello/templates/registration/pwreset.html
   cs/portal/trunk/hello/hello/templates/registration/pwreset_done.html
   cs/portal/trunk/hello/hello/templates/registration/pwreset_email.txt
   cs/portal/trunk/hello/hello/urls.py
   cs/portal/trunk/hello/hello/views.py
   cs/portal/trunk/hello/setup.py
Log:
Created a simple "hello world" web portal based upon the SPECFEM web
portal.


Added: cs/portal/trunk/hello/ez_setup.py
===================================================================
--- cs/portal/trunk/hello/ez_setup.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/ez_setup.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,222 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c3"
+DEFAULT_URL     = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+}
+
+import sys, os
+
+def _validate_md5(egg_name, data):
+    if egg_name in md5_data:
+        from md5 import md5
+        digest = md5(data).hexdigest()
+        if digest != md5_data[egg_name]:
+            print >>sys.stderr, (
+                "md5 validation of %s failed!  (Possible download problem?)"
+                % egg_name
+            )
+            sys.exit(2)
+    return data
+
+
+def use_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    download_delay=15
+):
+    """Automatically find/download setuptools and make it available on sys.path
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end with
+    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
+    it is not already available.  If `download_delay` is specified, it should
+    be the number of seconds that will be paused before initiating a download,
+    should one be required.  If an older version of setuptools is installed,
+    this routine will print a message to ``sys.stderr`` and raise SystemExit in
+    an attempt to abort the calling script.
+    """
+    try:
+        import setuptools
+        if setuptools.__version__ == '0.0.1':
+            print >>sys.stderr, (
+            "You have an obsolete version of setuptools installed.  Please\n"
+            "remove it from your system entirely before rerunning this script."
+            )
+            sys.exit(2)
+    except ImportError:
+        egg = download_setuptools(version, download_base, to_dir, download_delay)
+        sys.path.insert(0, egg)
+        import setuptools; setuptools.bootstrap_install_from = egg
+
+    import pkg_resources
+    try:
+        pkg_resources.require("setuptools>="+version)
+
+    except pkg_resources.VersionConflict, e:
+        # XXX could we install in a subprocess here?
+        print >>sys.stderr, (
+            "The required version of setuptools (>=%s) is not available, and\n"
+            "can't be installed while this script is running. Please install\n"
+            " a more recent version first.\n\n(Currently using %r)"
+        ) % (version, e.args[0])
+        sys.exit(2)
+
+def download_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    delay = 15
+):
+    """Download setuptools from a specified location and return its filename
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download attempt.
+    """
+    import urllib2, shutil
+    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+    url = download_base + egg_name
+    saveto = os.path.join(to_dir, egg_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            from distutils import log
+            if delay:
+                log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help).  I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+   %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+                    version, download_base, delay, url
+                ); from time import sleep; sleep(delay)
+            log.warn("Downloading %s", url)
+            src = urllib2.urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = _validate_md5(egg_name, src.read())
+            dst = open(saveto,"wb"); dst.write(data)
+        finally:
+            if src: src.close()
+            if dst: dst.close()
+    return os.path.realpath(saveto)
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+
+    try:
+        import setuptools
+    except ImportError:
+        egg = None
+        try:
+            egg = download_setuptools(version, delay=0)
+            sys.path.insert(0,egg)
+            from setuptools.command.easy_install import main
+            return main(list(argv)+[egg])   # we're done here
+        finally:
+            if egg and os.path.exists(egg):
+                os.unlink(egg)
+    else:
+        if setuptools.__version__ == '0.0.1':
+            # tell the user to uninstall obsolete version
+            use_setuptools(version)
+
+    req = "setuptools>="+version
+    import pkg_resources
+    try:
+        pkg_resources.require(req)
+    except pkg_resources.VersionConflict:
+        try:
+            from setuptools.command.easy_install import main
+        except ImportError:
+            from easy_install import main
+        main(list(argv)+[download_setuptools(delay=0)])
+        sys.exit(0) # try to force an exit
+    else:
+        if argv:
+            from setuptools.command.easy_install import main
+            main(argv)
+        else:
+            print "Setuptools version",version,"or greater has been installed."
+            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+
+
+def update_md5(filenames):
+    """Update our built-in md5 registry"""
+
+    import re
+    from md5 import md5
+
+    for name in filenames:
+        base = os.path.basename(name)
+        f = open(name,'rb')
+        md5_data[base] = md5(f.read()).hexdigest()
+        f.close()
+
+    data = ["    %r: %r,\n" % it for it in md5_data.items()]
+    data.sort()
+    repl = "".join(data)
+
+    import inspect
+    srcfile = inspect.getsourcefile(sys.modules[__name__])
+    f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+    match = re.search("\nmd5_data = {\n([^}]+)}", src)
+    if not match:
+        print >>sys.stderr, "Internal error!"
+        sys.exit(2)
+
+    src = src[:match.start(1)] + repl + src[match.end(1):]
+    f = open(srcfile,'w')
+    f.write(src)
+    f.close()
+
+
+if __name__=='__main__':
+    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+        update_md5(sys.argv[2:])
+    else:
+        main(sys.argv[1:])
+
+
+
+
+

Added: cs/portal/trunk/hello/hello/__init__.py
===================================================================

Added: cs/portal/trunk/hello/hello/constants.py
===================================================================
--- cs/portal/trunk/hello/hello/constants.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/constants.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,4 @@
+
+URL_ROOT = '/hello'
+HOME = URL_ROOT + '/'
+APP_LABEL = __name__.split('.')[-2]

Added: cs/portal/trunk/hello/hello/forms.py
===================================================================
--- cs/portal/trunk/hello/hello/forms.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/forms.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,204 @@
+
+from django import forms
+from django.contrib.auth import authenticate, login
+from django.contrib.auth.models import User
+from django.core import validators
+from models import UserInfo, Invite
+from constants import *
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Registration
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+usernameTaken = "This username is already taken."
+
+
+def isNotExistingUser(field_data, all_data):
+    try:
+        User.objects.get(username = field_data)
+    except User.DoesNotExist:
+        return
+    raise validators.ValidationError(usernameTaken)
+
+
+def passwordsMatch(field_data, all_data):
+    "Validates that the two password fields match."
+    if field_data != all_data['password1']:
+        raise validators.ValidationError, "Your passwords didn't match."
+    return
+
+
+def isValidInvitationCode(field_data, all_data):
+    try:
+        invite = Invite.objects.get(code = field_data)
+    except Invite.DoesNotExist:
+        raise validators.ValidationError, "Invalid invitation code."
+    if invite.hasExpired():
+        raise validators.ValidationError, "This invitation code has expired."
+    return
+
+
+class RegistrationManipulator(forms.Manipulator):
+    
+    def __init__(self):
+        self.fields = [
+            # User
+            forms.TextField('first_name',   maxlength=30,  is_required=True),
+            forms.TextField('last_name',    maxlength=30,  is_required=True),
+            forms.EmailField('email',                      is_required=True,  validator_list=[self.isUniqueEmail]),
+            # UserInfo
+            forms.TextField('institution',  maxlength=100, is_required=True),
+            forms.TextField('address1',     maxlength=100),
+            forms.TextField('address2',     maxlength=100),
+            forms.TextField('address3',     maxlength=100),
+            forms.PhoneNumberField('phone'),
+        ]
+
+    def usernameValidatorList(self):
+        return [validators.isAlphaNumeric]
+
+
+class RegistrationAddManipulator(RegistrationManipulator):
+    
+    def __init__(self, request):
+        super(RegistrationAddManipulator, self).__init__()
+        self.request = request
+        self.fields.extend([
+            forms.TextField('username',     maxlength=30,  is_required=True, validator_list=self.usernameValidatorList()),
+            forms.PasswordField('password1', maxlength=128, is_required=True),
+            forms.PasswordField('password2', maxlength=128, is_required=True, validator_list=[passwordsMatch]),
+            
+            forms.TextField('invite', maxlength=128, validator_list=[isValidInvitationCode]),
+            ])
+        
+    def save(self, new_data):
+
+        # Check the invitation code, if given.
+        approved = False
+        invite = None
+        inviteCode = new_data['invite']
+        if inviteCode:
+            invite = Invite.objects.get(code = inviteCode)
+            approved = True
+        
+        user, created = User.objects.get_or_create(
+            username = new_data['username'],
+            defaults = {'first_name': new_data['first_name'],
+                        'last_name':  new_data['last_name'],
+                        'email':      new_data['email']})
+        if not created:
+            # Race: the username was just taken!
+            return None, {'username': [usernameTaken]}
+
+        # Now we're committed to creating the user account.
+        user.set_password(new_data['password1'])
+        user.save()
+        UserInfo.objects.create(
+            user        = user,
+            institution = new_data['institution'],
+            address1    = new_data['address1'],
+            address2    = new_data['address2'],
+            address3    = new_data['address3'],
+            phone       = new_data['phone'],
+            invite      = invite,
+            approved    = approved,
+            )
+        
+        # Log-in the new user.
+        user = authenticate(username=new_data['username'], password=new_data['password1'])
+        if user is not None:
+            login(self.request, user)
+        
+        return user, {}
+
+    def flatten_data(self):
+        return {}
+    
+    def usernameValidatorList(self):
+        validator_list = super(RegistrationAddManipulator, self).usernameValidatorList()
+        validator_list.append(isNotExistingUser)
+        validator_list.append(self.hasCookiesEnabled)
+        return validator_list
+
+    def hasCookiesEnabled(self, field_data, all_data):
+        if self.request and not self.request.session.test_cookie_worked():
+            raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
+
+    def isUniqueEmail(self, field_data, all_data):
+        try:
+            User.objects.get(email__iexact = field_data)
+        except User.DoesNotExist:
+            return
+        raise validators.ValidationError(
+"""Someone has already registered using this e-mail address. """
+"""(If you've forgotten your username and/or password, """
+"""try <a href="%s/pwreset/">resetting your password</a>.)""" % URL_ROOT
+    )
+
+    
+class RegistrationChangeManipulator(RegistrationManipulator):
+
+    def __init__(self, user):
+        super(RegistrationChangeManipulator, self).__init__()
+        self.user = user
+    
+    def flatten_data(self):
+        new_data = {}
+        new_data.update(self.user.__dict__)
+        try:
+            userInfo = self.user.userinfo
+        except UserInfo.DoesNotExist:
+            pass
+        else:
+            new_data.update(userInfo.__dict__)
+        return new_data
+    
+    def save(self, new_data):
+        # Create UserInfo if it doesn't exist.
+        user = self.user
+        try:
+            userInfo = user.userinfo
+        except UserInfo.DoesNotExist:
+            userInfo = UserInfo()
+            user.userinfo = userInfo
+        # Save the new user.
+        user.first_name      = new_data['first_name']
+        user.last_name       = new_data['last_name']
+        user.email           = new_data['email']
+        userInfo.institution = new_data['institution']
+        userInfo.address1    = new_data['address1']
+        userInfo.address2    = new_data['address2']
+        userInfo.address3    = new_data['address3']
+        userInfo.phone       = new_data['phone']
+        user.save()
+        userInfo.save()
+        return user, {}
+
+    def isUniqueEmail(self, field_data, all_data):
+        try:
+            u = User.objects.get(email__iexact = field_data)
+        except User.DoesNotExist:
+            return
+        if u == self.user:
+            return
+        raise validators.ValidationError("Someone has already registered using this e-mail address (but under a different username).")
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Hidden Daemon Forms
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+class SimulationStatusManipulator(forms.Manipulator):
+
+    def __init__(self):
+        self.fields = [
+            forms.TextField(field_name='status', is_required=True),
+            forms.FileUploadField(field_name='output'),
+            ]
+        return
+
+
+# end of file

Added: cs/portal/trunk/hello/hello/middleware.py
===================================================================
--- cs/portal/trunk/hello/hello/middleware.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/middleware.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,147 @@
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# "Django Admin Hack - Fields varying with user permissions"
+# by Luke Plant
+# with Python 2.3 patch by Joseph Kocherhans
+#
+# from http://lukeplant.me.uk/blog.php?id=1107301634
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+try:
+    # It wasn't until Python 2.4 that they realized they needed this...
+    from threading import local
+except ImportError:
+    # ...thankfully, Django includes a substitute.
+    from django.utils._threading_local import local
+
+_thread_locals = local()
+
+class You_need_to_add_ThreadLocals_to_MIDDLEWARE_CLASSES(object): pass
+
+def get_current_user():
+    return getattr(_thread_locals, 'user', You_need_to_add_ThreadLocals_to_MIDDLEWARE_CLASSES())
+
+# Add this to MIDDLEWARE_CLASSES.
+class ThreadLocals(object):
+    """Middleware that gets various objects from the request object
+    and saves them in thread local storage.
+
+    """
+
+    def process_request(self, request):
+        _thread_locals.user = getattr(request, 'user', None)
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Revised django/contrib/sessions/middleware.py
+#
+# The following was backported from Django 0.96 to fix a bug which
+# causes Django to die with a SuspiciousOperation when a visitor
+# arrives with an old, stale cookie (one generated with a different
+# SECRET_KEY)... or (theoretically) when a hacker arrives with a bogus
+# cookie.
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+from django.conf import settings
+from django.contrib.sessions.models import Session
+from django.core.exceptions import SuspiciousOperation
+from django.utils.cache import patch_vary_headers
+import datetime
+
+TEST_COOKIE_NAME = 'testcookie'
+TEST_COOKIE_VALUE = 'worked'
+
+class SessionWrapper(object):
+    def __init__(self, session_key):
+        self.session_key = session_key
+        self.accessed = False
+        self.modified = False
+
+    def __contains__(self, key):
+        return key in self._session
+
+    def __getitem__(self, key):
+        return self._session[key]
+
+    def __setitem__(self, key, value):
+        self._session[key] = value
+        self.modified = True
+
+    def __delitem__(self, key):
+        del self._session[key]
+        self.modified = True
+
+    def keys(self):
+        return self._session.keys()
+
+    def items(self):
+        return self._session.items()
+
+    def get(self, key, default=None):
+        return self._session.get(key, default)
+
+    def set_test_cookie(self):
+        self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
+
+    def test_cookie_worked(self):
+        return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
+
+    def delete_test_cookie(self):
+        del self[TEST_COOKIE_NAME]
+
+    def _get_session(self):
+        # Lazily loads session from storage.
+        self.accessed = True
+        try:
+            return self._session_cache
+        except AttributeError:
+            if self.session_key is None:
+                self._session_cache = {}
+            else:
+                try:
+                    s = Session.objects.get(session_key=self.session_key,
+                        expire_date__gt=datetime.datetime.now())
+                    self._session_cache = s.get_decoded()
+                except (Session.DoesNotExist, SuspiciousOperation):
+                    self._session_cache = {}
+                    # Set the session_key to None to force creation of a new
+                    # key, for extra security.
+                    self.session_key = None
+            return self._session_cache
+
+    _session = property(_get_session)
+
+class SessionMiddleware(object):
+    def process_request(self, request):
+        request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
+
+    def process_response(self, request, response):
+        # If request.session was modified, or if response.session was set, save
+        # those changes and set a session cookie.
+        try:
+            accessed = request.session.accessed
+            modified = request.session.modified
+        except AttributeError:
+            pass
+        else:
+            if accessed:
+                patch_vary_headers(response, ('Cookie',))
+            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
+                if request.session.session_key:
+                    session_key = request.session.session_key
+                else:
+                    session_key = Session.objects.get_new_session_key()
+
+                if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
+                    max_age = None
+                    expires = None
+                else:
+                    max_age = settings.SESSION_COOKIE_AGE
+                    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
+                new_session = Session.objects.save(session_key, request.session._session,
+                    datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
+                response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
+                    max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN)
+        return response
+
+# end of file

Added: cs/portal/trunk/hello/hello/models.py
===================================================================
--- cs/portal/trunk/hello/hello/models.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/models.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,92 @@
+
+from django.db import models
+from django.contrib.auth.models import User
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Simulations
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+class Simulation(models.Model):
+    # general info
+    user = models.ForeignKey(User)
+    name = models.CharField(maxlength=100, core=True)
+    created = models.DateTimeField(auto_now_add=True, editable=False)
+    modified = models.DateTimeField(auto_now=True, editable=False)
+
+    # parameters
+    friend = models.CharField(maxlength=100)
+    happy = models.BooleanField(default=True)
+    speed = models.FloatField(max_digits=19, decimal_places=10)
+    steps = models.IntegerField(core=True, default=10)
+    nodes = models.IntegerField(core=True, default=1) # number of processors
+
+    # run/job info from daemon
+    status = models.CharField(maxlength=100, default="new")
+    started = models.DateTimeField(auto_now_add=True, editable=False)
+    finished = models.DateTimeField(null=True, blank=True)
+
+    def __str__(self): return self.name
+    
+    class Admin:
+        list_display = ('user', 'name', 'status', 'started', 'finished')
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Registration
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+class Invite(models.Model):
+    code = models.CharField(maxlength=100, unique=True, db_index=True)
+    name = models.CharField(maxlength=100)
+    description = models.TextField(blank=True)
+    created = models.DateTimeField(auto_now_add=True, editable=False)
+    modified = models.DateTimeField(auto_now=True, editable=False)
+    expires = models.DateTimeField(null=True, blank=True)
+
+    def __str__(self): return self.name
+
+    def hasExpired(self):
+        import datetime
+        if self.expires is None:
+            return False
+        now = datetime.datetime.now()
+        return self.expires < now
+
+    class Admin:
+        list_display = ('name', 'code', 'expires', 'hasExpired')
+
+
+class UserInfo(models.Model):
+    user = models.OneToOneField(User)
+    institution = models.CharField(maxlength=100, core=True)
+    address1 = models.CharField(maxlength=100, null=True, blank=True)
+    address2 = models.CharField(maxlength=100, null=True, blank=True)
+    address3 = models.CharField(maxlength=100, null=True, blank=True)
+    phone = models.PhoneNumberField(null=True, blank=True)
+
+    # the invitation used, if any
+    invite = models.ForeignKey(Invite, null=True, blank=True)
+
+    # 'True' if this user can run simulations.  Users who enter a
+    # valid invitation code are automatically approved.
+    approved = models.BooleanField(default=False)
+    help_visible = models.BooleanField(default=True)
+
+    def __str__(self): return self.user.username
+
+    # forward to User
+    def username(self): return self.user.username
+    def first_name(self): return self.user.first_name
+    def last_name(self): return self.user.last_name
+
+    class Admin:
+        # This is an admittedly crude way to allow admins to scan for
+        # unapproved users in the 'admin' interface.
+        list_display = ('username', 'first_name', 'last_name', 'invite', 'approved')
+        list_filter = ('approved',)
+
+
+# end of file

Added: cs/portal/trunk/hello/hello/notifications.py
===================================================================
--- cs/portal/trunk/hello/hello/notifications.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/notifications.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,75 @@
+
+from django.core.mail import send_mail, mail_admins, mail_managers
+from constants import *
+
+
+def notify_user_of_successful_simulation(sim):
+
+    user = sim.user
+
+    subject = 'The simulation "%s" has finished running.' % sim.name
+    message = ['The simulation "%s" has finished running.' % sim.name,
+               "",
+               "To download the output, visit the following web page:",
+               "",
+               "https://crust.geodynamics.org%s/simulations/%d/" % (URL_ROOT, sim.id),
+               "",
+               "Sincerely,",
+               "The Web Portal",
+               ]
+    message = "\n".join(message)
+    
+    send_mail(subject, message, "Web Portal <portal at geodynamics.org>", [user.email], fail_silently=True)
+    
+    return
+
+
+def notify_admins_of_failed_simulation(sim):
+
+    subject = 'simumlation %d failed' % sim.id
+    message = ["Simulation %d has failed." % sim.id,
+               "",
+               ]
+    message = "\n".join(message)
+    
+    mail_admins(subject, message, fail_silently=True)
+    
+    return
+
+
+def notify_managers_of_new_user(request, user):
+
+    userInfo = user.userinfo
+    invite = userInfo.invite
+    
+    subject = 'new user %s (%s)' % (user.username, invite or "UNINVITED")
+    message = ["A new user has registered.",
+               ""]
+    if not invite:
+        message.extend([
+            """NOTE: Before this new user can run simulations, you must approve them by selecting the "approved" checkbox on the following page:""",
+            "",
+            "https://crust.geodynamics.org/admin/%s/userinfo/%d/" % (APP_LABEL, userInfo._get_pk_val()),
+            "",
+            ])
+    message.extend([
+        "username:" + user.username,
+        "first name: " + user.first_name,
+        "last name: " + user.last_name,
+        "e-mail: " + user.email,
+        "institution: " + userInfo.institution,
+        "address1: " + userInfo.address1,
+        "address2: " + userInfo.address2,
+        "address3: " + userInfo.address3,
+        "phone: " + userInfo.phone,
+        "invitation: " + str(invite),
+        "",
+        ])
+    message = "\n".join(message)
+    
+    mail_managers(subject, message, fail_silently=True)
+    
+    return
+
+
+# end of file

Added: cs/portal/trunk/hello/hello/site/__init__.py
===================================================================

Added: cs/portal/trunk/hello/hello/site/manage.py
===================================================================
--- cs/portal/trunk/hello/hello/site/manage.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/site/manage.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)

Added: cs/portal/trunk/hello/hello/site/settings.py
===================================================================
--- cs/portal/trunk/hello/hello/site/settings.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/site/settings.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,93 @@
+# Django settings for mysite project.
+
+import os
+
+DEBUG = os.environ.has_key("WEBPORTAL_DEBUG")
+TEMPLATE_DEBUG = DEBUG
+
+if DEBUG:
+    ADMINS = (
+        ("Leif", "leif at geodynamics.org"),
+        )
+else:
+    ADMINS = (
+        ("CIG Portal", "portal at geodynamics.org"),
+        ("Leif", "leif at geodynamics.org"),
+        )
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3'           # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+# Environment Example: export WEBPORTAL_DATABASE_NAME='/Users/leif/Projects/WebPortal/SiteDatabase.db'             # Or path to database file if using sqlite3.
+DATABASE_NAME = os.environ.get("WEBPORTAL_DATABASE_NAME")
+DATABASE_USER = ''             # Not used with sqlite3.
+DATABASE_PASSWORD = ''         # Not used with sqlite3.
+DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+assert DATABASE_NAME # setenv WEBPORTAL_DATABASE_NAME
+
+# Local time zone for this installation. All choices can be found here:
+# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+TIME_ZONE = 'US/Pacific'
+
+# Language code for this installation. All choices can be found here:
+# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+# http://blogs.law.harvard.edu/tech/stories/storyReader$15
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# Absolute path to the directory that holds media.
+# Example:             "/home/media/media.lawrence.com/"
+# Environment Example: export WEBPORTAL_MEDIA_ROOT= '/Users/leif/Projects/WebPortal'
+MEDIA_ROOT = os.environ.get("WEBPORTAL_MEDIA_ROOT")
+assert MEDIA_ROOT # setenv WEBPORTAL_MEDIA_ROOT
+
+# URL that handles the media served from MEDIA_ROOT.
+# Example: "http://media.lawrence.com"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = os.environ.get("WEBPORTAL_SECRET_KEY")
+assert SECRET_KEY # setenv WEBPORTAL_SECRET_KEY
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+    'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.middleware.doc.XViewMiddleware',
+)
+
+ROOT_URLCONF = 'hello.site.urls'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates".
+    # Always use forward slashes, even on Windows.
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.admin',
+    'hello',
+)
+
+#DEFAULT_CHARSET = 'utf-16'
+#DEFAULT_CHARSET = 'us-ascii' # otherwise e-mails are base64-encoded
+
+DEFAULT_FROM_EMAIL = "Leif Strand <leif at geodynamics.org>"
+SERVER_EMAIL = "Leif Strand <leif at geodynamics.org>"

Added: cs/portal/trunk/hello/hello/site/urls.py
===================================================================
--- cs/portal/trunk/hello/hello/site/urls.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/site/urls.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,14 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    # Example:
+    # (r'^mysite/', include('mysite.apps.foo.urls.foo')),
+
+    # Uncomment this for admin:
+    (r'^admin/', include('django.contrib.admin.urls')),
+
+    #(r'^accounts/login/$', 'django.contrib.auth.views.login'),
+
+    (r'^hello/', include('hello.urls')),
+
+)

Added: cs/portal/trunk/hello/hello/static/cig.gif
===================================================================
(Binary files differ)


Property changes on: cs/portal/trunk/hello/hello/static/cig.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: cs/portal/trunk/hello/hello/static/input.txt
===================================================================
--- cs/portal/trunk/hello/hello/static/input.txt	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/static/input.txt	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1 @@
+Hello, world.

Added: cs/portal/trunk/hello/hello/static/style.css
===================================================================
--- cs/portal/trunk/hello/hello/static/style.css	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/static/style.css	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,168 @@
+
+img {
+    border-width: 0;
+}
+
+#logo {
+    vertical-align: middle;
+}
+
+.error {
+    font-weight: bold;
+    color: red;
+}
+
+.messages {
+    font-weight: bold;
+    padding: .2em .2em .2em .2em;
+    border: thin solid black;
+    background-color: yellow;
+}
+
+div.login {
+    float: right;
+}
+
+.login li {
+    list-style: none;
+    display: inline;
+    padding-left: .5em;
+    padding-right: .5em;
+}
+
+
+/* taskbar */
+
+div.taskbar {
+    clear: both;
+}
+
+.taskbar ul {
+    list-style: none;
+    border-top: 1px solid black;
+    border-bottom: 1px solid black;
+    padding-top: 2px;
+    padding-bottom: 2px;
+    margin-left: 0;
+    background-color: #eee;
+    line-height: 200%;
+}
+
+.taskbar li {
+    display: inline;
+    border-style: outset;
+    font-size: large;
+    margin-left: .1em;
+    margin-right: .1em;
+}
+
+.taskbar a {
+    text-decoration: none;
+    color: black;
+    padding-left: 1em;
+    padding-right: 1em;
+}
+
+.taskbar li.selected {
+    border-style: inset;
+    font-weight: bold;
+    color: black;
+    background-color: orange;
+}
+
+
+/* toolbars */
+
+div.toolbar {
+    padding: .4em 1em .4em 1em;
+    margin: .6em 0em .6em 0em;
+    border-top: 1px solid black;
+    border-bottom: 1px solid black;
+    background-color: #eee;
+}
+
+.toolbar form {
+    display: inline;
+}
+
+
+/* tabs */
+
+div.tabs {
+    clear: both;
+}
+
+.tabs ul {
+    list-style: none;
+    margin-left: 0;
+    border-bottom: thick solid orange;
+}
+
+.tabs li {
+    display: inline;
+    border-top: 1px solid black;
+    border-right: 1px solid black;
+    font-size: large;
+}
+
+.tabs li.first {
+    border-left: 1px solid black;
+}
+
+.tabs a {
+    text-decoration: none;
+    color: black;
+    padding-left: 1em;
+    padding-right: 1em;
+}
+
+.tabs li.selected {
+    border-bottom: thin solid orange;
+    font-weight: bold;
+    color: black;
+    background-color: orange;
+}
+
+
+/* tables */
+
+table {
+    border: thin solid black;
+}
+
+.even {
+    background-color: #eef;
+}
+
+th {
+    text-align: left;
+    padding-left: 1em;
+    padding-right: 1em;
+}
+
+td {
+    padding-left: 1em;
+    padding-right: 1em;
+}
+
+
+/* forms */
+
+label {
+    font-weight: bold;
+}
+
+label.before:after {
+    content: ":";
+}
+
+
+/* detail views */
+
+.properties dt {
+    font-weight: bold;
+}
+
+.properties dt:after {
+    content: ":";
+}

Added: cs/portal/trunk/hello/hello/templates/404.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/404.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/404.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,10 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75>Home</a>
+
+<h1>Not Found</h1>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/500.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/500.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/500.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,10 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75>Home</a>
+
+<h1>Internal Server Error</h1>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/base.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/base.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/base.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,19 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<div class=login>
+    <ul>
+       <li id=greeting>{% if user.is_anonymous %}Welcome.{% else %}Welcome, {{ user.first_name }} ({{ user.username }}).{% endif %}
+       <li><a href="/hello/logout/">logout</a>
+    </ul>
+</div>
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75></a>
+{% if messages %}<span class=messages>{{ messages|join:" " }}</span>{% endif %}
+
+{% block desktop %}
+{% endblock %}
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/home.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/home.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/home.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,27 @@
+
+{% extends "hello/base.html" %}
+
+{% block desktop %}
+
+<div class=taskbar>
+    <ul>
+        <li class="first selected"><a href="/hello/">home</a>
+        <li><a href="/hello/simulations/">simulations</a>
+        <li><a href="/hello/registration/">profile</a>
+    </ul>
+</div>
+
+<h1 class=titlebar>home</h1>
+
+<h3>quick start</h3>
+
+<p>To set-up a new simulation, proceed as follows:
+
+<ol>
+    <li>Click "Simulations".
+    <li>Click "New...".
+    <li>Enter the parameters for the simulation.
+    <li>Click "Run".
+</ol>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/parameters.txt
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/parameters.txt	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/parameters.txt	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,5 @@
+friend: {{ object.friend }}
+happy: {{ object.happy }}
+speed: {{ object.speed }}
+steps: {{ object.steps }}
+nodes: {{ object.nodes }}

Added: cs/portal/trunk/hello/hello/templates/hello/register.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/register.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/register.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,17 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75>Home</a>
+
+<h1 class=titlebar>new user registration</h1>
+
+<p>To create a new account, simply fill-out the form below.
+
+<p>If you've registered before, but have simply forgotten your username and/or password, try <a href="/hello/pwreset/">resetting your password</a>.
+   If you need help, <a href="mailto:portal at geodynamics.org">contact us</a>.
+
+{% include "hello/registration_form.html" %}
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/registration_form.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/registration_form.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/registration_form.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,112 @@
+
+{% if form.has_errors %}
+<p><span class=error>Please correct the following error{{ form.error_dict|pluralize }}.</span>
+{% endif %}
+
+<form method="post" action="/hello/registration/">
+    <div class=tab30ex>
+
+    {% if user.is_anonymous %}
+    <fieldset><legend>username and password</legend>
+
+    <div>
+        <label for="id_username" class=before>username</label>
+        {{ form.username }}
+        {% if form.username.errors %}<span class=error>{{ form.username.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_password1" class=before>password</label>
+        {{ form.password1 }}
+        {% if form.password1.errors %}<span class=error>{{ form.password1.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_password2" class=before>confirm password</label>
+        {{ form.password2 }}
+        {% if form.password2.errors %}<span class=error>{{ form.password2.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    </fieldset>
+
+    {% endif %}
+
+    <fieldset><legend>contact information</legend>
+
+    <div>
+        <label for="id_first_name" class=before>first name</label>
+        {{ form.first_name }}
+        {% if form.first_name.errors %}<span class=error>{{ form.first_name.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_last_name" class=before>last name</label>
+        {{ form.last_name }}
+        {% if form.last_name.errors %}<span class=error>{{ form.last_name.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_email" class=before>e-mail</label>
+        {{ form.email }}
+        {% if form.email.errors %}<span class=error>{{ form.email.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_institution" class=before>institution</label>
+        {{ form.institution }}
+        {% if form.institution.errors %}<span class=error>{{ form.institution.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_address1" class=before>address</label>
+        {{ form.address1 }} (optional)
+        {% if form.address1.errors %}<span class=error>{{ form.address1.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_address2" class=before>city</label>
+        {{ form.address2 }} (optional)
+        {% if form.address2.errors %}<span class=error>{{ form.address2.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_address3" class=before>state &amp; ZIP</label>
+        {{ form.address3 }} (optional)
+        {% if form.address3.errors %}<span class=error>{{ form.address3.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_phone" class=before>phone number</label>
+        {{ form.phone }} (optional)
+        {% if form.phone.errors %}<span class=error>{{ form.phone.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    </fieldset>
+
+
+    {% if user.is_anonymous %}
+
+    <fieldset><legend>invitation</legend>
+
+    <p>If you were given an invitation code, enter it below.
+       A valid invitation enables you to start running simulations immediately!
+
+    <div>
+        <label for="id_invite" class=before>invitation code</label>
+        {{ form.invite }}
+        {% if form.invite.errors %}<span class=error>{{ form.invite.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    </fieldset>
+
+    {% endif %}
+
+    {% if user.is_anonymous %}
+    <div><input class=submit type="submit" value="Register"/></div>
+    {% else %}
+    <div><input class=submit type="submit" value="Save"/></div>
+    {% endif %}
+
+    </div> <!-- tab30ex -->
+
+</form>

Added: cs/portal/trunk/hello/hello/templates/hello/root.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/root.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/root.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,12 @@
+<!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" xml:lang="en" lang="en">
+<head>
+    <link href="/hello/static/style.css" type="text/css" rel="stylesheet" media="all" />
+    <title>{% block title %}Hello{% endblock %}</title>
+</head>
+<body>
+{% block body %}
+{% endblock %}
+</body>
+</html>

Added: cs/portal/trunk/hello/hello/templates/hello/settings.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/settings.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/settings.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,17 @@
+
+{% extends "hello/base.html" %}
+
+{% block desktop %}
+
+<div class=taskbar>
+    <ul>
+        <li class=first><a href="/hello/">home</a>
+        <li><a href="/hello/simulations/">simulations</a>
+        <li class=selected><a href="/hello/registration/">profile</a>
+    </ul>
+</div>
+
+{% block content %}
+{% endblock %}
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/simulation_confirm_delete.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulation_confirm_delete.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulation_confirm_delete.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,13 @@
+
+{% extends "hello/simulations.html" %}
+
+{% block content %}
+
+<h2 class=titlebar>delete simulation</h2>
+
+<form method="post" action="/hello/simulations/{{ object.id }}/delete/">
+    <p>Are you sure you want to delete the simulation "{{ object }}"?
+    <p><input type="submit" value="Delete" />
+</form>
+{% endblock %}
+

Added: cs/portal/trunk/hello/hello/templates/hello/simulation_detail.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulation_detail.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulation_detail.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,29 @@
+
+{% extends "hello/simulations.html" %}
+
+{% block content %}
+
+<h2 class=titlebar>{{ object.name }}</h2>
+
+<div class=toolbar>
+    <form action="delete/" method="get"><input type="submit" value="Delete" /></form>
+</div>
+
+<div class=properties>
+
+    <div class=box>
+    <dl>
+        <dt>status</dt><dd>{{ object.status }}</dd>
+        <dt>created</dt><dd>{{ object.created|date }} {{ object.created|time }}</dd>
+        <dt>modified</dt><dd>{{ object.modified|date }} {{ object.modified|time }}</dd>
+        <dt>friend</dt><dd>{{ object.friend }}</dd>
+        <dt>happy</dt><dd>{{ object.happy }}</dd>
+        <dt>speed</dt><dd>{{ object.speed  }}</dd>
+        <dt>steps</dt><dd>{{ object.steps }}</dd>
+        <dt>nodes</dt><dd>{{ object.nodes }}</dd>
+    </dl>
+    </div>
+
+</div>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/simulation_form.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulation_form.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulation_form.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,73 @@
+
+{% extends "hello/simulations.html" %}
+
+{% block content %}
+
+<h2 class=titlebar>{% if object %}edit{% else %}new{% endif %} simulation</h2>
+
+{% if object %}
+<div class=toolbar>
+    <form action="../delete/" method="get"><input type="submit" value="Delete" /></form>
+</div>
+{% endif %}
+
+<form method="post" action="{{ action }}">
+
+    {% if form.has_errors %}
+    <p><span class=error>Please correct the following error{{ form.error_dict|pluralize }}.</span>
+    {% endif %}
+
+    <div class=tab30ex>
+
+    <input type="hidden" name="user" value="{{ user.id }}">
+    <input type="hidden" name="status" value="new">
+
+    <div>
+        <label for="id_name" class=before>name</label>
+        {{ form.name }}
+        {% if form.name.errors %}<span class=error>{{ form.name.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_friend" class=before>friend</label>
+        {{ form.friend }}
+        {% if form.friend.errors %}<span class=error>{{ form.friend.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div class=checkbox>
+        {{ form.happy }}
+        <label for="id_happy" class=after>happiness</label>
+        {% if form.happy.errors %}<span class=error>{{ form.happy.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_speed" class=before>speed</label>
+        {{ form.speed }} minutes
+        {% if form.speed.errors %}<span class=error>{{ form.speed.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_steps" class=before>time steps</label>
+        {{ form.steps }}
+        {% if form.steps.errors %}<span class=error>{{ form.steps.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        <label for="id_nodes" class=before>nodes</label>
+        {{ form.nodes }}
+        {% if form.nodes.errors %}<span class=error>{{ form.nodes.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+        {% if user.userinfo.approved %}
+        <input type="submit" value="Run" />
+        {% else %}
+        <input type="submit" value="Run" DISABLED />
+        {% endif %}
+    </div>
+
+    </div> <!-- tab30ex -->
+
+</form>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/simulation_list.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulation_list.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulation_list.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,38 @@
+
+{% extends "hello/simulations.html" %}
+
+{% block content %}
+
+<div class=toolbar>
+    <form action="create/" method="get"><input type="submit" value="New..." /></form>
+</div>
+
+{% if object_list %}
+    <table rules=groups>
+        
+        <colgroup><col class=odd><col class=even><col class=odd><col class=even><col class=odd><col class=even><col class=odd></colgroup>
+
+        <thead>
+        <tr><th>name</th><th>status</th><th>friend</th><th>happy</th><th>speed</th><th>steps</th><th>nodes</th></tr>
+        </thead>
+        
+        <tbody>
+        {% for sim in object_list %}
+        <tr>
+            <th><a href="/hello/simulations/{{ sim.id }}/">{{ sim.name }}</a></th>
+            <td>{{ sim.status }}</td>
+            <td>{{ sim.friend }}</td>
+            <td>{{ sim.happy }}</td>
+            <td>{{ sim.speed }}</td>
+            <td>{{ sim.steps }}</td>
+            <td>{{ sim.nodes }}</td>
+        </tr>
+        {% endfor %}
+        </tbody>
+
+    </table>
+{% else %}
+    <p>You have no simulations.
+{% endif %}
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/simulation_list.py
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulation_list.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulation_list.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,5 @@
+(
+    {% for object in object_list %}
+    { 'id': {{ object.id }}, 'status': '{{ object.status }}', 'nodes': {{ object.nodes }} },
+    {% endfor %}
+)

Added: cs/portal/trunk/hello/hello/templates/hello/simulation_status.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulation_status.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulation_status.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,14 @@
+<html>
+    <head>
+        <title>Simulation Status</title>
+    </head>
+
+    <body>
+
+    <form action="." method="POST" enctype="multipart/form-data">
+    <p>{{ form.status }}
+    <p><input type="submit" value="Save">
+    </form>
+
+    </body>
+</html>

Added: cs/portal/trunk/hello/hello/templates/hello/simulations.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/simulations.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/simulations.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,19 @@
+
+{% extends "hello/base.html" %}
+
+{% block desktop %}
+
+<div class=taskbar>
+    <ul>
+        <li class=first><a href="/hello/">home</a>
+        <li class=selected><a href="/hello/simulations/">simulations</a>
+        <li><a href="/hello/registration/">profile</a>
+    </ul>
+</div>
+
+<h1 class=titlebar>simulations</h1>
+
+{% block content %}
+{% endblock %}
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/splash.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/splash.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/splash.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,27 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<div class=splash>
+
+    <div class=nameplate>
+        <p id=presented>Presented by:</p>
+        <ul class=producers>
+            <li><a href="http://www.geodynamics.org/">Computational Infrastructure for Geodynamics</a>
+            <li><a href="http://www.caltech.edu/">California Institute of Technology</a>
+        </ul>
+        <h1>Hello</h1>
+        <h2>Web Portal</h2>
+    </div>
+
+    <p>Version 1.0
+
+    <p><a class=button href="login/">login</a>
+
+    <p>Are you a new user? If so, please <a href="registration/">register</a>.<br>
+       If you need help, <a href="mailto:portal at geodynamics.org">contact us</a>.
+
+</div> <!-- splash -->
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/hello/userinfo_form.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/hello/userinfo_form.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/hello/userinfo_form.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,17 @@
+
+{% extends "hello/settings.html" %}
+
+{% block content %}
+
+<h1 class=titlebar>profile</h1>
+
+<div class=tabs>
+    <ul>
+        <li class="selected first"><a href="/hello/registration/">registration</a>
+        <li><a href="/hello/registration/password/">password</a>
+    </ul>
+</div>
+
+{% include "hello/registration_form.html" %}
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/registration/login.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/registration/login.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/registration/login.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,57 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75>Home</a>
+
+<h1 class=titlebar>login</h1>
+
+{% if form.has_errors %}
+<dl class=error>
+    {% for f in form.error_dict.iteritems %}
+    <dt>{{ f.0 }}</dt>
+    <dd>
+        <ul class=error>
+        {% for e in f.1 %}
+            <li>{{ e }}</li>
+        {% endfor %}
+        </ul>
+    </dd>
+    {% endfor %}
+</dl>
+{% endif %}
+
+<form method="post" action="/hello/login/">
+
+    <div class=tab30ex>
+
+    <div>
+        <label for="id_username" class=before>username</label>
+        {{ form.username }}
+    </div>
+
+    <div>
+        <label for="id_password" class=before>password</label>
+        {{ form.password }}
+    </div>
+
+    <div><input class=submit type="submit" value="login" /></div>
+
+    {% if next %}
+    <input type="hidden" name="next" value="{{ next }}" />
+    {% else %}
+    <input type="hidden" name="next" value="/hello/" />
+    {% endif %}
+
+    </div> <!-- tab30ex -->
+
+</form>
+
+<p>Are you a new user? If so, please <a href="/hello/registration/">register</a>.
+
+<p>If you've forgotten your username and/or password, try <a href="/hello/pwreset/">resetting your password</a>.
+
+<p>If you need help, <a href="mailto:portal at geodynamics.org">contact us</a>.
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/registration/pwchange.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/registration/pwchange.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/registration/pwchange.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,46 @@
+
+{% extends "hello/settings.html" %}
+
+{% block content %}
+
+<h1 class=titlebar>profile</h1>
+
+<div class=tabs>
+    <ul>
+        <li class=first><a href="/hello/registration/">registration</a>
+        <li class=selected><a href="/hello/registration/password/">password</a>
+    </ul>
+</div>
+
+{% if form.has_errors %}
+<p><span class=error>Please correct the following error{{ form.error_dict|pluralize }}.</span>
+{% endif %}
+
+<form action="/hello/registration/password/" method="post">
+    <div class=tab30ex>
+
+    <div>
+            <label for="id_old_password" class=before>old password </label>
+            {{ form.old_password }}
+            {% if form.old_password.errors %}<span class=error>{{ form.old_password.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+            <label for="id_new_password1" class=before>new password </label>
+            {{ form.new_password1 }}
+            {% if form.new_password1.errors %}<span class=error>{{ form.new_password1.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div>
+            <label for="id_new_password2" class=before>confirm password </label>
+            {{ form.new_password2 }}
+            {% if form.new_password2.errors %}<span class=error>{{ form.new_password2.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <div><input class=submit type="submit" value="Change" /></div>
+
+    </div> <!-- tab30ex -->
+
+</form>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/registration/pwchange_done.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/registration/pwchange_done.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/registration/pwchange_done.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,19 @@
+
+{% extends "hello/settings.html" %}
+
+{% block content %}
+
+<h1 class=titlebar>profile</h1>
+
+<div class=tabs>
+    <ul>
+        <li class=first><a href="/hello/registration/">registration</a>
+        <li class=selected><a href="/hello/registration/password/">password</a>
+    </ul>
+</div>
+
+<h2 class=titlebar>password change successful</h2>
+
+<p>Your password was changed.</p>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/registration/pwreset.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/registration/pwreset.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/registration/pwreset.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,31 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75>Home</a>
+
+<h1 class=titlebar>password reset</h1>
+
+<p>Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you.</p>
+
+<form action="/hello/pwreset/" method="post">
+
+    {% if form.has_errors %}
+    <p><span class=error>Please correct the following error{{ form.error_dict|pluralize }}.</span>
+    {% endif %}
+
+    <div class=tab30ex>
+
+    <div>
+        <label for="id_email" class=before>e-mail address</label>
+        {{ form.email }} {% if form.email.errors %}<span class=error>{{ form.email.errors|join:", " }}</span>{% endif %}
+    </div>
+
+    <input class=submit type="submit" value="reset my password" /></p>
+
+    </div> <!-- tab30ex -->
+
+</form>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/registration/pwreset_done.html
===================================================================
--- cs/portal/trunk/hello/hello/templates/registration/pwreset_done.html	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/registration/pwreset_done.html	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,14 @@
+
+{% extends "hello/root.html" %}
+
+{% block body %}
+
+<p><a href="/hello/"><img id=logo src="/hello/static/cig.gif" width=78 height=75>Home</a>
+
+<h1 class=titlebar>password reset</h1>
+
+<p><b>Success!</b> We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly.</p>
+
+<p><a href="/hello/login/">Login</a></p>
+
+{% endblock %}

Added: cs/portal/trunk/hello/hello/templates/registration/pwreset_email.txt
===================================================================
--- cs/portal/trunk/hello/hello/templates/registration/pwreset_email.txt	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/templates/registration/pwreset_email.txt	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,18 @@
+You're receiving this e-mail because you requested a password reset
+for your user account at the Hello Web Portal:
+
+http://crust.geodynamics.org/hello/
+
+Your new password is: {{ new_password }}
+
+Feel free to change this password by going to this page:
+
+https://crust.geodynamics.org/hello/registration/password/
+
+Your username, in case you've forgotten: {{ user.username }}
+
+Thanks for using our site!
+
+--
+Computational Infrastructure for Geodynamics
+http://www.geodynamics.org/

Added: cs/portal/trunk/hello/hello/urls.py
===================================================================
--- cs/portal/trunk/hello/hello/urls.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/urls.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,60 @@
+
+import os
+import models
+from django.conf.urls.defaults import *
+from constants import *
+
+static_media_root = os.environ.get("WEBPORTAL_MEDIA_ROOT")+'/static'
+
+
+def view(name):
+    return APP_LABEL + '.views.' + name
+
+
+urlpatterns = patterns('',
+    (r'^$', view('home')),
+
+    (r'^login/$', 'django.contrib.auth.views.login'),
+    (r'^logout/$', 'django.contrib.auth.views.logout', dict(next_page = HOME)),
+    (r'^pwreset/$', 'django.contrib.auth.views.password_reset', dict(template_name='registration/pwreset.html',
+                                                                     email_template_name='registration/pwreset_email.txt')),
+    (r'^pwreset/done/$', 'django.views.generic.simple.direct_to_template', { 'template': 'registration/pwreset_done.html'}),
+    
+    (r'^registration/$', view('registration')),
+    (r'^registration/password/$', 'django.contrib.auth.views.password_change', dict(template_name='registration/pwchange.html')),
+    (r'^registration/password/done/$', 'django.views.generic.simple.direct_to_template', { 'template': 'registration/pwchange_done.html'}),
+
+    (r'^simulations/$', view('simulation_index')),
+    (r'^simulations/create/$', view('create_simulation')),
+    (r'^simulations/(?P<object_id>\d+)/$', view('simulation_detail')),
+    (r'^simulations/(?P<object_id>\d+)/delete/$', 'django.views.generic.create_update.delete_object',
+     dict(model = models.Simulation, post_delete_redirect = URL_ROOT + '/simulations/')),
+
+
+    # ~~~~~ Hidden Daemon Pages ~~~~~
+
+    # the simulation list
+
+    (r'^simulations/list.py$', 'django.views.generic.list_detail.object_list', dict(queryset = models.Simulation.objects.all(),
+                                                                                    allow_empty = True,
+                                                                                    template_name = APP_LABEL + '/simulation_list.py',
+                                                                                    mimetype = 'text/plain')),
+    # input files
+
+    (r'^simulations/(?P<object_id>\d+)/parameters.txt$', view('parameters_txt')),
+    (r'^simulations/(?P<object_id>\d+)/list.txt$', view('list_txt')),
+    (r'^simulations/(?P<object_id>\d+)/input.txt$', view('input_txt')),
+
+    # status form
+                       
+    (r'^simulations/(?P<object_id>\d+)/status/$', view('update_simulation_status')),
+
+
+    # ~~~~~ Debugging ~~~~~
+    
+    # static files
+    (r'^static/(.*)', 'django.views.static.serve', {'document_root':static_media_root}),
+)
+
+
+# end of file

Added: cs/portal/trunk/hello/hello/views.py
===================================================================
--- cs/portal/trunk/hello/hello/views.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/hello/views.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,205 @@
+# Create your views here.
+
+from django import forms
+from django.conf import settings
+from django.contrib.auth.decorators import user_passes_test
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template.context import RequestContext
+from django.views.generic.create_update import create_object
+from models import Simulation
+from constants import *
+
+import os, os.path
+import notifications
+
+
+# Create our own version of 'login_required' which redirects to our login page.
+login_required = user_passes_test(lambda u: not u.is_anonymous(), URL_ROOT + '/login/')
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Home
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+def home(request):
+    if request.user.is_anonymous():
+        return render_to_response(APP_LABEL + '/splash.html', {},
+                                  RequestContext(request, {}))
+    return render_to_response(APP_LABEL + '/home.html',
+                              {},
+                              RequestContext(request, {}))
+
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Simulations
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+disapproval = "You cannot run simulations until you are approved by a site administrator."
+
+
+def simulation_index(request):
+    from django.views.generic.list_detail import object_list
+    return object_list(request, Simulation.objects.filter(user=request.user),
+                       allow_empty=True)
+simulation_index = login_required(simulation_index)
+
+
+def create_simulation(request):
+    return create_object(request,
+                         Simulation,
+                         post_save_redirect = URL_ROOT + '/simulations/%(id)d/',
+                         )
+create_simulation = login_required(create_simulation)
+
+
+def simulation_detail(request, object_id):
+    from django.views.generic.list_detail import object_detail
+    return object_detail(request, Simulation.objects.all(), object_id)
+simulation_detail = login_required(simulation_detail)
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Authentication & Registration
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+def registration(request):
+    from forms import RegistrationAddManipulator, RegistrationChangeManipulator
+    
+    isNewUser = request.user.is_anonymous()
+    if isNewUser:
+        manipulator = RegistrationAddManipulator(request)
+        template = APP_LABEL + '/register.html'
+    else:
+        manipulator = RegistrationChangeManipulator(request.user)
+        template = APP_LABEL + '/userinfo_form.html'
+
+    if request.method == 'POST':
+        new_data = request.POST.copy()
+        errors = manipulator.get_validation_errors(new_data)
+        if not errors:
+            manipulator.do_html2python(new_data)
+            user, errors = manipulator.save(new_data)
+            if not errors:
+                if isNewUser:
+                    request.session.delete_test_cookie()
+                    notifications.notify_managers_of_new_user(request, user)
+                    user.message_set.create(message="Welcome to the CIG web portal!")
+                else:
+                    user.message_set.create(message="Your contact information has been saved.")
+                return HttpResponseRedirect(HOME)
+    else:
+        # Populate new_data with a 'flattened' version of the current data.
+        new_data = manipulator.flatten_data()
+        errors = {}
+
+    if isNewUser:
+        request.session.set_test_cookie()
+    
+    # Populate the FormWrapper.
+    form = forms.FormWrapper(manipulator, new_data, errors, edit_inline = True)
+    
+    return render_to_response(template, { 'form': form }, RequestContext(request, {}))
+
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Hidden Daemon Pages
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+# ~~~~~ Input Files ~~~~~
+
+
+def parameters_txt(request, object_id):
+    # This illustrates how to generate an input file from a template.
+    
+    from django.template import loader, Context
+
+    response = HttpResponse(mimetype='text/plain')
+
+    simulation = get_object_or_404(Simulation, id=object_id)
+
+    t = loader.get_template(APP_LABEL + '/parameters.txt')
+    c = Context({
+        'object': simulation,
+    })
+    response.write(t.render(c))
+    return response
+
+
+def list_txt(request, object_id):
+    # This illustrates how to generate an input file directly.
+
+    response = HttpResponse(mimetype='text/plain')
+
+    simulation = get_object_or_404(Simulation, id=object_id)
+
+    for i in xrange(0, simulation.steps):
+        response.write("%s %r %8.4f\n" %
+                       (simulation.friend, simulation.happy, simulation.speed))
+
+    return response
+
+
+def input_txt(request, object_id):
+    # This illustrates how to send an input file from the filesystem.
+    
+    import shutil
+    
+    filename = os.path.join(os.environ.get("WEBPORTAL_MEDIA_ROOT"), 'static', 'input.txt')
+    
+    response = HttpResponse(mimetype='text/plain')
+    response['Content-Disposition'] = 'attachment; filename=input.txt'
+    stream = open(filename, 'rb')
+    shutil.copyfileobj(stream,  response)
+    stream.close()
+    return response
+
+
+
+# ~~~~~ Status Form ~~~~~
+
+
+def update_simulation_status(request, object_id):
+    from forms import SimulationStatusManipulator
+    import datetime
+
+    manipulator = SimulationStatusManipulator()
+
+    simulation = get_object_or_404(Simulation, id=object_id)
+
+    if request.method == 'POST':
+        response = HttpResponse(mimetype='text/plain')
+        new_data = request.POST.copy()
+        new_data.update(request.FILES)
+        errors = manipulator.get_validation_errors(new_data)
+        if errors:
+            response.write(repr(errors))
+        else:
+            manipulator.do_html2python(new_data)
+            simulation.status = new_data['status']
+            if simulation.status in ['done', 'error']:
+                simulation.finished = datetime.datetime.now()
+                if simulation.status == 'error':
+                    notifications.notify_admins_of_failed_simulation(simulation)
+                else:
+                    notifications.notify_user_of_successful_simulation(simulation)
+            simulation.save()
+            response.write(repr('OK'))
+        return response
+    
+    errors = {}
+    new_data = {'status': simulation.status}
+    form = forms.FormWrapper(manipulator, new_data, errors)
+    return render_to_response(APP_LABEL + '/simulation_status.html',
+                              {'form': form},
+                              RequestContext(request, {}))
+
+
+
+# end of file

Added: cs/portal/trunk/hello/setup.py
===================================================================
--- cs/portal/trunk/hello/setup.py	2007-11-20 01:26:37 UTC (rev 8305)
+++ cs/portal/trunk/hello/setup.py	2007-11-20 01:50:31 UTC (rev 8306)
@@ -0,0 +1,30 @@
+
+try:
+    # If setuptools 0.6b1 or later is installed, run with it.
+    from pkg_resources import require
+    require("setuptools>=0.6b1")
+except:
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+from setuptools import setup
+
+setup(
+    name = 'hello', 
+    version = '1.0',
+    url = 'http://www.geodynamics.org/',
+    author = 'Leif Strand',
+    author_email = 'leif at geodynamics.org',
+    packages = [ 'hello' ],
+    
+    install_requires = [
+    'Django >= 0.95',
+    #'pysqlite >= 2.0.3',
+    ],
+    
+    dependency_links = [
+    'http://www.djangoproject.com/download/0.95/tarball/#egg=Django-0.95',
+    'http://initd.org/tracker/pysqlite',
+    ],
+
+)



More information about the cig-commits mailing list