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
     """
 
+    #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
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)
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 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 = (
@@ -50,6 +50,8 @@ urlpatterns = patterns('',
     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 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 manifold.manifoldapi        import execute_query
 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')
+        print "AUTH QUERY =====================", auth_query
+        print "filter", auth_query.filters
         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")
+
+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-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
+