portal: added wip for PI validation page
authorJordan Augé <jordan.auge@lip6.fr>
Fri, 23 Aug 2013 12:35:31 +0000 (14:35 +0200)
committerJordan Augé <jordan.auge@lip6.fr>
Fri, 23 Aug 2013 12:35:31 +0000 (14:35 +0200)
sample: application added for testing purposes
manifold: fix to base data structure to reflect changes in the manifold package until merge
django: use of the South application for DB migration: see reset.py and README.migrations

27 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
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/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/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]

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
     """
 
     A filter is a set of predicates
     """
 
+    #def __init__(self, s=()):
+    #    super(Filter, self).__init__(s)
+
     @staticmethod
     def from_list(l):
         f = Filter()
     @staticmethod
     def from_list(l):
         f = Filter()
@@ -37,15 +40,29 @@ class Filter(set):
                 f.add(Predicate(key, '=', value))
         return f
 
                 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):
     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):
 
     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])
 
     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])
 
     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:
     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):
         #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)
 
     def get_eq(self, key):
         return self.get_op(key, eq)
@@ -127,9 +151,9 @@ class Filter(set):
 #            dic = predicate.filter(dic)
 #        return dic
 
 #            dic = predicate.filter(dic)
 #        return dic
 
-    def match(self, dic):
+    def match(self, dic, ignore_missing=True):
         for predicate in self:
         for predicate in self:
-            if not predicate.match(dic, ignore_missing=True):
+            if not predicate.match(dic, ignore_missing):
                 return False
         return True
 
                 return False
         return True
 
@@ -140,8 +164,11 @@ class Filter(set):
                 output.append(x)
         return output
 
                 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):
 #    """
 
 #class OldFilter(Parameter, dict):
 #    """
index e45844c..764336f 100644 (file)
 #   Thierry Parmentelat <thierry.parmentelat@inria.fr>
 
 from types                      import StringTypes
 #   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
 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)
 
         #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
 
         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)
             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)
 
         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.filters = Filter()
         self.params  = {}
         self.fields  = set()
-        self.timestamp  = "now" 
         self.timestamp  = 'now' # ignored for 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()) 
     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 ''
         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 = {
         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'
             '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,
             'action': self.action,
             'object': self.object,
             'timestamp': self.timestamp,
-            'filters': self.filters,
+            'filters': self.filters.to_list(),
             'params': self.params,
             'fields': list(self.fields)
         }
             'params': self.params,
             'fields': list(self.fields)
         }
@@ -237,6 +241,7 @@ class Query(object):
             if (debug):
                 import traceback
                 traceback.print_exc()
             if (debug):
                 import traceback
                 traceback.print_exc()
+        self.sanitize()
 
     #--------------------------------------------------------------------------- 
     # Accessors
 
     #--------------------------------------------------------------------------- 
     # Accessors
@@ -371,6 +376,14 @@ class Query(object):
         return self
 
     def filter_by(self, *args):
         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:
         if len(args) == 1:
             filters = args[0]
             if filters == None:
@@ -388,17 +401,20 @@ class Query(object):
         return self
             
     def select(self, *fields):
         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
 
         # 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)
 
                 fields = tuple(tmp)
 
+        if not fields:
+            # Delete all fields
+            self.fields = set()
+            return self
+
         for field in fields:
             self.fields.add(field)
         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
 
 
         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_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]
     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 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
     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])
             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',
     # 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',
     # 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 #
 # 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')),
     (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
     # Debug
     url(r'^debug/', include('debug_platform.urls')),
     # Static files
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
     password    = models.TextField()
     keypair     = models.TextField()
     # institution
+    authority_hrn = models.TextField()
+    # models.ForeignKey(Institution)
 
     objects = RegistrationManager()
 
 
     objects = RegistrationManager()
 
@@ -308,3 +310,4 @@ class PendingUser(models.Model):
 
 class PendingSlice(models.Model):
     slice_name  = models.TextField()
 
 class PendingSlice(models.Model):
     slice_name  = models.TextField()
+    authority_hrn = models.TextField(null=True)
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
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 eba1c9c..f2d877f 100644 (file)
@@ -22,7 +22,7 @@
 
 from django.conf.urls import patterns, include, url
 from portal           import views
 
 from django.conf.urls import patterns, include, url
 from portal           import views
-from portal.views     import UserRegisterView, UserValidateView, DashboardView, PresViewView
+from portal.views     import UserRegisterView, UserValidateView, DashboardView, PresViewView, ValidatePendingView
 from portal.util      import TemplateView
 
 # DEPRECATED #named_register_forms = (
 from portal.util      import TemplateView
 
 # DEPRECATED #named_register_forms = (
@@ -50,6 +50,8 @@ urlpatterns = patterns('',
     url(r'^contact/?$', views.contact),
     # Slice request
     url(r'^slice_request/?$', views.slice_request),
     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'),
 
     url(r'^pres_view/?$', PresViewView.as_view(), name='pres_view'),
     (r'^methods/(?P<type>\w+)/?$', 'portal.views.pres_view_methods'),
index df281fc..980265c 100644 (file)
@@ -39,6 +39,7 @@ from portal.forms                import UserRegisterForm, SliceRequestForm, Cont
 from portal.util                 import RegistrationView, ActivationView
 from portal.models               import PendingUser, PendingSlice
 from manifold.core.query         import Query
 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
 from unfold.page                 import Page
 from myslice.viewutils           import topmenu_items, the_user
 from django.http                 import HttpResponseRedirect, HttpResponse
@@ -61,6 +62,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')
         #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)
 
         page.enqueue_query(slice_query)
         page.enqueue_query(auth_query)
 
@@ -894,3 +897,217 @@ def pres_view_static(request, constraints, id):
 
     json_answer = json.dumps(cmd)
     return HttpResponse (json_answer, mimetype="application/json")
 
     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
+                if not auth_hrn:
+                    auth_hrn = "ple.upmc" # XXX HARDCODED
+
+                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==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..7e006c9
--- /dev/null
+++ b/reset.py
@@ -0,0 +1,3 @@
+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
+