Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
diff --git a/observer-initscript b/observer-initscript
new file mode 100644
index 0000000..d948eac
--- /dev/null
+++ b/observer-initscript
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# observer       Starts and stops Observer daemon
+#
+
+# Source function library.
+. /etc/init.d/functions
+
+[ -f /etc/sysconfig/plstackobserver ] && . /etc/sysconfig/plstackobserver
+
+
+plstackobserver=${NODEMANAGER-"python /opt/planetstack/planetstack-backend.py -d"}
+prog="OpenCloud Observer"
+pidfile=${PIDFILE-/var/run/plstackobserver.pid}
+
+RETVAL=0
+
+function start() {
+    action $"Starting $prog: " daemon --pidfile=$pidfile --check=plstackobserver $plstackobserver "$@"
+}
+
+function stop() {
+    action $"Stopping $prog: " killproc -p $pidfile plstackobserver
+}
+
+case "$1" in
+    start)
+	start $options
+	;;
+    stop)
+	stop
+	;;
+    status)
+	status -p $pidfile plstackobserver
+	RETVAL=$?
+	;;
+    restart|reload)
+	shift
+	stop
+	start $options "$@"
+	;;
+    condrestart)
+	shift
+	[ -f ${pidfile} ] && { stop; start $options "$@"; }
+	;;
+    restartverbose)
+	shift
+	stop
+	$plstackobserver $verboseoptions "$@"
+	;;
+    restartdebug)
+	shift
+	stop
+	echo "Restarting with $debugoptions $@ .."
+	$plstackobserver $debugoptions "$@"
+	;;
+    *)
+	echo $"Usage: $0 {start|stop|status|restart|condrestart|restartdebug [-d]}"
+	exit 1
+	;;
+esac
+
+exit $RETVAL
diff --git a/opencloud.spec b/opencloud.spec
index 184cbf8..26f67f8 100644
--- a/opencloud.spec
+++ b/opencloud.spec
@@ -70,12 +70,15 @@
 rm -rf %{buildroot}
 mkdir -p  %{buildroot}
 install -d %{buildroot}/opt/planetstack
+install -d %{buildroot}/etc/init.d
 
 # in builddir
 cp -rp ./planetstack %{buildroot}/opt/.
+cp observer-initscript %{buildroot}/etc/init.d/plstackobserver
 
 find %{buildroot}/opt/planetstack -type f -print | sed "s@^$RPM_BUILD_ROOT@@g" > %{_tmppath}/tmp-filelist
 cp %{_tmppath}/tmp-filelist /tmp/tmp-filelist
+echo /etc/init.d/plstackobserver > %{_tmppath}/tmp-filelist
 
 %clean
 rm -rf %{buildroot}
diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py
index 8e81b4b..9f8fbb0 100644
--- a/planetstack/core/admin.py
+++ b/planetstack/core/admin.py
@@ -234,6 +234,13 @@
     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
         if db_field.name == 'deploymentNetwork':
            kwargs['queryset'] = Deployment.select_by_acl(request.user)
+           # the inscrutable jquery selector below says:
+           #     find the closest parent "tr" to the current element
+           #     then find the child with class "field-node"
+           #     then find the child with that is a select
+           #     then return its id
+           kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
+           #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
 
         field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
@@ -760,6 +767,19 @@
         ('reservations','Reservations'),
     )
 
+    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
+        #deployment_nodes = {}
+        #for node in Node.objects.all():
+        #    deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
+
+        deployment_nodes = []
+        for node in Node.objects.all():
+            deployment_nodes.append( (node.deployment.id, node.id, node.name) )
+
+        context["deployment_nodes"] = deployment_nodes
+
+        return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
+
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
             kwargs['queryset'] = Site.select_by_user(request.user)
diff --git a/planetstack/core/context_processors.py b/planetstack/core/context_processors.py
index 270ec4e..9cfcaa6 100644
--- a/planetstack/core/context_processors.py
+++ b/planetstack/core/context_processors.py
@@ -1,4 +1,14 @@
 from django.conf import settings
+from core.models import Site
+
 
 def planetstack(request):
-    return {"DISABLE_MINIDASHBOARD": settings.DISABLE_MINIDASHBOARD}
+    allSites = []
+    for site in Site.objects.all():
+        allowNewUsers = True    # replace with logic for blessing sites for registration, if necessary
+        allSites.append( {"name": site.name,
+                           "id": site.id,
+                           "allowNewUsers": allowNewUsers} )
+
+    return {"DISABLE_MINIDASHBOARD": settings.DISABLE_MINIDASHBOARD,
+            "sites": allSites}
diff --git a/planetstack/core/fixtures/initial_data.json b/planetstack/core/fixtures/initial_data.json
index 1c81b7e..c5c754f 100644
--- a/planetstack/core/fixtures/initial_data.json
+++ b/planetstack/core/fixtures/initial_data.json
@@ -501,6 +501,16 @@
     }
 },
 {
+    "pk": 5,
+    "model": "core.siterole",
+    "fields": {
+        "updated": "2014-04-22T11:34:39.770Z",
+        "enacted": null,
+        "role": "default",
+        "created": "2014-04-22T11:34:39.770Z"
+    }
+},
+{
     "pk": 5, 
     "model": "core.deployment", 
     "fields": {
diff --git a/planetstack/core/static/planetstack.css b/planetstack/core/static/planetstack.css
index 44d62b4..8fb6e86 100644
--- a/planetstack/core/static/planetstack.css
+++ b/planetstack/core/static/planetstack.css
@@ -496,13 +496,13 @@
 display:none;
 }
 
-.login #content-main form input[type=text]{
+.login #content-main form input[type=text], .requestDialog.ui-widget input{
 width: 94%;
 padding:4px 6px;
 border-radius: 0px;
 height: 30px;
-background-color: #E5E5E5;
-background-image: url('name.png');
+background-color: rgb(250, 255, 189);
+/*background-image: url('name.png');*/
 background-repeat: no-repeat;
 background-position: 95%;
 font-size: 12px;
@@ -671,11 +671,18 @@
 	float: left;
 }
 
-.createAccountLink {
+#request-account-form{
+	display:none;
+}
+
+#requestAccountLink {
 	width: 55%;
 	text-align: right;
 	float: left;
-	
+	padding-left: 21%;
+	cursor: pointer;
+	color: #448CCA;
+	text-decoration: underline;	
 }
 
 .login .btn-info {
@@ -1031,6 +1038,15 @@
  margin-left: 5%;
 }
 
+
+#adv-slice-image-value{
+margin-right: 0.5%;
+}
+
+#adv-network-value {
+margin-right: 0.3%;
+}
+
 #network-dropdown,#adv-network-dropdown,#adv-network-value{
  margin-left: 3.7%;
 }
@@ -1043,16 +1059,16 @@
    margin-left: 2%;
 }
 #adv-dataset-dropdown{
-margin-left: 3%;
+margin-left: 3.7%;
 }
 #advanced-tenant,#basic-tenant,#sliver-btn,#save-btn{
   float:right;
 }
- #delete-slice-btn,#download-details{
+ #delete-slice-btn,#download-details,#add-user-btn{
 	margin-left:1%;
 }
 
-#sliver-btn,#save-btn,#create-slice-btn,#delete-slice-btn,#download-details{
+#sliver-btn,#save-btn,#create-slice-btn,#delete-slice-btn,#add-user-btn,#download-details{
   margin-top:1%;
 }
 
@@ -1138,12 +1154,64 @@
 
 #private-vol{
 margin-right: 15% !important;
-}

-

-.customize_row {

-  display: table;

-}

-.customize_column {

-  display: table-cell;

-  padding: 10px;

-}

+}
+.customize_row {
+  display: table;
+}
+.customize_column {
+  display: table-cell;
+  padding: 10px;
+}
+
+.request-form-row{
+padding:1% 8%;
+}
+
+.requestDialog{
+background-color: white;
+border-radius: 8px;
+width: 30% !important;
+height: 40% !important;
+margin-top: -16%;
+top: -103.703125px !important;
+}
+
+.request-form-row label{
+	float: left;
+}
+
+
+.requestDialog .ui-dialog-buttonset .ui-button{
+border-radius: 0 !important;
+background-color: grey !important;
+font-weight: bold !important;
+font-size: 0.9em; !important
+}
+
+.requestDialog .ui-dialog-titlebar-close{
+float:right;
+}
+
+#request-signup{
+height: 40px !important;
+margin: 0 14%;
+float: left;
+background-color: #448CCA;
+background-image: none;
+width: 70% !important;
+}
+
+.requestDialog .ui-dialog-titlebar{
+border-radius: 0 !important;
+height: 25px;
+padding-top: 2%;
+}
+
+.requestDialog #ui-id-1{
+padding-left: 28%;
+font-size: medium;
+}
+
+#request-site-name{
+	width: 98%;
+}
diff --git a/planetstack/ec2_observer/syncstep.py b/planetstack/ec2_observer/syncstep.py
index dcfea7d..d5f7523 100644
--- a/planetstack/ec2_observer/syncstep.py
+++ b/planetstack/ec2_observer/syncstep.py
@@ -60,7 +60,10 @@
         for dep in self.dependencies:
             peer_name = dep[0].lower() + dep[1:]    # django names are camelCased with the first letter lower
             peer_object = getattr(obj, peer_name)
-            if (peer_object.pk==failed.pk):
+            
+            # peer_object can be None, and if so there
+            # is no object-level dependency
+            if (peer_object and peer_object.pk==failed.pk):
                 raise FailedDependency
 
     def call(self, failed=[], deletion=False):
diff --git a/planetstack/planetstack/urls.py b/planetstack/planetstack/urls.py
index 3513a38..929505d 100644
--- a/planetstack/planetstack/urls.py
+++ b/planetstack/planetstack/urls.py
@@ -114,7 +114,7 @@
     url(r'^plstackapi/users/$', UserList.as_view(), name='user-list'),
     url(r'^plstackapi/users/(?P<pk>[a-zA-Z0-9_\-]+)/$', UserDetail.as_view(), name='user-detail'),
 
-    url(r'^legacyapi/$', 'core.views.legacyapi.LegacyXMLRPC', name='xmlrpc'),
+    url(r'^xmlrpc/legacyapi/$', 'core.views.legacyapi.LegacyXMLRPC', name='xmlrpc'),
 
 #    url(r'^analytics/(?P<name>\w+)/$', AnalyticsAjaxView.as_view(), name="analytics"),
 
diff --git a/planetstack/templates/admin/core/slice/change_form.html b/planetstack/templates/admin/core/slice/change_form.html
new file mode 100644
index 0000000..c94b580
--- /dev/null
+++ b/planetstack/templates/admin/core/slice/change_form.html
@@ -0,0 +1,26 @@
+{% extends 'admin/change_form.html' %}
+{% block extrahead %} 
+{{ block.super }} 
+<script>
+deployment_nodes = [
+{% for dn in deployment_nodes %}
+   [{{ dn.0 }}, {{ dn.1 }} , "{{ dn.2 }}"],
+{% endfor %}
+];
+
+function update_nodes(deployment_select, node_select_id) {
+    deployment_id = $(deployment_select).val();
+    html = "<option value=''>---------</option>\n";
+    for (i in deployment_nodes) {
+        dn = deployment_nodes[i];
+        if (dn[0] == deployment_id) {
+            html = html + '<option value="' + dn[1] + '">' + dn[2] + '</option>\n'
+        }
+    }
+    //console.log(html);
+    $("#"+node_select_id).empty().append(html);
+}
+</script>
+
+{% endblock %}
+
diff --git a/planetstack/templates/admin/login.html b/planetstack/templates/admin/login.html
index b04f842..a165707 100644
--- a/planetstack/templates/admin/login.html
+++ b/planetstack/templates/admin/login.html
@@ -4,8 +4,11 @@
 {% block extrastyle %}{{ block.super }}
 <link rel="stylesheet" type="text/css" href="/static/suit/bootstrap/css/bootstrap.min.css" media="all"/>
 <link rel="stylesheet" type="text/css" href="{% static "planetstack.css" %}" />
+<script src="{% static 'suit/js/jquery-1.9.1.min.js' %}"></script>
+<script src="http://code.jquery.com/ui/1.11.0/jquery-ui.js"></script>
 {% endblock %}
 
+
 {% block bodyclass %}login{% endblock %}
 
 {% block nav-global %}{% endblock %}
@@ -42,22 +45,96 @@
     <input type="hidden" name="this_is_the_login_form" value="1" />
     <input type="hidden" name="next" value="{{ next }}" />
   </div>
-  {% url 'admin_password_reset' as password_reset_url %}
-  {% if password_reset_url %}
-  <div class="password-reset-link">
-    <a href="{{ password_reset_url }}">{% trans 'Forgotten your password or username?' %}</a>
-  </div>
-  {% endif %}
   <div class="submit-row">
     <input type="submit" class="btn btn-info" value="{% trans 'SIGN IN' %}" />
   </div>
-{% url 'django-admindocs-docroot' as docsroot %}
-                  {% if docsroot %}
-<div class="createAccountLink"><a href="{{ docsroot }}">{% trans 'Request a new Account' %}</a></div>
-
-                  {% endif %}
+        <div id="requestAccountLink">{% trans 'Request a new Account' %}</div>
 </form>
+
+<div id="request-account-form" title="Request an Account" style="display: none;">
+	<form>
+		<fieldset>
+			<div class="request-form-row">
+				<label for="request-first-name">First Name</label>
+				<input type="text" name="request-first-name" id="request-first-name">
+			</div>
+			 <div class="request-form-row">
+                                <label for="request-last-name">Last Name</label>
+                                <input type="text" name="request-last-name" id="request-last-name">
+                        </div>
+			 <div class="request-form-row">
+                                <label for="request-email">Email</label>
+                                <input type="text" name="request-email" id="request-email">
+                        </div>
+			 <div class="request-form-row">
+                                <label for="request-site-name">Site</label><br>
+				<select id="request-site-name" name="request-site-name">
+                                    <option>---------</option>
+                                    {% for site in sites %}
+                                        {% if site.allowNewUsers %}
+                                            <option>{{ site.name }}</option>
+                                        {% endif %}
+                                    {% endfor %}
+				</select>
+                        </div>
+			<div class="submit-row">
+    				<input id ="request-signup" class="btn btn-info" value="SIGN UP">
+  			</div>
+		</fieldset>
+	</form>
+</div>
+</div>
+</div>
+
+
 <script type="text/javascript">
+$(function() {
+	initRequest();
+});
+function initRequest(){
+	$.ajax({
+			url: '/tenantview',
+			dataType: 'json',
+			success: function (data) {
+				var sites = data['sitesToBeRequested'];
+				console.log(sites);
+				for (site in sites){
+					$("#request-site-name").append("<option>" + site + "</option>");
+				}
+			}
+		});
+}
+$("#requestAccountLink").unbind().click(function(){
+	$("#request-account-form").dialog({
+					autoOpen: false,
+					modal: true,
+					dialogClass: "requestDialog",
+				});
+				$("#request-account-form").dialog("open");
+})
+$("#request-signup").unbind().click(function(){
+							$.ajax({
+								url: '/requestaccess/',
+								dataType: 'json',
+								data: {
+									email: $("#request-email").val(),
+									firstname: $("#request-first-name").val(),
+									lastname: $("#request-last-name").val(),
+									site: $("#request-site-name").val(),
+									csrfmiddlewaretoken: "{{ csrf_token }}", // < here 
+									state: "inactive"
+								},
+								async: false,
+								type: 'POST',
+								success: function () {
+									$("#request-account-form").dialog("close");	
+									alert("Your request has been submitted");							
+								},
+								error:function (xhr, textStatus, thrownError){
+         							   alert("Error:", textStatus + " " + xhr.responseText);
+        						        }
+							});
+})
 document.getElementById('id_username').focus()
 </script>
 </div>