Fix: merge conflic /portal/views.py
authorYasin <mohammed-yasin.rahman@lip6.fr>
Fri, 23 Aug 2013 22:49:28 +0000 (00:49 +0200)
committerYasin <mohammed-yasin.rahman@lip6.fr>
Fri, 23 Aug 2013 22:49:28 +0000 (00:49 +0200)
35 files changed:
README.migrations [new file with mode: 0644]
manifold/core/filter.py
manifold/core/query.py
manifold/manifoldapi.py
manifold/util/clause.py [new file with mode: 0644]
manifold/util/predicate.py
myslice/settings.py
myslice/urls.py
plugins/hazelnut/static/js/hazelnut.js
plugins/lists/static/js/simplelist.js
portal/migrations/0001_initial.py [new file with mode: 0644]
portal/migrations/0002_extend_slice.py [new file with mode: 0644]
portal/migrations/__init__.py [new file with mode: 0644]
portal/models.py
portal/static/css/register.css
portal/static/css/validate_pending.css [new file with mode: 0644]
portal/static/img/authority-icon.png [new file with mode: 0644]
portal/static/img/resource-icon.png [new file with mode: 0644]
portal/static/img/slice-icon.png [new file with mode: 0644]
portal/static/img/user-icon.png [new file with mode: 0644]
portal/templates/contact.html
portal/templates/platform.html [new file with mode: 0644]
portal/templates/register_4m_f4f.html
portal/templates/validate_pending.html [new file with mode: 0644]
portal/urls.py
portal/views.py
requirements/common.txt
reset.py [new file with mode: 0755]
sample/__init__.py [new file with mode: 0644]
sample/templates/websockets.html [new file with mode: 0644]
sample/templates/websockets2.html [new file with mode: 0644]
sample/urls.py [new file with mode: 0644]
sample/views.py [new file with mode: 0644]
trash/sliceview.py
views/templates/view-login.html

diff --git a/README.migrations b/README.migrations
new file mode 100644 (file)
index 0000000..0d52816
--- /dev/null
@@ -0,0 +1,4 @@
+see reset.sh
+
+./manage.py schemamigration portal --initial
+./manage.py schemamigration portal extend_slice --auto
index 0cf1c38..3a21348 100644 (file)
@@ -16,6 +16,9 @@ class Filter(set):
     A filter is a set of predicates
     """
 
+    #def __init__(self, s=()):
+    #    super(Filter, self).__init__(s)
+
     @staticmethod
     def from_list(l):
         f = Filter()
@@ -37,15 +40,29 @@ class Filter(set):
                 f.add(Predicate(key, '=', value))
         return f
 
+    def to_list(self):
+        ret = []
+        for predicate in self:
+            ret.append(predicate.to_list())
+        return ret
+        
+
+    @staticmethod
+    def from_clause(clause):
+        """
+        NOTE: We can only handle simple clauses formed of AND fields.
+        """
+        raise Exception, "Not implemented"
+
     def filter_by(self, predicate):
         self.add(predicate)
         return self
 
     def __str__(self):
-        return '<Filter: %s>' % ' AND '.join([str(pred) for pred in self])
+        return ' AND '.join([str(pred) for pred in self])
 
     def __repr__(self):
-        return self.__str__()
+        return '<Filter: %s>' % ' AND '.join([str(pred) for pred in self])
 
     def __key(self):
         return tuple([hash(pred) for pred in self])
@@ -61,6 +78,8 @@ class Filter(set):
     def keys(self):
         return set([x.key for x in self])
 
+    # XXX THESE FUNCTIONS SHOULD ACCEPT MULTIPLE FIELD NAMES
+
     def has(self, key):
         for x in self:
             if x.key == key:
@@ -94,10 +113,15 @@ class Filter(set):
         #self = filter(lambda x: x.key != key, self)
 
     def get_op(self, key, op):
-        for x in self:
-            if x.key == key and x.op == op:
-                return x.value
-        raise KeyError, key
+        if isinstance(op, (list, tuple, set)):
+            for x in self:
+                if x.key == key and x.op in op:
+                    return x.value
+        else:
+            for x in self:
+                if x.key == key and x.op == op:
+                    return x.value
+        return None
 
     def get_eq(self, key):
         return self.get_op(key, eq)
@@ -127,9 +151,9 @@ class Filter(set):
 #            dic = predicate.filter(dic)
 #        return dic
 
-    def match(self, dic):
+    def match(self, dic, ignore_missing=True):
         for predicate in self:
-            if not predicate.match(dic, ignore_missing=True):
+            if not predicate.match(dic, ignore_missing):
                 return False
         return True
 
@@ -140,8 +164,11 @@ class Filter(set):
                 output.append(x)
         return output
 
-    def to_list(self):
-        return [list(pred.get_str_tuple()) for pred in self]
+    def get_field_names(self):
+        field_names = set()
+        for predicate in self:
+            field_names |= predicate.get_field_names()
+        return field_names
 
 #class OldFilter(Parameter, dict):
 #    """
index e45844c..764336f 100644 (file)
 #   Thierry Parmentelat <thierry.parmentelat@inria.fr>
 
 from types                      import StringTypes
-from manifold.core.filter         import Filter, Predicate
-from manifold.util.frozendict     import frozendict
-from manifold.util.type           import returns, accepts
+from manifold.core.filter       import Filter, Predicate
+from manifold.util.frozendict   import frozendict
+from manifold.util.type         import returns, accepts
+from manifold.util.clause       import Clause
 import copy
 
 import json
@@ -116,10 +117,13 @@ class Query(object):
         #else:
         #        raise ParameterError, "No valid constructor found for %s : args = %r" % (self.__class__.__name__, args)
 
-        if not self.filters: self.filters = Filter()
-        if not self.params:  self.params  = {}
-        if not self.fields:  self.fields  = set()
-        if not self.timestamp:      self.timestamp      = "now" 
+        self.sanitize()
+
+    def sanitize(self):
+        if not self.filters:   self.filters   = Filter()
+        if not self.params:    self.params    = {}
+        if not self.fields:    self.fields    = set()
+        if not self.timestamp: self.timestamp = "now" 
 
         if isinstance(self.filters, list):
             f = self.filters
@@ -127,6 +131,8 @@ class Query(object):
             for x in f:
                 pred = Predicate(x)
                 self.filters.add(pred)
+        elif isinstance(self.filters, Clause):
+            self.filters = Filter.from_clause(self.filters)
 
         if isinstance(self.fields, list):
             self.fields = set(self.fields)
@@ -148,10 +154,8 @@ class Query(object):
         self.filters = Filter()
         self.params  = {}
         self.fields  = set()
-        self.timestamp  = "now" 
         self.timestamp  = 'now' # ignored for now
 
-
     def to_sql(self, platform='', multiline=False):
         get_params_str = lambda : ', '.join(['%s = %r' % (k, v) for k, v in self.get_params().items()])
         get_select_str = lambda : ', '.join(self.get_select()) 
@@ -159,13 +163,13 @@ class Query(object):
         table  = self.get_from()
         select = 'SELECT %s' % (get_select_str()    if self.get_select()    else '*')
         where  = 'WHERE %s'  % self.get_where()     if self.get_where()     else ''
-        at     = 'AT %s '    % self.get_timestamp() if self.get_timestamp() else ''
+        at     = 'AT %s    % self.get_timestamp() if self.get_timestamp() else ''
         params = 'SET %s'    % get_params_str()     if self.get_params()    else ''
 
         sep = ' ' if not multiline else '\n  '
         if platform: platform = "%s:" % platform
         strmap = {
-            'get'   : '%(select)s%(sep)s%(at)sFROM %(platform)s%(table)s%(sep)s%(where)s%(sep)s',                                           
+            'get'   : '%(select)s%(sep)s%(at)s%(sep)sFROM %(platform)s%(table)s%(sep)s%(where)s%(sep)s',                                           
             'update': 'UPDATE %(platform)s%(table)s%(sep)s%(params)s%(sep)s%(where)s%(sep)s%(select)s',       
             'create': 'INSERT INTO %(platform)s%(table)s%(sep)s%(params)s%(sep)s%(select)s',
             'delete': 'DELETE FROM %(platform)s%(table)s%(sep)s%(where)s'
@@ -196,7 +200,7 @@ class Query(object):
             'action': self.action,
             'object': self.object,
             'timestamp': self.timestamp,
-            'filters': self.filters,
+            'filters': self.filters.to_list(),
             'params': self.params,
             'fields': list(self.fields)
         }
@@ -237,6 +241,7 @@ class Query(object):
             if (debug):
                 import traceback
                 traceback.print_exc()
+        self.sanitize()
 
     #--------------------------------------------------------------------------- 
     # Accessors
@@ -371,6 +376,14 @@ class Query(object):
         return self
 
     def filter_by(self, *args):
+        """
+        Args:
+            args: It may be:
+                - the parts of a Predicate (key, op, value)
+                - None
+                - a Filter instance
+                - a set/list/tuple of Predicate instances
+        """
         if len(args) == 1:
             filters = args[0]
             if filters == None:
@@ -388,17 +401,20 @@ class Query(object):
         return self
             
     def select(self, *fields):
-        if not fields:
-            # Delete all fields
-            self.fields = set()
-            return self
 
         # Accept passing iterables
         if len(fields) == 1:
             tmp, = fields
-            if isinstance(tmp, (list, tuple, set, frozenset)):
+            if not tmp:
+                fields = None
+            elif isinstance(tmp, (list, tuple, set, frozenset)):
                 fields = tuple(tmp)
 
+        if not fields:
+            # Delete all fields
+            self.fields = set()
+            return self
+
         for field in fields:
             self.fields.add(field)
         return self
index 9361b6e..81a2caf 100644 (file)
@@ -61,3 +61,18 @@ class ManifoldAPI:
 
         return func
 
+def execute_query(request, query):
+    if not 'manifold' in request.session:
+        print "W: Used hardcoded demo account for execute_query"
+        manifold_api_session_auth = {'AuthMethod': 'password', 'Username': 'demo', 'AuthString': 'demo'}
+    else:
+        manifold_api_session_auth = request.session['manifold']['auth']
+    manifold_api = ManifoldAPI(auth=manifold_api_session_auth)
+    print "-"*80
+    print query
+    print query.to_dict()
+    print "-"*80
+    result = manifold_api.forward(query.to_dict())
+    if result['code'] == 2:
+        raise Exception, 'Error running query'
+    return result['value'] 
diff --git a/manifold/util/clause.py b/manifold/util/clause.py
new file mode 100644 (file)
index 0000000..670a689
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Implements a clause
+# - a "tree" (more precisely a predecessor map, typically computed thanks to a DFS) 
+# - a set of needed fields (those queried by the user)
+#
+# Copyright (C) UPMC Paris Universitas
+# Authors:
+#   Jordan Augé       <jordan.auge@lip6.fr> 
+#   Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
+
+import pyparsing as pp
+import operator, re
+
+from manifold.util.predicate import Predicate
+from types                 import StringTypes
+
+# XXX When to use Keyword vs. Regex vs. CaselessLiteral
+# XXX capitalization ?
+
+# Instead of CaselessLiteral, try using CaselessKeyword. Keywords are better
+# choice for grammar keywords, since they inherently avoid mistaking the leading
+# 'in' of 'inside' as the keyword 'in' in your grammar.
+
+
+class Clause(object):
+
+    def __new__(cls, *args, **kwargs):
+        if len(args) == 1 and isinstance(args[0], StringTypes):
+            return ClauseStringParser().parse(args[0])
+        return super(Clause, cls).__new__(cls, *args, **kwargs)
+
+    def __init__(self, *args, **kwargs):
+        if len(args) == 2:
+            # unary
+            self.operator = Predicate.operators[args[0]]
+            self.operands = [args[1]]
+        elif len(args) == 3:
+            self.operator = Predicate.operators[args[1]]
+            self.operands = [args[0], args[2]]
+        else:
+            raise Exception, "Clause can only be unary or binary"
+                
+    def opstr(self, operator):
+        ops = [string for string, op in Predicate.operators.items() if op == operator]
+        return ops[0] if ops else ''
+
+    def __repr__(self):
+        if len(self.operands) == 1:
+            return "%s(%s)" % (self.operator, self.operands[0])
+        else:
+            return "(%s %s %s)" % (self.operands[0], self.opstr(self.operator), self.operands[1])
+
+class ClauseStringParser(object):
+
+    def __init__(self):
+        """
+        BNF HERE
+        """
+
+        #integer = pp.Word(nums)
+        #floatNumber = pp.Regex(r'\d+(\.\d*)?([eE]\d+)?')
+        point = pp.Literal( "." )
+        e     = pp.CaselessLiteral( "E" )
+
+        # Regex string representing the set of possible operators
+        # Example : ">=|<=|!=|>|<|="
+        OPERATOR_RX = '|'.join([re.sub('\|', '\|', o) for o in Predicate.operators.keys()])
+
+        # predicate
+        field = pp.Word(pp.alphanums + '_')
+        operator = pp.Regex(OPERATOR_RX).setName("operator")
+        value = pp.QuotedString('"') #| pp.Combine( pp.Word( "+-"+ pp.nums, pp.nums) + pp.Optional( point + pp.Optional( pp.Word( pp.nums ) ) ) + pp.Optional( e + pp.Word( "+-"+pp.nums, pp.nums ) ) )
+
+        predicate = (field + operator + value).setParseAction(self.handlePredicate)
+
+        # clause of predicates
+        and_op = pp.CaselessLiteral("and") | pp.Keyword("&&")
+        or_op  = pp.CaselessLiteral("or")  | pp.Keyword("||")
+        not_op = pp.Keyword("!")
+
+        predicate_precedence_list = [
+            (not_op, 1, pp.opAssoc.RIGHT, lambda x: self.handleClause(*x)),
+            (and_op, 2, pp.opAssoc.LEFT,  lambda x: self.handleClause(*x)),
+            (or_op,  2, pp.opAssoc.LEFT,  lambda x: self.handleClause(*x))
+        ]
+        clause = pp.operatorPrecedence(predicate, predicate_precedence_list)
+
+        self.bnf = clause
+
+    def handlePredicate(self, args):
+        return Predicate(*args)
+
+    def handleClause(self, args):
+        return Clause(*args)
+
+    def parse(self, string):
+        return self.bnf.parseString(string,parseAll=True)
+
+if __name__ == "__main__":
+    print ClauseStringParser().parse('country == "Europe" || ts > "01-01-2007" && country == "France"')
+    print Clause('country == "Europe" || ts > "01-01-2007" && country == "France"')
index aa09379..3b5ba80 100644 (file)
@@ -112,6 +112,7 @@ class Predicate:
     def get_tuple(self):
         return (self.key, self.op, self.value)
 
+
     def get_str_op(self):
         op_str = [s for s, op in self.operators.iteritems() if op == self.op]
         return op_str[0]
@@ -119,6 +120,9 @@ class Predicate:
     def get_str_tuple(self):
         return (self.key, self.get_str_op(), self.value,)
 
+    def to_list(self):
+        return list(self.get_str_tuple())
+
     def match(self, dic, ignore_missing=False):
         if isinstance(self.key, tuple):
             print "PREDICATE MATCH", self.key
@@ -223,3 +227,9 @@ class Predicate:
             return set(self.value)
         else:
             return set([self.value])
+
+    def has_empty_value(self):
+        if isinstance(self.value, (list, tuple, set, frozenset)):
+            return not any(self.value)
+        else:
+            return not self.value
index 06e4eae..df4f6d7 100644 (file)
@@ -184,11 +184,13 @@ INSTALLED_APPS = (
     # views - more or less stable 
     'views',
     'trash',
+    'south', # managing database migrations
     # Uncomment the next line to enable the admin:
     # 'django.contrib.admin',
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
     'portal',
+    'sample',
 # DEPRECATED #    'django.contrib.formtools',
 # DEPRECATED ##    'crispy_forms',
 # DEPRECATED #
index 8accdb5..ef1f30c 100644 (file)
@@ -50,6 +50,8 @@ urlpatterns = patterns(
     (r'^slice/(?P<slicename>[\w\.]+)/?$', 'trash.sliceview.slice_view'),
     # Portal
     url(r'^portal/', include('portal.urls')),
+    # Portal
+    url(r'^sample/', include('sample.urls')),
     # Debug
     url(r'^debug/', include('debug_platform.urls')),
     # Static files
index af2dcfa..ca63e1e 100644 (file)
@@ -7,7 +7,6 @@
 (function($){
 
     // TEMP
-    var ELEMENT_KEY = 'resource_hrn';
     var debug=false;
     debug=true
 
  // UNUSED ? //         
  // UNUSED ? //         }, // update_plugin
 
-        checkbox: function (plugin_uuid, header, field) //, selected_str, disabled_str)
+        checkbox: function (key, value)
         {
             var result="";
-            if (header === null)
-                header = '';
             // Prefix id with plugin_uuid
             result += "<input";
             result += " class='hazelnut-checkbox'";
-            result += " id='" + this.id('checkbox', this.id_from_key(this.key, unfold.get_value(header))) + "'";
-             //hazelnut-checkbox-" + plugin_uuid + "-" + unfold.get_value(header).replace(/\\/g, '')  + "'";
-            result += " name='" + unfold.get_value(field) + "'";
+            result += " id='" + this.id('checkbox', this.id_from_key(key, value)) + "'";
+            result += " name='" + key + "'";
             result += " type='checkbox'";
-            //result += selected_str;
-            //result += disabled_str;
             result += " autocomplete='off'";
-            result += " value='" + unfold.get_value(header) + "'";
+            result += " value='" + value + "'";
             result += "></input>";
             return result;
         }, // checkbox
             if (this.options.checkboxes)
                 // Use a key instead of hostname (hard coded...)
                 // XXX remove the empty checked attribute
-                line.push(this.checkbox(this.options.plugin_uuid, record[ELEMENT_KEY], record['type']));
+                line.push(this.checkbox(this.key, record[this.key]));
     
             // XXX Is adding an array of lines more efficient ?
             this.table.fnAddData(line);
index 9cd5129..aed6217 100644 (file)
@@ -96,7 +96,7 @@
         if (key == 'slice.slice_hrn') {
             return "<i class='icon-play-circle'></i><a href='/slice/" + value + "'>" + value + "</a>";
         } else if (key == 'network_hrn') {
-            return "<i class='icon-play-circle'></i>" + value ;
+            return "<i class='icon-play-circle'></i><a href='/portal/platform/" + value + "'>" + value + "</a>";
         } else {
             return value;
         }
diff --git a/portal/migrations/0001_initial.py b/portal/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..24960f3
--- /dev/null
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'Institution'
+        db.create_table(u'portal_institution', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.TextField')()),
+        ))
+        db.send_create_signal(u'portal', ['Institution'])
+
+        # Adding model 'PendingUser'
+        db.create_table(u'portal_pendinguser', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('first_name', self.gf('django.db.models.fields.TextField')()),
+            ('last_name', self.gf('django.db.models.fields.TextField')()),
+            ('affiliation', self.gf('django.db.models.fields.TextField')()),
+            ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)),
+            ('password', self.gf('django.db.models.fields.TextField')()),
+            ('keypair', self.gf('django.db.models.fields.TextField')()),
+            ('authority_hrn', self.gf('django.db.models.fields.TextField')()),
+        ))
+        db.send_create_signal(u'portal', ['PendingUser'])
+
+        # Adding model 'PendingSlice'
+        db.create_table(u'portal_pendingslice', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('slice_name', self.gf('django.db.models.fields.TextField')()),
+        ))
+        db.send_create_signal(u'portal', ['PendingSlice'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'Institution'
+        db.delete_table(u'portal_institution')
+
+        # Deleting model 'PendingUser'
+        db.delete_table(u'portal_pendinguser')
+
+        # Deleting model 'PendingSlice'
+        db.delete_table(u'portal_pendingslice')
+
+
+    models = {
+        u'portal.institution': {
+            'Meta': {'object_name': 'Institution'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {})
+        },
+        u'portal.pendingslice': {
+            'Meta': {'object_name': 'PendingSlice'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slice_name': ('django.db.models.fields.TextField', [], {})
+        },
+        u'portal.pendinguser': {
+            'Meta': {'object_name': 'PendingUser'},
+            'affiliation': ('django.db.models.fields.TextField', [], {}),
+            'authority_hrn': ('django.db.models.fields.TextField', [], {}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'first_name': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'keypair': ('django.db.models.fields.TextField', [], {}),
+            'last_name': ('django.db.models.fields.TextField', [], {}),
+            'password': ('django.db.models.fields.TextField', [], {})
+        }
+    }
+
+    complete_apps = ['portal']
\ No newline at end of file
diff --git a/portal/migrations/0002_extend_slice.py b/portal/migrations/0002_extend_slice.py
new file mode 100644 (file)
index 0000000..633006a
--- /dev/null
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'PendingSlice.authority_hrn'
+        db.add_column(u'portal_pendingslice', 'authority_hrn',
+                      self.gf('django.db.models.fields.TextField')(null=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'PendingSlice.authority_hrn'
+        db.delete_column(u'portal_pendingslice', 'authority_hrn')
+
+
+    models = {
+        u'portal.institution': {
+            'Meta': {'object_name': 'Institution'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {})
+        },
+        u'portal.pendingslice': {
+            'Meta': {'object_name': 'PendingSlice'},
+            'authority_hrn': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slice_name': ('django.db.models.fields.TextField', [], {})
+        },
+        u'portal.pendinguser': {
+            'Meta': {'object_name': 'PendingUser'},
+            'affiliation': ('django.db.models.fields.TextField', [], {}),
+            'authority_hrn': ('django.db.models.fields.TextField', [], {}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'first_name': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'keypair': ('django.db.models.fields.TextField', [], {}),
+            'last_name': ('django.db.models.fields.TextField', [], {}),
+            'password': ('django.db.models.fields.TextField', [], {})
+        }
+    }
+
+    complete_apps = ['portal']
\ No newline at end of file
diff --git a/portal/migrations/__init__.py b/portal/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
index 029e3b5..a16e330 100644 (file)
@@ -215,6 +215,8 @@ class PendingUser(models.Model):
     password    = models.TextField()
     keypair     = models.TextField()
     # institution
+    authority_hrn = models.TextField()
+    # models.ForeignKey(Institution)
 
     objects = RegistrationManager()
 
@@ -308,3 +310,4 @@ class PendingUser(models.Model):
 
 class PendingSlice(models.Model):
     slice_name  = models.TextField()
+    authority_hrn = models.TextField(null=True)
index 687d7ed..4cb8079 100644 (file)
@@ -10,7 +10,7 @@ input {
         /*border:2px solid #456879;*/
         height: 22px;
         width: 200px;
-        font-size: 10px;
+        /* font-size: 10px; */
     }    
 label { 
         float: left;
@@ -21,7 +21,12 @@ label {
         display: inline-block;
         
 }
-
+.OneLabTitle{
+    padding-top: 12px;
+    background-color: #C1DAD7;
+    border: 1px solid #61210B;
+    text-align: center;
+}
 .fed4fireTitle{
     padding-top: 12px;
     background-color: orange; 
@@ -34,9 +39,9 @@ label.error { float: none; color: red; padding-left: .5em; vertical-align: top;
 p {
 /*border-bottom:1px solid #B7DDF2;*/
 color:#666666;
-font-size:10px;
-margin-bottom:20px;
-padding-bottom:10px;
+/* font-size:10px; */
+/* margin-bottom:20px; */
+/* padding-bottom:10px; */
 }
 
 
diff --git a/portal/static/css/validate_pending.css b/portal/static/css/validate_pending.css
new file mode 100644 (file)
index 0000000..4ed27e1
--- /dev/null
@@ -0,0 +1,49 @@
+.portal_validate_request {
+       background-repeat: no-repeat;
+       background-position: 10px center;
+       padding: 10px;
+       position:relative;
+}
+
+.portal_validate_request .slice {
+       background-image: url(../img/slice-icon.png);
+}
+
+.portal_validate_request.user {
+       background-image: url(../img/user-icon.png);
+}
+
+.portal_validate_request .authority {
+       background-image: url(../img/authority-icon.png);
+}
+
+.portal_validate_request .type {
+       position: relative;
+       left: 2%;
+}
+
+.portal_validate_request .id {
+       position: relative;
+       left: 5%;
+}
+.portal_validate_request .timestamp {
+       position: relative;
+       left: 10%;
+}
+.portal_validate_request .details {
+       position: relative;
+       left: 20%;
+}
+
+.portal_validate_request input {
+       position: relative;
+       left: 40%;
+}
+
+.portal_validate_request.even {
+       background-color: white;
+}
+
+.portal_validate_request.odd {
+       background-color: azure;
+}
diff --git a/portal/static/img/authority-icon.png b/portal/static/img/authority-icon.png
new file mode 100644 (file)
index 0000000..bde603a
Binary files /dev/null and b/portal/static/img/authority-icon.png differ
diff --git a/portal/static/img/resource-icon.png b/portal/static/img/resource-icon.png
new file mode 100644 (file)
index 0000000..919bdbe
Binary files /dev/null and b/portal/static/img/resource-icon.png differ
diff --git a/portal/static/img/slice-icon.png b/portal/static/img/slice-icon.png
new file mode 100644 (file)
index 0000000..9e8747b
Binary files /dev/null and b/portal/static/img/slice-icon.png differ
diff --git a/portal/static/img/user-icon.png b/portal/static/img/user-icon.png
new file mode 100644 (file)
index 0000000..88f6bf8
Binary files /dev/null and b/portal/static/img/user-icon.png differ
index f9e2e4e..6630f23 100644 (file)
@@ -7,16 +7,19 @@
 
 {% block unfold1_main %}
 
-
-<div style=" padding-top: 12px; background-color: orange; border: 1px solid #61210B; text-align: center;">
-         <h3>Onelab Support</h3>
-         <h3>If you have already registered then send an <a href="mailto:support@myslice.info"> e-mail </a>  or <a href="http://trac.myslice.info/" >visit us</a></h3>
+<link rel="stylesheet" type="text/css" href="{{STATIC_URL}}/css/register.css" />
+<form class="cmxform" id="commentForm" action="#" method="post">{% csrf_token %}
+<fieldset>
+<div class="OneLabTitle" style="margin-bottom:20px;">
+         <h2>Onelab Support</h2>
+         <h4>If you have already registered then send an <a href="mailto:support@myslice.info"> e-mail </a>  or <a href="http://trac.myslice.info/" >visit us</a></h4>
 </div>
 
-<form action="#" method="post">{% csrf_token %}
 {{ form.as_p }}
-<input type="submit" value="Submit" />
+<p style="text-align: center;width: 80%;">
+    <input type="submit" value="Submit" />
+</p>
+</fieldset>
 </form>
-
 {% endblock %}
 
diff --git a/portal/templates/platform.html b/portal/templates/platform.html
new file mode 100644 (file)
index 0000000..d24d9ed
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends "layout-unfold1.html" %}
+
+{% block head %}
+<link rel="stylesheet" type="text/css" href="{{STATIC_URL}}/css/dashboard.css" />
+{% endblock %}
+
+{% block unfold1_main %}
+
+<h1>Platform</h1>
+{{networks}}
+{% endblock %}
index 3a78a44..46fe09e 100644 (file)
@@ -22,7 +22,7 @@
 <form class="cmxform" id="commentForm" method="post" action="" enctype="multipart/form-data" >
 {% csrf_token %}
  <fieldset>
-    <div class="fed4fireTitle">
+    <div class="OneLabTitle">
         <h2>OneLab Experimenter Registration</h2>
         <h3>For First Line Support please <a href="/portal/contact" >Contact Support</a></h3>
     </div>
      <p class="hint">Enter your Institution name</p>
      <?php echo $erraffiliation;?>
    </div>
+
+    <div class="field">
+      <label for="auth_list">Authority</label>
+      <select id="auth_list" name="authority_hrn" size="1" class="required">
+        {% for authority in authorities %}
+        <option value="{{ authority.authority_hrn }}"/>{{authority.name}} ({{authority.authority_hrn}})</option>
+        {% endfor %}
+     </select>
+     <p class="hint">Please select an authority responsible for vetting your account</p>
+   </div>
+
    <div class="field">
      <label for="cemail">Email</label>
      <input type="text" id="email" name="email" size="25"  class="required email" value="{{ email }}"/> 
diff --git a/portal/templates/validate_pending.html b/portal/templates/validate_pending.html
new file mode 100644 (file)
index 0000000..dd7d5c2
--- /dev/null
@@ -0,0 +1,66 @@
+{% extends "layout-unfold1.html" %}
+
+{% block head %}
+<link rel="stylesheet" type="text/css" href="{{STATIC_URL}}/css/validate_pending.css" />
+{% endblock %}
+
+{% block unfold1_main %}
+
+<h1>Pending requests</h1>
+
+<h2>My authorities</h2>
+
+{% if my_authorities %}
+
+{% for authority, requests in my_authorities.items %}
+<h3>{{authority}}</h3>
+    {% for request in requests %}
+    <div class='portal_validate_request {{request.type}} {% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}'>
+               <span class='type'>{{ request.type }}</span>
+               <span class='id'>{{ request.id }}</span>
+               <span class='timestamp'>{{ request.timestamp }}</span>
+               <span class='details'>{{ request.details }}</span>
+               {% if request.allowed == 'allowed' %}
+               <input type='checkbox'/>
+               {% else %}
+                       {% if request.allowed == 'expired' %}
+                               expired
+                       {% else %} {# denied #}
+                               denied
+                       {% endif %}
+               {% endif %}
+       </div>
+    {% endfor %}
+{% endfor %}
+
+{% else %}
+<i>There is no pending request waiting for validation.</i>
+{% endif %}
+
+{% if delegation_authorities %}
+<h2>Authorities with delegation</h2>
+
+{% for authority, requests in delegation_authorities.items %}
+<h3>{{authority}}</h3>
+    {% for request in requests %}
+    <div class='portal_validate_request {{request.type}} {% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}'>
+               <span class='type'>{{ request.type }}</span>
+               <span class='id'>{{ request.id }}</span>
+               <span class='timestamp'>{{ request.timestamp }}</span>
+               <span class='details'>{{ request.details }}</span>
+               {% if request.allowed == 'allowed' %}
+               <input type='checkbox'/>
+               {% else %}
+                       {% if request.allowed == 'expired' %}
+                               expired
+                       {% else %} {# denied #}
+                               denied
+                       {% endif %}
+               {% endif %}
+       </div>
+    {% endfor %}
+{% endfor %}
+
+{% endif %}
+
+{% endblock %}
index 9882845..2887388 100644 (file)
@@ -22,8 +22,7 @@
 
 from django.conf.urls import patterns, include, url
 from portal           import views
-from portal.views     import  DashboardView, PresViewView
-from portal.views     import DashboardView, PresViewView, PlatformsView
+from portal.views     import DashboardView, PresViewView, PlatformsView, PlatformView, ValidatePendingView # UserRegisterView, UserValidateView
 from portal.util      import TemplateView
 
 # DEPRECATED #named_register_forms = (
@@ -46,12 +45,16 @@ urlpatterns = patterns('',
     #url(r'^my_account/?$', MyAccountView.as_view(), name='my_account'),
     url(r'^account/?$', views.my_account),
     url(r'^platforms/?$', PlatformsView.as_view(), name='platforms'),
+    #url(r'^portal/platform/?$', PlatformView.platform_view(), name='platform'),
+    url(r'^platform/(?P<platformname>[\w\.]+)/?$', PlatformView.as_view(), name='platform'),
     url(r'^acc_process/?$', views.acc_process),
     url(r'^register/?$', views.register_4m_f4f),
     #url(r'^reg_process/?$', views.reg_4m_f4f_process),
     url(r'^contact/?$', views.contact),
     # Slice request
     url(r'^slice_request/?$', views.slice_request),
+    # Validate pending requests
+    url(r'^validate/?$', ValidatePendingView.as_view()),
 
     url(r'^pres_view/?$', PresViewView.as_view(), name='pres_view'),
     (r'^methods/(?P<type>\w+)/?$', 'portal.views.pres_view_methods'),
index 16086c2..4df9fc8 100644 (file)
@@ -41,6 +41,7 @@ from portal.forms                import SliceRequestForm, ContactForm
 from portal.util                 import RegistrationView, ActivationView
 from portal.models               import PendingUser, PendingSlice
 from manifold.core.query         import Query
+from manifold.manifoldapi        import execute_query
 from unfold.page                 import Page
 from myslice.viewutils           import topmenu_items, the_user
 from django.http                 import HttpResponseRedirect, HttpResponse
@@ -68,6 +69,8 @@ class DashboardView(TemplateView):
         #slice_query = Query().get('slice').filter_by('user.user_hrn', 'contains', user_hrn).select('slice_hrn')
         slice_query = Query().get('user').filter_by('user_hrn', '==', '$user_hrn').select('user_hrn', 'slice.slice_hrn')
         auth_query  = Query().get('network').select('network_hrn')
+        print "AUTH QUERY =====================", auth_query
+        print "filter", auth_query.filters
         page.enqueue_query(slice_query)
         page.enqueue_query(auth_query)
 
@@ -476,16 +479,6 @@ class DashboardView(TemplateView):
 # DEPRECATED #        context.update(page.prelude_env())
 # DEPRECATED #        return context
 
-
-@login_required
-# View for my_account form
-def my_account(request):
-    return render(request, 'my_account.html', {
-        #'form': form,
-        'topmenu_items': topmenu_items('My Account', request),
-        'username': the_user (request)
-    })
-
 # View for platforms
 class PlatformsView(TemplateView):
     template_name = "platforms.html"
@@ -537,6 +530,77 @@ class PlatformsView(TemplateView):
         context.update(page.prelude_env())
 
         return context
+
+
+
+# View for 1 platform and its details
+class PlatformView(TemplateView):
+    template_name = "platform.html"
+
+    def get_context_data(self, **kwargs):
+        page = Page(self.request)
+
+        for key, value in kwargs.iteritems():
+            print "%s = %s" % (key, value)       
+            if key == "platformname":
+                platformname=value
+                
+        network_query  = Query().get('local:platform').filter_by('platform', '==', platformname).select('platform','platform_longname','gateway_type')
+        page.enqueue_query(network_query)
+
+        page.expose_js_metadata()
+        page.expose_queries()
+        networklist = Hazelnut(
+            page  = page,
+            title = 'List',
+            domid = 'checkboxes',
+            # this is the query at the core of the slice list
+            query = network_query,
+            query_all = network_query,
+            checkboxes = False,
+            datatables_options = {
+            # for now we turn off sorting on the checkboxes columns this way
+            # this of course should be automatic in hazelnut
+            'aoColumns'      : [None, None, None, None, {'bSortable': False}],
+            'iDisplayLength' : 25,
+            'bLengthChange'  : True,
+            },
+        )
+#
+#        networklist = SimpleList(
+#            title = None,
+#            page  = page,
+#            key   = 'platform',
+#            query = network_query,
+#        )
+
+        context = super(PlatformView, self).get_context_data(**kwargs)
+        context['person']   = self.request.user
+        context['networks'] = networklist.render(self.request)
+
+        # XXX This is repeated in all pages
+        # more general variables expected in the template
+        context['title'] = 'Platforms connected to MySlice'
+        # the menu items on the top
+        context['topmenu_items'] = topmenu_items('Platforms', self.request)
+        # so we can sho who is logged
+        context['username'] = the_user(self.request)
+
+        context.update(page.prelude_env())
+
+        return context
+
+
+@login_required
+# View for my_account form
+def my_account(request):
+    return render(request, 'my_account.html', {
+        #'form': form,
+        'topmenu_items': topmenu_items('My Account', request),
+        'username': the_user (request)
+    })
+
+
 @login_required
 #my_acc form value processing
 def acc_process(request):
@@ -631,11 +695,17 @@ def acc_process(request):
 
 def register_4m_f4f(request):
     errors = []
+
+    authorities_query = Query.get('authority').filter_by('authority_hrn', 'included', ['ple.inria', 'ple.upmc']).select('name', 'authority_hrn')
+    #authorities_query = Query.get('authority').select('authority_hrn')
+    authorities = execute_query(request, authorities_query)
+
     if request.method == 'POST':
         #get_email = PendingUser.objects.get(email)
         reg_fname = request.POST.get('firstname', '')
         reg_lname = request.POST.get('lastname', '')
         reg_aff = request.POST.get('affiliation','')
+        reg_auth = request.POST.get('authority_hrn', '')
         reg_email = request.POST.get('email','').lower()
         
         #POST value validation  
@@ -651,6 +721,7 @@ def register_4m_f4f(request):
             errors.append('Affiliation may contain only letters, numbers, spaces and @/./+/-/_ characters.')
             #return HttpResponse("Only Letters, Numbers and _ is allowed in Affiliation")
             #return render(request, 'register_4m_f4f.html')
+        # XXX validate authority hrn !!
         if PendingUser.objects.filter(email__iexact=reg_email):
             errors.append('Email already registered.Please provide a new email address.')
             #return HttpResponse("Email Already exists")
@@ -704,6 +775,7 @@ def register_4m_f4f(request):
         #b.save()
         if not errors:
             b = PendingUser(first_name=reg_fname, last_name=reg_lname, affiliation=reg_aff,
+                            authority_hrn=reg_auth,
                             email=reg_email, password=request.POST['password'], keypair=keypair)
             b.save()
             return render(request, 'user_register_complete.html')
@@ -714,8 +786,10 @@ def register_4m_f4f(request):
         'firstname': request.POST.get('firstname', ''),
         'lastname': request.POST.get('lastname', ''),
         'affiliation': request.POST.get('affiliation', ''),
+        'authority_hrn': request.POST.get('authority_hrn', ''),
         'email': request.POST.get('email', ''),
         'password': request.POST.get('password', ''),           
+        'authorities': authorities
     })        
     
 
@@ -962,3 +1036,215 @@ def pres_view_static(request, constraints, id):
 
     json_answer = json.dumps(cmd)
     return HttpResponse (json_answer, mimetype="application/json")
+
+class ValidatePendingView(TemplateView):
+    template_name = "validate_pending.html"
+
+    def get_context_data(self, **kwargs):
+        # We might have slices on different registries with different user accounts 
+        # We note that this portal could be specific to a given registry, to which we register users, but i'm not sure that simplifies things
+        # Different registries mean different identities, unless we identify via SFA HRN or have associated the user email to a single hrn
+
+        #messages.info(self.request, 'You have logged in')
+        page = Page(self.request)
+
+        ctx_my_authorities = {}
+        ctx_delegation_authorities = {}
+
+
+        # The user need to be logged in
+        if the_user(self.request):
+            # Who can a PI validate:
+            # His own authorities + those he has credentials for.
+            # In MySlice we need to look at credentials also.
+            
+
+            # XXX This will have to be asynchroneous. Need to implement barriers,
+            # for now it will be sufficient to have it working statically
+
+            # get user_id to later on query accounts
+            # XXX Having real query plan on local tables would simplify all this
+            # XXX $user_email is still not available for local tables
+            #user_query = Query().get('local:user').filter_by('email', '==', '$user_email').select('user_id')
+            user_query = Query().get('local:user').filter_by('email', '==', the_user(self.request)).select('user_id')
+            user, = execute_query(self.request, user_query)
+            user_id = user['user_id']
+
+            # Query manifold to learn about available SFA platforms for more information
+            # In general we will at least have the portal
+            # For now we are considering all registries
+            all_authorities = []
+            platform_ids = []
+            sfa_platforms_query = Query().get('local:platform').filter_by('gateway_type', '==', 'sfa').select('platform_id', 'platform', 'auth_type')
+            sfa_platforms = execute_query(self.request, sfa_platforms_query)
+            for sfa_platform in sfa_platforms:
+                print "SFA PLATFORM > ", sfa_platform['platform']
+                if not 'auth_type' in sfa_platform:
+                    continue
+                auth = sfa_platform['auth_type']
+                if not auth in all_authorities:
+                    all_authorities.append(auth)
+                platform_ids.append(sfa_platform['platform_id'])
+
+            # We can check on which the user has authoritity credentials = PI rights
+            credential_authorities = set()
+            credential_authorities_expired = set()
+
+            # User account on these registries
+            user_accounts_query = Query.get('local:account').filter_by('user_id', '==', user_id).filter_by('platform_id', 'included', platform_ids).select('config')
+            user_accounts = execute_query(self.request, user_accounts_query)
+            #print "=" * 80
+            #print user_accounts
+            #print "=" * 80
+            for user_account in user_accounts:
+                config = json.loads(user_account['config'])
+                creds = []
+                if 'authority_credentials' in config:
+                    for authority_hrn, credential in config['authority_credentials'].items():
+                        #if credential is not expired:
+                        credential_authorities.add(authority_hrn)
+                        #else
+                        #    credential_authorities_expired.add(authority_hrn)
+                if 'delegated_authority_credentials' in config:
+                    for authority_hrn, credential in config['delegated_authority_credentials'].items():
+                        #if credential is not expired:
+                        credential_authorities.add(authority_hrn)
+                        #else
+                        #    credential_authorities_expired.add(authority_hrn)
+
+            print 'credential_authorities =', credential_authorities
+            print 'credential_authorities_expired =', credential_authorities_expired
+
+            # ** Where am I a PI **
+            # For this we need to ask SFA (of all authorities) = PI function
+            pi_authorities_query = Query.get('user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
+            pi_authorities_tmp = execute_query(self.request, pi_authorities_query)
+            pi_authorities = set()
+            for pa in pi_authorities_tmp:
+                pi_authorities |= set(pa['pi_authorities'])
+
+            print "pi_authorities =", pi_authorities
+            
+            # My authorities + I have a credential
+            pi_credential_authorities = pi_authorities & credential_authorities
+            pi_no_credential_authorities = pi_authorities - credential_authorities - credential_authorities_expired
+            pi_expired_credential_authorities = pi_authorities & credential_authorities_expired
+            # Authorities I've been delegated PI rights
+            pi_delegation_credential_authorities = credential_authorities - pi_authorities
+            pi_delegation_expired_authorities = credential_authorities_expired - pi_authorities
+
+            print "pi_credential_authorities =", pi_credential_authorities
+            print "pi_no_credential_authorities =", pi_no_credential_authorities
+            print "pi_expired_credential_authorities =", pi_expired_credential_authorities
+            print "pi_delegation_credential_authorities = ", pi_delegation_credential_authorities
+            print "pi_delegation_expired_authorities = ", pi_delegation_expired_authorities
+
+            # Summary intermediary
+            pi_my_authorities = pi_credential_authorities | pi_no_credential_authorities | pi_expired_credential_authorities
+            pi_delegation_authorities = pi_delegation_credential_authorities | pi_delegation_expired_authorities
+
+            print "--"
+            print "pi_my_authorities = ", pi_my_authorities
+            print "pi_delegation_authorities = ", pi_delegation_authorities
+
+            # Summary all
+            queried_pending_authorities = pi_my_authorities | pi_delegation_authorities
+            print "----"
+            print "queried_pending_authorities = ", queried_pending_authorities
+            
+            # Pending requests + authorities
+            #pending_users = PendingUser.objects.filter(authority_hrn__in = queried_pending_authorities).all() 
+            #pending_slices = PendingSlice.objects.filter(authority_hrn__in = queried_pending_authorities).all() 
+            pending_users = PendingUser.objects.all()
+            pending_slices = PendingSlice.objects.all()
+
+            # Dispatch requests and build the proper structure for the template:
+
+            print "pending users =", pending_users
+            print "pending slices =", pending_slices
+
+            for user in pending_users:
+                auth_hrn = user.authority_hrn
+
+                request = {}
+                request['type'] = 'user'
+                request['id'] = 'TODO' # XXX in DB ?
+                request['timestamp'] = 'TODO' # XXX in DB ?
+                request['details'] = "%s %s <%s>" % (user.first_name, user.last_name, user.email)
+
+                if auth_hrn in pi_my_authorities:
+                    dest = ctx_my_authorities
+
+                    # define the css class
+                    if auth_hrn in pi_credential_authorities:
+                        request['allowed'] = 'allowed'
+                    elif auth_hrn in pi_expired_credential_authorities:
+                        request['allowed'] = 'expired'
+                    else: # pi_no_credential_authorities
+                        request['allowed'] = 'denied'
+
+                elif auth_hrn in pi_delegation_authorities:
+                    dest = ctx_delegation_authorities
+
+                    if auth_hrn in pi_delegation_credential_authorities:
+                        request['allowed'] = 'allowed'
+                    else: # pi_delegation_expired_authorities
+                        request['allowed'] = 'expired'
+
+                else:
+                    continue
+                    
+                if not auth_hrn in dest:
+                    dest[auth_hrn] = []
+                print "auth_hrn [%s] was added %r" % (auth_hrn, request)
+                dest[auth_hrn].append(request) 
+
+            for slice in pending_slices:
+                auth_hrn = slice.authority_hrn
+                if not auth_hrn:
+                    auth_hrn = "ple.upmc" # XXX HARDCODED
+
+                request = {}
+                request['type'] = 'slice'
+                request['id'] = 'TODO' # XXX in DB ?
+                request['timestamp'] = 'TODO' # XXX in DB ?
+                request['details'] = "Number of nodes: %d -- Type of nodes: %s<br/>%s" % ('TODO', 'TODO', 'TODO') # XXX 
+                if auth_hrn in pi_my_authorities:
+                    dest = ctx_my_authorities
+
+                    # define the css class
+                    if auth_hrn in pi_credential_authorities:
+                        request['allowed'] = 'allowed'
+                    elif auth_hrn in pi_expired_credential_authorities:
+                        request['allowed'] = 'expired'
+                    else: # pi_no_credential_authorities
+                        request['allowed'] = 'denied'
+
+                elif auth_hrn in pi_delegation_authorities:
+                    dest = ctx_delegation_authorities
+
+                    if auth_hrn in pi_delegation_credential_authorities:
+                        request['allowed'] = 'allowed'
+                    else: # pi_delegation_expired_authorities
+                        request['allowed'] = 'expired'
+                    
+                if not auth_hrn in dest:
+                    dest[auth_hrn] = []
+                dest[auth_hrn].append(request) 
+
+        context = super(ValidatePendingView, self).get_context_data(**kwargs)
+        context['my_authorities']   = ctx_my_authorities
+        context['delegation_authorities'] = ctx_delegation_authorities
+
+        # XXX This is repeated in all pages
+        # more general variables expected in the template
+        context['title'] = 'Test view that combines various plugins'
+        # the menu items on the top
+        context['topmenu_items'] = topmenu_items('Dashboard', self.request) 
+        # so we can sho who is logged
+        context['username'] = the_user(self.request) 
+
+        # XXX We need to prepare the page for queries
+        #context.update(page.prelude_env())
+
+        return context
index 6145269..43afd6d 100644 (file)
@@ -2,3 +2,4 @@
 #Django==1.4
 django-registration==1.0
 Sphinx==1.1.3
+django-south
diff --git a/reset.py b/reset.py
new file mode 100755 (executable)
index 0000000..b7bb571
--- /dev/null
+++ b/reset.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+rm myslice.sqlite3
+./manage.py syncdb
+./manage.py migrate portal
diff --git a/sample/__init__.py b/sample/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sample/templates/websockets.html b/sample/templates/websockets.html
new file mode 100644 (file)
index 0000000..9942fd0
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+   <head>
+      <script type="text/javascript">
+         var sock = null;
+         var wsuri = "ws://dev.myslice.info:9000";
+         window.onload = function() {
+            sock = new WebSocket(wsuri);
+            sock.onopen = function() {
+               console.log("connected to " + wsuri);
+            }
+            sock.onclose = function(e) {
+               console.log("connection closed (" + e.code + ")");
+            }
+            sock.onmessage = function(e) {
+               console.log("message received: " + e.data);
+            }
+         };
+         function send() {
+            var msg = document.getElementById('message').value;
+            sock.send(msg);
+         };
+      </script>
+   </head>
+   <body>
+      <h1>WebSocket Echo Test</h1>
+      <form>
+         <p>
+            Message:
+            <input id="message" type="text" value="Hello, world!">
+         </p>
+      </form>
+      <button onclick='send();'>Send Message</button>
+   </body>
+</html>
diff --git a/sample/templates/websockets2.html b/sample/templates/websockets2.html
new file mode 100644 (file)
index 0000000..b1119d0
--- /dev/null
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+   <head>
+      <script type="text/javascript">
+         var sock = null;
+         var ellog = null;
+
+         window.onload = function() {
+
+            var wsuri;
+            ellog = document.getElementById('log');
+
+            if (window.location.protocol === "file:") {
+               wsuri = "ws://localhost:9000";
+            } else {
+               wsuri = "ws://" + window.location.hostname + ":9000";
+            }
+
+            if ("WebSocket" in window) {
+               sock = new WebSocket(wsuri);
+            } else if ("MozWebSocket" in window) {
+               sock = new MozWebSocket(wsuri);
+            } else {
+               log("Browser does not support WebSocket!");
+               window.location = "http://autobahn.ws/unsupportedbrowser";
+            }
+
+            if (sock) {
+               sock.onopen = function() {
+                  log("Connected to " + wsuri);
+               }
+
+               sock.onclose = function(e) {
+                  log("Connection closed (wasClean = " + e.wasClean + ", code = " + e.code + ", reason = '" + e.reason + "')");
+                  sock = null;
+               }
+
+               sock.onmessage = function(e) {
+                  log("Got echo: " + e.data);
+               }
+            }
+         };
+
+         function broadcast() {
+            var msg = document.getElementById('message').value;
+            if (sock) {
+               sock.send(msg);
+               log("Sent: " + msg);
+            } else {
+               log("Not connected.");
+            }
+         };
+
+         function log(m) {
+            ellog.innerHTML += m + '\n';
+            ellog.scrollTop = ellog.scrollHeight;
+         };
+      </script>
+   </head>
+   <body>
+      <h1>Autobahn WebSocket Broadcast Demo</h1>
+      <noscript>You must enable JavaScript</noscript>
+      <form>
+         <p>Broadcast Message: <input id="message" type="text" size="50" maxlength="50" value="Hello from Browser!"></p>
+      </form>
+      <button onclick='broadcast();'>Broadcast Message</button>
+      <pre id="log" style="height: 20em; overflow-y: scroll; background-color: #faa;"></pre>
+   </body>
+</html>
diff --git a/sample/urls.py b/sample/urls.py
new file mode 100644 (file)
index 0000000..d56b5dc
--- /dev/null
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+#
+# sample/urls.py: URL mappings for the sample application
+# This file is part of the Manifold project.
+#
+# Authors:
+#   Jordan Augé <jordan.auge@lip6.fr>
+# Copyright 2013, UPMC Sorbonne Universités / LIP6
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+# 
+# You should have received a copy of the GNU General Public License along with
+# this program; see the file COPYING.  If not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from django.conf.urls import patterns, url
+from sample.views     import WebSocketsView, WebSockets2View
+
+urlpatterns = patterns('',
+    url(r'^websockets/?$', WebSocketsView.as_view(), name='websockets'),
+    url(r'^websockets2/?$', WebSockets2View.as_view(), name='websockets2'),
+)
diff --git a/sample/views.py b/sample/views.py
new file mode 100644 (file)
index 0000000..c134269
--- /dev/null
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+#
+# sample/views.py: views for the sample application
+# This file is part of the Manifold project.
+#
+# Authors:
+#   Jordan Augé <jordan.auge@lip6.fr>
+# Copyright 2013, UPMC Sorbonne Universités / LIP6
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+# 
+# You should have received a copy of the GNU General Public License along with
+# this program; see the file COPYING.  If not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from django.views.generic.base   import TemplateView
+from myslice.viewutils           import topmenu_items, the_user
+
+class WebSocketsView(TemplateView):
+    template_name = "websockets.html"
+
+    def get_context_data(self, **kwargs):
+        # We might have slices on different registries with different user accounts 
+        # We note that this portal could be specific to a given registry, to which we register users, but i'm not sure that simplifies things
+        # Different registries mean different identities, unless we identify via SFA HRN or have associated the user email to a single hrn
+
+        context = super(WebSocketsView, self).get_context_data(**kwargs)
+
+        # XXX This is repeated in all pages
+        # more general variables expected in the template
+        context['title'] = 'SAMPLE WEBSOCKET PAGE',
+        # the menu items on the top
+        context['topmenu_items'] = topmenu_items('Dashboard', self.request) 
+        # so we can sho who is logged
+        context['username'] = the_user(self.request) 
+
+        return context
+
+
+class WebSockets2View(TemplateView):
+    template_name = "websockets2.html"
+
+    def get_context_data(self, **kwargs):
+        # We might have slices on different registries with different user accounts 
+        # We note that this portal could be specific to a given registry, to which we register users, but i'm not sure that simplifies things
+        # Different registries mean different identities, unless we identify via SFA HRN or have associated the user email to a single hrn
+
+        context = super(WebSockets2View, self).get_context_data(**kwargs)
+
+        # XXX This is repeated in all pages
+        # more general variables expected in the template
+        context['title'] = 'SAMPLE WEBSOCKET PAGE',
+        # the menu items on the top
+        context['topmenu_items'] = topmenu_items('Dashboard', self.request) 
+        # so we can sho who is logged
+        context['username'] = the_user(self.request) 
+
+        return context
+
index a966100..9f1663e 100644 (file)
@@ -58,6 +58,9 @@ def _slice_view (request, slicename):
     resource_md = metadata.details_by_object('resource')
     resource_fields = [column['name'] for column in resource_md['column']]
 
+    user_md = metadata.details_by_object('user')
+    user_fields = ['user_hrn'] # [column['name'] for column in user_md['column']]
+
     # TODO The query to run is embedded in the URL
     main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
     main_query.select(
@@ -69,10 +72,12 @@ def _slice_view (request, slicename):
     )
 
     query_resource_all = Query.get('resource').select(resource_fields)
+    query_user_all = Query.get('user').select(user_fields)
 
     aq = AnalyzedQuery(main_query, metadata=metadata)
     page.enqueue_query(main_query, analyzed_query=aq)
     page.enqueue_query(query_resource_all)
+    page.enqueue_query(query_user_all)
 
     # Prepare the display according to all metadata
     # (some parts will be pending, others can be triggered by users).
@@ -203,6 +208,7 @@ def _slice_view (request, slicename):
         togglable   = False,
         # this is the query at the core of the slice list
         query       = sq_user,
+        query_all  = query_user_all,
         checkboxes  = True,
         datatables_options = { 
             # for now we turn off sorting on the checkboxes columns this way
index cff2c44..8d15f96 100644 (file)
@@ -19,9 +19,9 @@ Currently hard wired users are:
 </ul>
 </li></ul>
 -->
-
+<link rel="stylesheet" type="text/css" href="{{STATIC_URL}}/css/register.css" />
 <div style='padding: 20px;'>
-  <div style=' padding-top: 12px; background-color: orange; border: 1px solid #61210B; text-align: center;'>
+  <div class='OneLabTitle'>
     <h2 style="font-weight: bold;">Welcome to the OneLab portal !</h2>
     <h3>New to OneLab? Please <a href="/portal/register">register</a> or learn more about <a href="http://www.onelab.eu/" target="_blank">the project</a>.</h3>
   </div>