From d68dcefd28c832608cdb359a07a8b871cbe612ae Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jordan=20Aug=C3=A9?= Date: Fri, 23 Aug 2013 14:35:31 +0200 Subject: [PATCH] portal: added wip for PI validation page 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 --- README.migrations | 4 + manifold/core/filter.py | 47 ++++-- manifold/core/query.py | 50 ++++-- manifold/manifoldapi.py | 15 ++ manifold/util/clause.py | 103 ++++++++++++ manifold/util/predicate.py | 10 ++ myslice/settings.py | 2 + myslice/urls.py | 2 + portal/migrations/0001_initial.py | 74 +++++++++ portal/migrations/0002_extend_slice.py | 47 ++++++ portal/migrations/__init__.py | 0 portal/models.py | 3 + portal/static/css/validate_pending.css | 49 ++++++ portal/static/img/authority-icon.png | Bin 0 -> 408 bytes portal/static/img/resource-icon.png | Bin 0 -> 160 bytes portal/static/img/slice-icon.png | Bin 0 -> 265 bytes portal/static/img/user-icon.png | Bin 0 -> 510 bytes portal/templates/validate_pending.html | 66 ++++++++ portal/urls.py | 4 +- portal/views.py | 217 +++++++++++++++++++++++++ requirements/common.txt | 1 + reset.py | 3 + sample/__init__.py | 0 sample/templates/websockets.html | 41 +++++ sample/templates/websockets2.html | 69 ++++++++ sample/urls.py | 29 ++++ sample/views.py | 66 ++++++++ 27 files changed, 874 insertions(+), 28 deletions(-) create mode 100644 README.migrations create mode 100644 manifold/util/clause.py create mode 100644 portal/migrations/0001_initial.py create mode 100644 portal/migrations/0002_extend_slice.py create mode 100644 portal/migrations/__init__.py create mode 100644 portal/static/css/validate_pending.css create mode 100644 portal/static/img/authority-icon.png create mode 100644 portal/static/img/resource-icon.png create mode 100644 portal/static/img/slice-icon.png create mode 100644 portal/static/img/user-icon.png create mode 100644 portal/templates/validate_pending.html create mode 100755 reset.py create mode 100644 sample/__init__.py create mode 100644 sample/templates/websockets.html create mode 100644 sample/templates/websockets2.html create mode 100644 sample/urls.py create mode 100644 sample/views.py diff --git a/README.migrations b/README.migrations new file mode 100644 index 00000000..0d528167 --- /dev/null +++ b/README.migrations @@ -0,0 +1,4 @@ +see reset.sh + +./manage.py schemamigration portal --initial +./manage.py schemamigration portal extend_slice --auto diff --git a/manifold/core/filter.py b/manifold/core/filter.py index 0cf1c384..3a213483 100644 --- a/manifold/core/filter.py +++ b/manifold/core/filter.py @@ -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 '' % ' AND '.join([str(pred) for pred in self]) + return ' AND '.join([str(pred) for pred in self]) def __repr__(self): - return self.__str__() + return '' % ' 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): # """ diff --git a/manifold/core/query.py b/manifold/core/query.py index e45844c9..764336fa 100644 --- a/manifold/core/query.py +++ b/manifold/core/query.py @@ -10,9 +10,10 @@ # Thierry Parmentelat 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 diff --git a/manifold/manifoldapi.py b/manifold/manifoldapi.py index 9361b6eb..81a2caf6 100644 --- a/manifold/manifoldapi.py +++ b/manifold/manifoldapi.py @@ -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 index 00000000..670a689a --- /dev/null +++ b/manifold/util/clause.py @@ -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é +# Marc-Olivier Buob + +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"') diff --git a/manifold/util/predicate.py b/manifold/util/predicate.py index aa09379c..3b5ba80d 100644 --- a/manifold/util/predicate.py +++ b/manifold/util/predicate.py @@ -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 diff --git a/myslice/settings.py b/myslice/settings.py index 06e4eae7..df4f6d7c 100644 --- a/myslice/settings.py +++ b/myslice/settings.py @@ -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 # diff --git a/myslice/urls.py b/myslice/urls.py index 8accdb59..ef1f30ce 100644 --- a/myslice/urls.py +++ b/myslice/urls.py @@ -50,6 +50,8 @@ urlpatterns = patterns( (r'^slice/(?P[\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 index 00000000..24960f3e --- /dev/null +++ b/portal/migrations/0001_initial.py @@ -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 index 00000000..633006aa --- /dev/null +++ b/portal/migrations/0002_extend_slice.py @@ -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 index 00000000..e69de29b diff --git a/portal/models.py b/portal/models.py index 029e3b55..a16e3307 100644 --- a/portal/models.py +++ b/portal/models.py @@ -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 index 00000000..4ed27e14 --- /dev/null +++ b/portal/static/css/validate_pending.css @@ -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 index 0000000000000000000000000000000000000000..bde603ad02c9b94445776ec7e4fd7968a00f0b22 GIT binary patch literal 408 zcmV;J0cZY+P)va*oLRw-YBr44Sg zy-kfUccNIaVr!wSO}Az-$&#@230SB}<4^ask~3SHinCx_2W2x z`IKiX$%L(a&}8ln-?+DZ6B&0qPCa-sE991qfz(4Fle%XV#VEQA{_;&z44y&f1qMM~(dF{sZrxI&yvAcvoWp00004nJ zuqS~qqea!s+dx6d64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1TKJzX3_ yEP9hmbYA{E@4%R_$mR12#>H$sQU)`47#NbyF(p_lE!F}mWbkzLb6Mw<&;$T`b1Jm} literal 0 HcmV?d00001 diff --git a/portal/static/img/slice-icon.png b/portal/static/img/slice-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9e8747bb8a73483e2a48bc2f8b4ecf086b4e6467 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^LO{&I!3HFo9rG`OIK@t$Asj$Z!;#Vf4nJ zu&o4P#xw6ERsjViOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kpe1e^K@|x zk(iqN=l_3uW)M(czkqrAh6vv6D_WG4m4%g=Rgxrzaf9X4_QAvpz5G&yPo^W^%{d?Rk^Z#A$3~^zd@4sKV<+ zmyfenM2IeLSfRz7nEv}{Ur<2d1RNfqt_}$JWi5vJ;nLW;Go!3>G$Z|oDe(-6%28e?W zE>$O&XVBAp@deleQXmn0d;PLe^X|&tU}|->=d2ONl3im8ffU&vq_D5|X5s6~%6+@O zE9-cA6MmN%GX(nI34^Of_zHNNTVW)sEDOebQ2|W68Hp;(-U_8C9v9jH5?`_w9)Q5G z_w|kP=YI)CqqrzkeayrXe}lEAyoIyrz$$fJHr;i zE)p1Y0aQT+V0S;BTgu(uLQAs?Dg;n +{% endblock %} + +{% block unfold1_main %} + +

Pending requests

+ +

My authorities

+ +{% if my_authorities %} + +{% for authority, requests in my_authorities.items %} +

{{authority}}

+ {% for request in requests %} +
+ {{ request.type }} + {{ request.id }} + {{ request.timestamp }} + {{ request.details }} + {% if request.allowed == 'allowed' %} + + {% else %} + {% if request.allowed == 'expired' %} + expired + {% else %} {# denied #} + denied + {% endif %} + {% endif %} +
+ {% endfor %} +{% endfor %} + +{% else %} +There is no pending request waiting for validation. +{% endif %} + +{% if delegation_authorities %} +

Authorities with delegation

+ +{% for authority, requests in delegation_authorities.items %} +

{{authority}}

+ {% for request in requests %} +
+ {{ request.type }} + {{ request.id }} + {{ request.timestamp }} + {{ request.details }} + {% if request.allowed == 'allowed' %} + + {% else %} + {% if request.allowed == 'expired' %} + expired + {% else %} {# denied #} + denied + {% endif %} + {% endif %} +
+ {% endfor %} +{% endfor %} + +{% endif %} + +{% endblock %} diff --git a/portal/urls.py b/portal/urls.py index eba1c9cd..f2d877fd 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -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\w+)/?$', 'portal.views.pres_view_methods'), diff --git a/portal/views.py b/portal/views.py index df281fc8..980265ca 100644 --- a/portal/views.py +++ b/portal/views.py @@ -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
%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 diff --git a/requirements/common.txt b/requirements/common.txt index 6145269b..43afd6d6 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -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 index 00000000..7e006c96 --- /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 index 00000000..e69de29b diff --git a/sample/templates/websockets.html b/sample/templates/websockets.html new file mode 100644 index 00000000..9942fd03 --- /dev/null +++ b/sample/templates/websockets.html @@ -0,0 +1,41 @@ + + + + + + +

WebSocket Echo Test

+
+

+ Message: + +

+
+ + + diff --git a/sample/templates/websockets2.html b/sample/templates/websockets2.html new file mode 100644 index 00000000..b1119d0a --- /dev/null +++ b/sample/templates/websockets2.html @@ -0,0 +1,69 @@ + + + + + + +

Autobahn WebSocket Broadcast Demo

+ +
+

Broadcast Message:

+
+ +

+   
+
diff --git a/sample/urls.py b/sample/urls.py
new file mode 100644
index 00000000..d56b5dc9
--- /dev/null
+++ b/sample/urls.py
@@ -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é 
+# 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
index 00000000..c134269f
--- /dev/null
+++ b/sample/views.py
@@ -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é 
+# 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
+
-- 
2.43.0