--- a/__pkginfo__.py Sun Nov 08 21:53:18 2009 +0100
+++ b/__pkginfo__.py Fri Nov 20 19:35:54 2009 +0100
@@ -10,7 +10,7 @@
numversion = (3, 5, 5)
version = '.'.join(str(num) for num in numversion)
-license = 'LGPL v2'
+license = 'LGPL'
copyright = '''Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
--- a/common/mixins.py Sun Nov 08 21:53:18 2009 +0100
+++ b/common/mixins.py Fri Nov 20 19:35:54 2009 +0100
@@ -180,7 +180,9 @@
For instance, the Person class might want to return a `companyname`
key.
"""
- return set(rs.type for rs, _ in cls.e_schema.attribute_definitions())
+ return set(rschema.type
+ for rschema, attrtype in cls.e_schema.attribute_definitions()
+ if attrtype.type not in ('Password', 'Bytes'))
def as_email_context(self):
"""returns the dictionary as used by the sendmail controller to
--- a/common/tags.py Sun Nov 08 21:53:18 2009 +0100
+++ b/common/tags.py Fri Nov 20 19:35:54 2009 +0100
@@ -18,6 +18,7 @@
attrs.setdefault('escapecontent', self.escapecontent)
return simple_sgml_tag(self.name, __content, **attrs)
+button = tag('button')
input = tag('input')
textarea = tag('textarea')
a = tag('a')
@@ -31,6 +32,9 @@
h3 = tag('h3')
h4 = tag('h4')
h5 = tag('h5')
+tr = tag('tr')
+th = tag('th')
+td = tag('td')
def select(name, id=None, multiple=False, options=[], **attrs):
if multiple:
--- a/dbapi.py Sun Nov 08 21:53:18 2009 +0100
+++ b/dbapi.py Fri Nov 20 19:35:54 2009 +0100
@@ -499,6 +499,8 @@
DBAPIRequest.relative_path = fake
DBAPIRequest.url = fake
DBAPIRequest.next_tabindex = fake
+ DBAPIRequest.get_page_data = fake
+ DBAPIRequest.set_page_data = fake
DBAPIRequest.add_js = fake #cwrb.add_js.im_func
DBAPIRequest.add_css = fake #cwrb.add_css.im_func
# XXX could ask the repo for it's base-url configuration
--- a/devtools/devctl.py Sun Nov 08 21:53:18 2009 +0100
+++ b/devtools/devctl.py Fri Nov 20 19:35:54 2009 +0100
@@ -180,6 +180,8 @@
tschema, tschema, rschema, eschema)
add_msg(w, label)
add_msg(w, label2)
+ # XXX also generate "creating ...' messages for actions in the
+ # addrelated submenu
w('# subject and object forms for each relation type\n')
w('# (no object form for final or symetric relation types)\n')
w('\n')
--- a/doc/book/en/annexes/depends.rst Sun Nov 08 21:53:18 2009 +0100
+++ b/doc/book/en/annexes/depends.rst Fri Nov 20 19:35:54 2009 +0100
@@ -9,10 +9,6 @@
cloning the mercurial forest, here is the list of tools and libraries you need
to have installed in order for CubicWeb to work:
-* mxDateTime - http://www.egenix.com/products/python/mxBase/mxDateTime/ - http://pypi.python.org/pypi/egenix-mx-base
-
-* pyro - http://pyro.sourceforge.net/ - http://pypi.python.org/pypi/Pyro
-
* yapps - http://theory.stanford.edu/~amitp/yapps/ -
http://pypi.python.org/pypi/Yapps2
@@ -22,6 +18,8 @@
* simplejson - http://code.google.com/p/simplejson/ -
http://pypi.python.org/pypi/simplejson
+* docsutils - http://docutils.sourceforge.net/ - http://pypi.python.org/pypi/docutils
+
* lxml - http://codespeak.net/lxml - http://pypi.python.org/pypi/lxml
* twisted - http://twistedmatrix.com/ - http://pypi.python.org/pypi/Twisted
@@ -44,12 +42,17 @@
* indexer - http://www.logilab.org/project/indexer -
http://pypi.python.org/pypi/indexer - included in the forest
+To activate Sparql querying:
+
* fyzz - http://www.logilab.org/project/fyzz - http://pypi.python.org/pypi/fyzz
- included in the forest
-* psycopg2 - http://initd.org/projects/psycopg2 - http://pypi.python.org/pypi/psycopg2
+To use network communication between cubicweb instances / clients:
-* docsutils - http://docutils.sourceforge.net/ - http://pypi.python.org/pypi/docutils
+* Pyro - http://pyro.sourceforge.net/ - http://pypi.python.org/pypi/Pyro
+
+If you're using a Postgres database (recommended):
+* psycopg2 - http://initd.org/projects/psycopg2 - http://pypi.python.org/pypi/psycopg2
For the google-appengine extension to be available, you also need:
@@ -59,5 +62,6 @@
* vobject - http://vobject.skyhouseconsulting.com/ -
http://pypi.python.org/pypi/vobject
+
Any help with the packaging of CubicWeb for more than Debian/Ubuntu (including
eggs, buildouts, etc) will be greatly appreciated.
--- a/doc/book/en/development/datamodel/definition.rst Sun Nov 08 21:53:18 2009 +0100
+++ b/doc/book/en/development/datamodel/definition.rst Fri Nov 20 19:35:54 2009 +0100
@@ -223,7 +223,7 @@
RQL expression for entity type permission :
-* you have to use the class `RQLExpression`
+* you have to use the class `ERQLExpression`
* the used expression corresponds to the WHERE statement of an RQL query
@@ -240,16 +240,16 @@
For RQL expressions on a relation type, the principles are the same except
for the following :
-* you have to use the class `RQLExpression` in the case of a non-final relation
+* you have to use the class `RRQLExpression` in the case of a non-final relation
* in the expression, the variables S, O and U are pre-defined references
to respectively the subject and the object of the current relation (on
which the action is being verified) and the user who executed the query
-* we can also defined rights on attributes of an entity (non-final relation),
+* we can also define rights over attributes of an entity (non-final relation),
knowing that :
- - to define RQL expression, we have to use the class `RQLExpression`
+ - to define RQL expression, we have to use the class `ERQLExpression`
in which X represents the entity the attribute belongs to
- the permissions `add` and `delete` are equivalent. Only `add`/`read`
--- a/entities/test/unittest_base.py Sun Nov 08 21:53:18 2009 +0100
+++ b/entities/test/unittest_base.py Fri Nov 20 19:35:54 2009 +0100
@@ -44,38 +44,24 @@
{'description_format': ('format', 'description')})
-class CWUserTC(BaseEntityTC):
- def test_dc_title_and_name(self):
- e = self.entity('CWUser U WHERE U login "member"')
- self.assertEquals(e.dc_title(), 'member')
- self.assertEquals(e.name(), 'member')
- self.execute(u'SET X firstname "bouah" WHERE X is CWUser, X login "member"')
- self.assertEquals(e.dc_title(), 'member')
- self.assertEquals(e.name(), u'bouah')
- self.execute(u'SET X surname "lôt" WHERE X is CWUser, X login "member"')
- self.assertEquals(e.dc_title(), 'member')
- self.assertEquals(e.name(), u'bouah lôt')
-
class EmailAddressTC(BaseEntityTC):
def test_canonical_form(self):
email1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
email2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com"').get_entity(0, 0)
email3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"').get_entity(0, 0)
- self.execute('SET X prefered_form Y WHERE X eid %s, Y eid %s' % (email1.eid, email2.eid))
+ email1.set_relations(prefered_form=email2)
self.assertEquals(email1.prefered.eid, email2.eid)
self.assertEquals(email2.prefered.eid, email2.eid)
self.assertEquals(email3.prefered.eid, email3.eid)
def test_mangling(self):
- eid = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"')[0][0]
- email = self.entity('Any X WHERE X eid %(x)s', {'x':eid}, 'x')
+ email = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
self.assertEquals(email.display_address(), 'maarten.ter.huurne@philips.com')
self.assertEquals(email.printable_value('address'), 'maarten.ter.huurne@philips.com')
self.vreg.config.global_set_option('mangle-emails', True)
self.assertEquals(email.display_address(), 'maarten.ter.huurne at philips dot com')
self.assertEquals(email.printable_value('address'), 'maarten.ter.huurne at philips dot com')
- eid = self.execute('INSERT EmailAddress X: X address "syt"')[0][0]
- email = self.entity('Any X WHERE X eid %(x)s', {'x':eid}, 'x')
+ email = self.execute('INSERT EmailAddress X: X address "syt"').get_entity(0, 0)
self.assertEquals(email.display_address(), 'syt')
self.assertEquals(email.printable_value('address'), 'syt')
@@ -93,6 +79,25 @@
self.failUnless(e.matching_groups(('xyz', 'managers')))
self.failIf(e.matching_groups(('xyz', 'abcd')))
+ def test_dc_title_and_name(self):
+ e = self.entity('CWUser U WHERE U login "member"')
+ self.assertEquals(e.dc_title(), 'member')
+ self.assertEquals(e.name(), 'member')
+ e.set_attributes(firstname=u'bouah')
+ self.assertEquals(e.dc_title(), 'member')
+ self.assertEquals(e.name(), u'bouah')
+ e.set_attributes(surname=u'lôt')
+ self.assertEquals(e.dc_title(), 'member')
+ self.assertEquals(e.name(), u'bouah lôt')
+
+ def test_allowed_massmail_keys(self):
+ e = self.entity('CWUser U WHERE U login "member"')
+ # Bytes/Password attributes should be omited
+ self.assertEquals(e.allowed_massmail_keys(),
+ set(('surname', 'firstname', 'login', 'last_login_time',
+ 'creation_date', 'modification_date', 'cwuri', 'eid'))
+ )
+
class InterfaceTC(CubicWebTC):
--- a/entities/test/unittest_wfobjs.py Sun Nov 08 21:53:18 2009 +0100
+++ b/entities/test/unittest_wfobjs.py Fri Nov 20 19:35:54 2009 +0100
@@ -482,12 +482,21 @@
# self.commit()
# test that the workflow is correctly enforced
+
+ def _cleanup_msg(self, msg):
+ """remove the variable part of one specific error message"""
+ lmsg = msg.split()
+ lmsg.pop(1)
+ lmsg.pop()
+ return ' '.join(lmsg)
+
def test_transition_checking1(self):
cnx = self.login('stduser')
user = cnx.user(self.session)
ex = self.assertRaises(ValidationError,
user.fire_transition, 'activate')
- self.assertEquals(ex.errors, {'by_transition': u"transition isn't allowed"})
+ self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+ u"transition isn't allowed from")
cnx.close()
def test_transition_checking2(self):
@@ -495,7 +504,8 @@
user = cnx.user(self.session)
ex = self.assertRaises(ValidationError,
user.fire_transition, 'dummy')
- self.assertEquals(ex.errors, {'by_transition': u"transition isn't allowed"})
+ self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+ u"transition isn't allowed from")
cnx.close()
def test_transition_checking3(self):
@@ -507,7 +517,8 @@
session.set_pool()
ex = self.assertRaises(ValidationError,
user.fire_transition, 'deactivate')
- self.assertEquals(ex.errors, {'by_transition': u"transition isn't allowed"})
+ self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+ u"transition isn't allowed from")
# get back now
user.fire_transition('activate')
cnx.commit()
--- a/entity.py Sun Nov 08 21:53:18 2009 +0100
+++ b/entity.py Fri Nov 20 19:35:54 2009 +0100
@@ -34,7 +34,7 @@
def greater_card(rschema, subjtypes, objtypes, index):
for subjtype in subjtypes:
for objtype in objtypes:
- card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
+ card = rschema.rdef(subjtype, objtype).cardinality[index]
if card in '+*':
return card
return '1'
@@ -144,7 +144,8 @@
cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
attr, cls.__regid__)
continue
- if not user.matching_groups(rschema.get_groups('read')):
+ rdef = eschema.rdef(attr)
+ if not user.matching_groups(rdef.get_groups('read')):
continue
var = varmaker.next()
selection.append(var)
@@ -153,7 +154,7 @@
if not rschema.final:
# XXX this does not handle several destination types
desttype = rschema.objects(eschema.type)[0]
- card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
+ card = rdef.cardinality[0]
if card not in '?1':
cls.warning('bad relation %s specified in fetch attrs for %s',
attr, cls)
@@ -256,10 +257,10 @@
self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
def check_perm(self, action):
- self.e_schema.check_perm(self._cw, action, self.eid)
+ self.e_schema.check_perm(self._cw, action, eid=self.eid)
def has_perm(self, action):
- return self.e_schema.has_perm(self._cw, action, self.eid)
+ return self.e_schema.has_perm(self._cw, action, eid=self.eid)
def view(self, vid, __registry='views', **kwargs):
"""shortcut to apply a view on this entity"""
@@ -339,11 +340,11 @@
return u''
if attrtype is None:
attrtype = self.e_schema.destination(attr)
- props = self.e_schema.rproperties(attr)
+ props = self.e_schema.rdef(attr)
if attrtype == 'String':
# internalinalized *and* formatted string such as schema
# description...
- if props.get('internationalizable'):
+ if props.internationalizable:
value = self._cw._(value)
attrformat = self.attr_metadata(attr, 'format')
if attrformat:
@@ -391,11 +392,12 @@
if rschema.type in self.skip_copy_for:
continue
# skip composite relation
- if self.e_schema.subjrproperty(rschema, 'composite'):
+ rdef = self.e_schema.rdef(rschema)
+ if rdef.composite:
continue
# skip relation with card in ?1 else we either change the copied
# object (inlined relation) or inserting some inconsistency
- if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1':
+ if rdef.cardinality[1] in '?1':
continue
rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
rschema.type, rschema.type)
@@ -405,14 +407,15 @@
if rschema.meta:
continue
# skip already defined relations
- if getattr(self, 'reverse_%s' % rschema.type):
+ if self.related(rschema.type, 'object'):
continue
+ rdef = self.e_schema.rdef(rschema, 'object')
# skip composite relation
- if self.e_schema.objrproperty(rschema, 'composite'):
+ if rdef.composite:
continue
# skip relation with card in ?1 else we either change the copied
# object (inlined relation) or inserting some inconsistency
- if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1':
+ if rdef.cardinality[0] in '?1':
continue
rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
rschema.type, rschema.type)
@@ -433,15 +436,16 @@
for rschema in self.e_schema.subject_relations():
if rschema.final:
continue
- if len(rschema.objects(self.e_schema)) > 1:
+ targets = rschema.objects(self.e_schema)
+ if len(targets) > 1:
# ambigous relations, the querier doesn't handle
# outer join correctly in this case
continue
if rschema.inlined:
matching_groups = self._cw.user.matching_groups
- if matching_groups(rschema.get_groups('read')) and \
- all(matching_groups(es.get_groups('read'))
- for es in rschema.objects(self.e_schema)):
+ rdef = rschema.rdef(self.e_schema, targets[0])
+ if matching_groups(rdef.get_groups('read')) and \
+ all(matching_groups(e.get_groups('read')) for e in targets):
yield rschema, 'subject'
def to_complete_attributes(self, skip_bytes=True):
@@ -453,7 +457,8 @@
if attr == 'eid':
continue
# password retreival is blocked at the repository server level
- if not self._cw.user.matching_groups(rschema.get_groups('read')) \
+ rdef = rschema.rdef(self.e_schema, attrschema)
+ if not self._cw.user.matching_groups(rdef.get_groups('read')) \
or attrschema.type == 'Password':
self[attr] = None
continue
@@ -489,24 +494,21 @@
if self.relation_cached(rtype, role):
continue
var = varmaker.next()
+ targettype = rschema.targets(self.e_schema, role)[0]
+ rdef = rschema.role_rdef(self.e_schema, targettype, role)
+ card = rdef.role_cardinality(role)
+ assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
+ role, card)
if role == 'subject':
- targettype = rschema.objects(self.e_schema)[0]
- card = rschema.rproperty(self.e_schema, targettype,
- 'cardinality')[0]
if card == '1':
rql.append('%s %s %s' % (V, rtype, var))
- else: # '?"
+ else:
rql.append('%s %s %s?' % (V, rtype, var))
else:
- targettype = rschema.subjects(self.e_schema)[1]
- card = rschema.rproperty(self.e_schema, targettype,
- 'cardinality')[1]
if card == '1':
rql.append('%s %s %s' % (var, rtype, V))
- else: # '?"
+ else:
rql.append('%s? %s %s' % (var, rtype, V))
- assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
- role, card)
selected.append(((rtype, role), var))
if selected:
# select V, we need it as the left most selected variable
@@ -652,16 +654,16 @@
restriction = []
args = {}
securitycheck_args = {}
- insertsecurity = (rtype.has_local_role('add') and not
- rtype.has_perm(self._cw, 'add', **securitycheck_args))
- constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+ rdef = rtype.role_rdef(self.e_schema, targettype, role)
+ insertsecurity = (rdef.has_local_role('add') and not
+ rdef.has_perm(self._cw, 'add', **securitycheck_args))
if vocabconstraints:
# RQLConstraint is a subclass for RQLVocabularyConstraint, so they
# will be included as well
- restriction += [cstr.restriction for cstr in constraints
+ restriction += [cstr.restriction for cstr in rdef.constraints
if isinstance(cstr, RQLVocabularyConstraint)]
else:
- restriction += [cstr.restriction for cstr in constraints
+ restriction += [cstr.restriction for cstr in rdef.constraints
if isinstance(cstr, RQLConstraint)]
etypecls = self._cw.vreg['etypes'].etype_class(targettype)
rql = etypecls.fetch_rql(self._cw.user, restriction,
@@ -671,12 +673,16 @@
before, after = rql.split(' WHERE ', 1)
rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
if insertsecurity:
- rqlexprs = rtype.get_rqlexprs('add')
+ rqlexprs = rdef.get_rqlexprs('add')
rewriter = RQLRewriter(self._cw)
rqlst = self._cw.vreg.parse(self._cw, rql, args)
+ if not self.has_eid():
+ existant = searchedvar
+ else:
+ existant = None # instead of 'SO', improve perfs
for select in rqlst.children:
rewriter.rewrite(select, [((searchedvar, searchedvar), rqlexprs)],
- select.solutions, args)
+ select.solutions, args, existant)
rql = rqlst.as_string()
return rql, args
@@ -719,12 +725,10 @@
related = list(rset.entities(col))
rschema = self._cw.vreg.schema.rschema(rtype)
if role == 'subject':
- rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
- 'cardinality')[1]
+ rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1]
target = 'object'
else:
- rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
- 'cardinality')[0]
+ rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0]
target = 'subject'
if rcard in '?1':
for rentity in related:
--- a/etwist/server.py Sun Nov 08 21:53:18 2009 +0100
+++ b/etwist/server.py Fri Nov 20 19:35:54 2009 +0100
@@ -103,11 +103,13 @@
assert self.base_url[-1] == '/'
self.https_url = config['https-url']
assert not self.https_url or self.https_url[-1] == '/'
+ # instantiate publisher here and not in init_publisher to get some
+ # checks done before daemonization (eg versions consistency)
+ self.appli = CubicWebPublisher(config, debug=self.debugmode)
+ self.versioned_datadir = 'data%s' % config.instance_md5_version()
def init_publisher(self):
config = self.config
- self.appli = CubicWebPublisher(config, debug=self.debugmode)
- self.versioned_datadir = 'data%s' % config.instance_md5_version()
# when we have an in-memory repository, clean unused sessions every XX
# seconds and properly shutdown the server
if config.repo_method == 'inmemory':
@@ -382,14 +384,13 @@
port = config['port'] or 8080
reactor.listenTCP(port, channel.HTTPFactory(website))
logger = getLogger('cubicweb.twisted')
- logger.info('instance started on %s', root_resource.base_url)
if not debug:
print 'instance starting in the background'
if daemonize():
return # child process
if config['pid-file']:
# ensure the directory where the pid-file should be set exists (for
- # instance /var/run/cubicweb may be deleted on computer restart)
+ # instance /var/run/cubicweb may be deleted on computer restart)
piddir = os.path.dirname(config['pid-file'])
if not os.path.exists(piddir):
os.makedirs(piddir)
@@ -403,6 +404,7 @@
uid = getpwnam(config['uid']).pw_uid
os.setuid(uid)
root_resource.start_service()
+ logger.info('instance started on %s', root_resource.base_url)
if config['profile']:
prof = hotshot.Profile(config['profile'])
prof.runcall(reactor.run)
--- a/etwist/twctl.py Sun Nov 08 21:53:18 2009 +0100
+++ b/etwist/twctl.py Fri Nov 20 19:35:54 2009 +0100
@@ -9,10 +9,14 @@
import sys
from cubicweb.toolsutils import CommandHandler
+from cubicweb.web.webctl import WebCreateHandler
# trigger configuration registration
import cubicweb.etwist.twconfig # pylint: disable-msg=W0611
+class TWCreateHandler(WebCreateHandler):
+ cfgname = 'twisted'
+
class TWStartHandler(CommandHandler):
cmdname = 'start'
cfgname = 'twisted'
@@ -28,8 +32,8 @@
try:
from cubicweb.server import serverctl
-
- class AllInOneCreateHandler(serverctl.RepositoryCreateHandler):
+ class AllInOneCreateHandler(serverctl.RepositoryCreateHandler,
+ TWCreateHandler):
"""configuration to get an instance running in a twisted web server
integrating a repository server in the same process
"""
@@ -38,6 +42,7 @@
def bootstrap(self, cubes, inputlevel=0):
"""bootstrap this configuration"""
serverctl.RepositoryCreateHandler.bootstrap(self, cubes, inputlevel)
+ TWCreateHandler.bootstrap(self, cubes, inputlevel)
class AllInOneStartHandler(TWStartHandler):
cmdname = 'start'
--- a/hooks/integrity.py Sun Nov 08 21:53:18 2009 +0100
+++ b/hooks/integrity.py Fri Nov 20 19:35:54 2009 +0100
@@ -33,6 +33,8 @@
# recheck pending eids
if self.session.deleted_in_transaction(self.eid):
return
+ if self.rtype in self.session.transaction_data.get('pendingrtypes', ()):
+ return
if self.session.unsafe_execute(*self._rql()).rowcount < 1:
etype = self.session.describe(self.eid)[0]
_ = self.session._
@@ -87,22 +89,16 @@
def after_add_entity(self):
eid = self.entity.eid
eschema = self.entity.e_schema
- for rschema, targetschemas, x in eschema.relation_definitions():
+ for rschema, targetschemas, role in eschema.relation_definitions():
# skip automatically handled relations
if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
continue
- if x == 'subject':
- subjtype = eschema
- objtype = targetschemas[0].type
- cardindex = 0
+ if role == 'subject':
opcls = _CheckSRelationOp
else:
- subjtype = targetschemas[0].type
- objtype = eschema
- cardindex = 1
opcls = _CheckORelationOp
- card = rschema.rproperty(subjtype, objtype, 'cardinality')
- if card[cardindex] in '1+':
+ rdef = rschema.role_rdef(eschema, targetschemas[0], role)
+ if rdef.role_cardinality(role) in '1+':
self.checkrel_if_necessary(opcls, rschema.type, eid)
def before_delete_relation(self):
@@ -173,7 +169,7 @@
entity = self.entity
for attr in entity.edited_attributes:
if schema.rschema(attr).final:
- constraints = [c for c in entity.e_schema.constraints(attr)
+ constraints = [c for c in entity.rdef(attr).constraints
if isinstance(c, RQLVocabularyConstraint)]
if constraints:
_CheckConstraintsOp(self._cw, constraints=constraints,
--- a/hooks/security.py Sun Nov 08 21:53:18 2009 +0100
+++ b/hooks/security.py Fri Nov 20 19:35:54 2009 +0100
@@ -25,10 +25,10 @@
for attr in editedattrs:
if attr in defaults:
continue
- rschema = eschema.subjrels[attr]
- if rschema.final: # non final relation are checked by other hooks
+ rdef = eschema.rdef(attr)
+ if rdef.final: # non final relation are checked by other hooks
# add/delete should be equivalent (XXX: unify them into 'update' ?)
- rschema.check_perm(session, 'add', eid)
+ rdef.check_perm(session, 'add', eid=eid)
class _CheckEntityPermissionOp(hook.LateOperation):
@@ -43,7 +43,10 @@
class _CheckRelationPermissionOp(hook.LateOperation):
def precommit_event(self):
- self.rschema.check_perm(self.session, self.action, self.eidfrom, self.eidto)
+ rdef = self.rschema.rdef(self.session.describe(self.eidfrom)[0],
+ self.session.describe(self.eidto)[0])
+ rdef.check_perm(self.session, self.action,
+ fromeid=self.eidfrom, toeid=self.eidto)
def commit_event(self):
pass
@@ -95,7 +98,9 @@
if (self.eidfrom, self.rtype, self.eidto) in nocheck:
return
rschema = self._cw.repo.schema[self.rtype]
- rschema.check_perm(self._cw, 'add', self.eidfrom, self.eidto)
+ rdef = rschema.rdef(self._cw.describe(self.eidfrom)[0],
+ self._cw.describe(self.eidto)[0])
+ rdef.check_perm(session, 'add', fromeid=self.eidfrom, toeid=self.eidto)
class AfterAddRelationSecurityHook(SecurityHook):
@@ -114,17 +119,7 @@
eidfrom=self.eidfrom,
eidto=self.eidto)
else:
- rschema.check_perm(self._cw, 'add', self.eidfrom, self.eidto)
-
-
-class BeforeDelRelationSecurityHook(SecurityHook):
- __regid__ = 'securitybeforedelrelation'
- events = ('before_delete_relation',)
+ rdef = rschema.rdef(session.describe(self.eidfrom)[0],
+ session.describe(self.eidto)[0])
+ rdef.check_perm(session, 'add', fromeid=self.eidfrom, toeid=self.eidto)
- def __call__(self):
- nocheck = self._cw.transaction_data.get('skip-security', ())
- if (self.eidfrom, self.rtype, self.eidto) in nocheck:
- return
- self._cw.repo.schema[self.rtype].check_perm(self._cw, 'delete',
- self.eidfrom, self.eidto)
-
--- a/hooks/syncschema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/hooks/syncschema.py Fri Nov 20 19:35:54 2009 +0100
@@ -153,8 +153,11 @@
def commit_event(self):
rebuildinfered = self.session.data.get('rebuild-infered', True)
- repo = self.session.repo
- repo.set_schema(repo.schema, rebuildinfered=rebuildinfered)
+ self.repo.set_schema(self.repo.schema, rebuildinfered=rebuildinfered)
+ # CWUser class might have changed, update current session users
+ cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser')
+ for session in self.repo._sessions.values():
+ session.user.__class__ = cwuser_cls
def rollback_event(self):
self.precommit_event()
@@ -188,19 +191,6 @@
return i + 1
-class MemSchemaPermOperation(MemSchemaOperation):
- """base class to synchronize schema permission definitions"""
- def __init__(self, session, perm, etype_eid):
- self.perm = perm
- try:
- self.name = session.entity_from_eid(etype_eid).name
- except IndexError:
- self.error('changing permission of a no more existant type #%s',
- etype_eid)
- else:
- hook.Operation.__init__(self, session)
-
-
# operations for high-level source database alteration ########################
class SourceDbCWETypeRename(hook.Operation):
@@ -495,7 +485,7 @@
return
subjtype, rtype, objtype = session.vreg.schema.schema_by_eid(rdef.eid)
cstrtype = self.entity.type
- oldcstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+ oldcstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
table = SQL_PREFIX + str(subjtype)
column = SQL_PREFIX + str(rtype)
@@ -575,8 +565,7 @@
"""actually add the relation type to the instance's schema"""
eid = None # make pylint happy
def commit_event(self):
- rschema = self.session.vreg.schema.add_relation_type(self.kobj)
- rschema.set_default_groups()
+ self.session.vreg.schema.add_relation_type(self.kobj)
class MemSchemaCWRTypeUpdate(MemSchemaOperation):
@@ -614,7 +603,7 @@
def commit_event(self):
# structure should be clean, not need to remove entity's relations
# at this point
- self.rschema._rproperties[self.kobj].update(self.values)
+ self.rschema.rdef[self.kobj].update(self.values)
class MemSchemaRDefDel(MemSchemaOperation):
@@ -646,7 +635,7 @@
subjtype, rtype, objtype = self.session.vreg.schema.schema_by_eid(rdef.eid)
self.prepare_constraints(subjtype, rtype, objtype)
cstrtype = self.entity.type
- self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+ self.cstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
self.newcstr.eid = self.entity.eid
@@ -672,13 +661,9 @@
self.constraints.remove(self.cstr)
-class MemSchemaPermCWGroupAdd(MemSchemaPermOperation):
+class MemSchemaPermissionAdd(MemSchemaOperation):
"""synchronize schema when a *_permission relation has been added on a group
"""
- def __init__(self, session, perm, etype_eid, group_eid):
- self.group = session.entity_from_eid(group_eid).name
- super(MemSchemaPermCWGroupAdd, self).__init__(
- session, perm, etype_eid)
def commit_event(self):
"""the observed connections pool has been commited"""
@@ -688,17 +673,21 @@
# duh, schema not found, log error and skip operation
self.error('no schema for %s', self.name)
return
- groups = list(erschema.get_groups(self.perm))
+ perms = list(erschema.action_permissions(self.action))
+ if hasattr(self, group_eid):
+ perm = self.session.entity_from_eid(self.group_eid).name
+ else:
+ perm = erschema.rql_expression(self.expr)
try:
- groups.index(self.group)
- self.warning('group %s already have permission %s on %s',
- self.group, self.perm, erschema.type)
+ perms.index(perm)
+ self.warning('%s already in permissions for %s on %s',
+ perm, self.action, erschema)
except ValueError:
- groups.append(self.group)
- erschema.set_groups(self.perm, groups)
+ perms.append(perm)
+ erschema.set_action_permissions(self.action, perms)
-class MemSchemaPermCWGroupDel(MemSchemaPermCWGroupAdd):
+class MemSchemaPermissionDel(MemSchemaPermissionAdd):
"""synchronize schema when a *_permission relation has been deleted from a
group
"""
@@ -711,60 +700,17 @@
# duh, schema not found, log error and skip operation
self.error('no schema for %s', self.name)
return
- groups = list(erschema.get_groups(self.perm))
- try:
- groups.remove(self.group)
- erschema.set_groups(self.perm, groups)
- except ValueError:
- self.error('can\'t remove permission %s on %s to group %s',
- self.perm, erschema.type, self.group)
-
-
-class MemSchemaPermRQLExpressionAdd(MemSchemaPermOperation):
- """synchronize schema when a *_permission relation has been added on a rql
- expression
- """
- def __init__(self, session, perm, etype_eid, expression):
- self.expr = expression
- super(MemSchemaPermRQLExpressionAdd, self).__init__(
- session, perm, etype_eid)
-
- def commit_event(self):
- """the observed connections pool has been commited"""
+ perms = list(erschema.action_permissions(self.action))
+ if hasattr(self, group_eid):
+ perm = self.session.entity_from_eid(self.group_eid).name
+ else:
+ perm = erschema.rql_expression(self.expr)
try:
- erschema = self.session.vreg.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- exprs = list(erschema.get_rqlexprs(self.perm))
- exprs.append(erschema.rql_expression(self.expr))
- erschema.set_rqlexprs(self.perm, exprs)
-
-
-class MemSchemaPermRQLExpressionDel(MemSchemaPermRQLExpressionAdd):
- """synchronize schema when a *_permission relation has been deleted from an
- rql expression
- """
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- erschema = self.session.vreg.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- rqlexprs = list(erschema.get_rqlexprs(self.perm))
- for i, rqlexpr in enumerate(rqlexprs):
- if rqlexpr.expression == self.expr:
- rqlexprs.pop(i)
- break
- else:
- self.error('can\'t remove permission %s on %s for expression %s',
- self.perm, erschema.type, self.expr)
- return
- erschema.set_rqlexprs(self.perm, rqlexprs)
+ perms.remove(self.group)
+ erschema.set_action_permissions(self.action, perms)
+ except ValueError:
+ self.error('can\'t remove permission %s for %s on %s',
+ perm, self.action, erschema)
class MemSchemaSpecializesAdd(MemSchemaOperation):
@@ -835,6 +781,7 @@
* register an operation to add the entity type to the instance's
schema on commit
"""
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/hooks/syncschema.py
__regid__ = 'syncaddcwetype'
events = ('after_add_entity',)
@@ -859,7 +806,7 @@
rschema = schema[rtype]
sampletype = rschema.subjects()[0]
desttype = rschema.objects()[0]
- props = rschema.rproperties(sampletype, desttype)
+ props = rschema.rdef(sampletype, desttype)
relrqls += list(ss.rdef2rql(rschema, name, desttype, props))
# now remove it !
schema.del_entity_type(name)
@@ -965,6 +912,17 @@
SourceDbCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues,
entity=entity)
+def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
+ errors = {}
+ # don't use getattr(entity, attr), we would get the modified value if any
+ for attr in ro_attrs:
+ if attr in entity.edited_attributes:
+ origval, newval = entity_oldnewvalue(entity, attr)
+ if newval != origval:
+ errors[attr] = session._("can't change the %s attribute") % \
+ display_name(session, attr)
+ if errors:
+ raise ValidationError(entity.eid, errors)
# relation_type hooks ##########################################################
@@ -1102,8 +1060,8 @@
entity = self._cw.entity_from_eid(self.eidto)
subjtype, rtype, objtype = schema.schema_by_eid(self.eidfrom)
try:
- cstr = rtype.constraint_by_type(subjtype, objtype,
- entity.cstrtype[0].name)
+ cstr = rtype.rdef(subjtype, objtype).constraint_by_type(
+ entity.cstrtype[0].name)
except IndexError:
self._cw.critical('constraint type no more accessible')
else:
@@ -1124,12 +1082,14 @@
events = ('after_add_relation',)
def __call__(self):
- perm = self.rtype.split('_', 1)[0]
+ action = self.rtype.split('_', 1)[0]
if self._cw.describe(self.eidto)[0] == 'CWGroup':
- MemSchemaPermCWGroupAdd(self._cw, perm, self.eidfrom, self.eidto)
+ MemSchemaPermissionAdd(self._cw, action=action, eid=self.eidfrom,
+ group_eid=self.eidto)
else: # RQLExpression
expr = self._cw.entity_from_eid(self.eidto).expression
- MemSchemaPermRQLExpressionAdd(self._cw, perm, self.eidfrom, expr)
+ MemSchemaPermissionAdd(session, action=action, eid=self.eidfrom,
+ expr=expr)
class BeforeDelPermissionHook(AfterAddPermissionHook):
@@ -1143,12 +1103,14 @@
def __call__(self):
if self._cw.deleted_in_transaction(self.eidfrom):
return
- perm = self.rtype.split('_', 1)[0]
+ action = self.rtype.split('_', 1)[0]
if self._cw.describe(self.eidto)[0] == 'CWGroup':
- MemSchemaPermCWGroupDel(self._cw, perm, self.eidfrom, self.eidto)
+ MemSchemaPermissionDel(self._cw, action=action, eid=self.eidfrom,
+ group_eid=self.eidto)
else: # RQLExpression
expr = self._cw.entity_from_eid(self.eidto).expression
- MemSchemaPermRQLExpressionDel(self._cw, perm, self.eidfrom, expr)
+ MemSchemaPermissionDel(self._cw, action=action, eid=self.eidfrom,
+ expr=expr)
# specializes synchronization hooks ############################################
--- a/hooks/workflow.py Sun Nov 08 21:53:18 2009 +0100
+++ b/hooks/workflow.py Fri Nov 20 19:35:54 2009 +0100
@@ -224,9 +224,10 @@
msg = session._("transition doesn't belong to entity's workflow")
raise ValidationError(entity.eid, {'by_transition': msg})
if not tr.has_input_state(fromstate):
- msg = session._("transition isn't allowed")
+ msg = session._("transition %s isn't allowed from %s") % (
+ _(tr.name), _(fromstate.name))
raise ValidationError(entity.eid, {'by_transition': msg})
- if not tr.may_be_fired(foreid):
+ if not tr.may_be_fired(foreid):
msg = session._("transition may not be fired")
raise ValidationError(entity.eid, {'by_transition': msg})
if entity.get('to_state'):
--- a/i18n/en.po Sun Nov 08 21:53:18 2009 +0100
+++ b/i18n/en.po Fri Nov 20 19:35:54 2009 +0100
@@ -356,6 +356,9 @@
msgid "From:"
msgstr ""
+msgid "Help"
+msgstr ""
+
msgid "Int"
msgstr "Integer"
@@ -787,6 +790,12 @@
msgid "actions_follow_description"
msgstr ""
+msgid "actions_help"
+msgstr ""
+
+msgid "actions_help_description"
+msgstr ""
+
msgid "actions_logout"
msgstr "logout"
--- a/i18n/es.po Sun Nov 08 21:53:18 2009 +0100
+++ b/i18n/es.po Fri Nov 20 19:35:54 2009 +0100
@@ -364,6 +364,9 @@
msgid "From:"
msgstr "De: "
+msgid "Help"
+msgstr ""
+
msgid "Int"
msgstr "Número entero"
@@ -810,6 +813,12 @@
msgid "actions_follow_description"
msgstr ""
+msgid "actions_help"
+msgstr ""
+
+msgid "actions_help_description"
+msgstr ""
+
msgid "actions_logout"
msgstr "Desconectarse"
--- a/i18n/fr.po Sun Nov 08 21:53:18 2009 +0100
+++ b/i18n/fr.po Fri Nov 20 19:35:54 2009 +0100
@@ -363,6 +363,9 @@
msgid "From:"
msgstr "De :"
+msgid "Help"
+msgstr "Aide"
+
msgid "Int"
msgstr "Nombre entier"
@@ -815,6 +818,12 @@
msgid "actions_follow_description"
msgstr ""
+msgid "actions_help"
+msgstr ""
+
+msgid "actions_help_description"
+msgstr ""
+
msgid "actions_logout"
msgstr "se déconnecter"
@@ -2037,8 +2046,9 @@
"destination state. No destination state means that transition should go back "
"to the state from which we've entered the subworkflow."
msgstr ""
-"état de destination de la transition. Si aucun état de destination n'est spécifié, la transition "
-"ira vers l'état depuis lequel l'entité est entrée dans le sous-workflow."
+"état de destination de la transition. Si aucun état de destination n'est "
+"spécifié, la transition ira vers l'état depuis lequel l'entité est entrée "
+"dans le sous-workflow."
msgid "destination_state"
msgstr "état de destination"
@@ -3076,7 +3086,7 @@
msgstr "vues possibles"
msgid "powered by CubicWeb"
-msgstr "utilise la technologie CubicWeb"
+msgstr "construit avec CubicWeb"
msgid "prefered_form"
msgstr "forme préférée"
@@ -3229,10 +3239,10 @@
msgctxt "CWGroup"
msgid "require_group_object"
-msgstr "dé"
+msgstr "de"
msgid "require_group_object"
-msgstr "à les droits"
+msgstr "a les droits"
msgid "require_permission"
msgstr "require permission"
@@ -3815,7 +3825,7 @@
msgstr "peut modifier"
msgid "update_permission_object"
-msgstr "à la permission de modifier"
+msgstr "a la permission de modifier"
msgid "updated"
msgstr ""
--- a/misc/migration/bootstrapmigration_repository.py Sun Nov 08 21:53:18 2009 +0100
+++ b/misc/migration/bootstrapmigration_repository.py Fri Nov 20 19:35:54 2009 +0100
@@ -28,6 +28,17 @@
session.set_shared_data('do-not-insert-cwuri', False)
if applcubicwebversion < (3, 5, 0) and cubicwebversion >= (3, 5, 0):
+ # check that migration is not doomed
+ rset = rql('Any X,Y WHERE X transition_of E, Y transition_of E, '
+ 'X name N, Y name N, NOT X identity Y',
+ ask_confirm=False)
+ if rset:
+ from logilab.common.shellutils import ASK
+ if not ASK.confirm('Migration will fail because of transitions with the same name. '
+ 'Continue anyway ?'):
+ import sys
+ sys.exit(1)
+ # proceed with migration
add_entity_type('Workflow')
add_entity_type('BaseTransition')
add_entity_type('WorkflowTransition')
--- a/misc/migration/postcreate.py Sun Nov 08 21:53:18 2009 +0100
+++ b/misc/migration/postcreate.py Fri Nov 20 19:35:54 2009 +0100
@@ -34,7 +34,10 @@
# create anonymous user if all-in-one config and anonymous user has been specified
if hasattr(config, 'anonymous_user'):
anonlogin, anonpwd = config.anonymous_user()
- if anonlogin:
+ if anonlogin == session.user.login:
+ print 'you are using a manager account as anonymous user.'
+ print 'Hopefully this is not a production instance...'
+ elif anonlogin:
rql('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s,'
'X in_group G WHERE G name "guests"',
{'login': unicode(anonlogin), 'pwd': anonpwd})
--- a/rqlrewrite.py Sun Nov 08 21:53:18 2009 +0100
+++ b/rqlrewrite.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,6 +13,7 @@
from rql import nodes as n, stmts, TypeResolverException
from logilab.common.compat import any
+from logilab.common.graph import has_path
from cubicweb import Unauthorized, typed_eid
@@ -134,7 +135,7 @@
if len(self.select.solutions) < len(self.solutions):
raise Unsupported()
- def rewrite(self, select, snippets, solutions, kwargs):
+ def rewrite(self, select, snippets, solutions, kwargs, existingvars=None):
"""
snippets: (varmap, list of rql expression)
with varmap a *tuple* (select var, snippet var)
@@ -146,6 +147,7 @@
self.removing_ambiguity = False
self.exists_snippet = {}
self.pending_keys = []
+ self.existingvars = existingvars
# we have to annotate the rqlst before inserting snippets, even though
# we'll have to redo it latter
self.annotate(select)
@@ -213,6 +215,14 @@
def insert_snippet(self, varmap, snippetrqlst, parent=None):
new = snippetrqlst.where.accept(self)
+ existing = self.existingvars
+ self.existingvars = None
+ try:
+ return self._insert_snippet(varmap, parent, new)
+ finally:
+ self.existingvars = existing
+
+ def _insert_snippet(self, varmap, parent, new):
if new is not None:
if self.varinfo.get('stinfo', {}).get('optrelations'):
assert parent is None
@@ -392,12 +402,12 @@
orel = self.varinfo['lhs_rels'][sniprel.r_type]
cardindex = 0
ttypes_func = rschema.objects
- rprop = rschema.rproperty
+ rdef = rschema.rdef
else: # target == 'subject':
orel = self.varinfo['rhs_rels'][sniprel.r_type]
cardindex = 1
ttypes_func = rschema.subjects
- rprop = lambda x, y, z: rschema.rproperty(y, x, z)
+ rdef = lambda x, y: rschema.rdef(y, x)
except KeyError, ex:
# may be raised by self.varinfo['xhs_rels'][sniprel.r_type]
return None
@@ -409,7 +419,7 @@
# variable from the original query
for etype in self.varinfo['stinfo']['possibletypes']:
for ttype in ttypes_func(etype):
- if rprop(etype, ttype, 'cardinality')[cardindex] in '+*':
+ if rdef(etype, ttype).cardinality[cardindex] in '+*':
return None
return orel
@@ -478,8 +488,26 @@
def visit_exists(self, node):
return self._visit_unary(node, n.Exists)
+ def keep_var(self, varname):
+ if varname in 'SO':
+ return varname in self.existingvars
+ if varname == 'U':
+ return True
+ vargraph = self.current_expr.vargraph
+ for existingvar in self.existingvars:
+ #path = has_path(vargraph, varname, existingvar)
+ if has_path(vargraph, varname, existingvar):
+ return True
+ # no path from this variable to an existing variable
+ return False
+
def visit_relation(self, node):
lhs, rhs = node.get_variable_parts()
+ # remove relations where an unexistant variable and or a variable linked
+ # to an unexistant variable is used.
+ if self.existingvars:
+ if not self.keep_var(lhs.name):
+ return
if node.r_type in ('has_add_permission', 'has_update_permission',
'has_delete_permission', 'has_read_permission'):
assert lhs.name == 'U'
@@ -488,6 +516,8 @@
self.pending_keys.append( (key, action) )
return
if isinstance(rhs, n.VariableRef):
+ if self.existingvars and not self.keep_var(rhs.name):
+ return
if lhs.name in self.revvarmap and rhs.name != 'U':
orel = self._may_be_shared_with(node, 'object', lhs.name)
if orel is not None:
--- a/rset.py Sun Nov 08 21:53:18 2009 +0100
+++ b/rset.py Fri Nov 20 19:35:54 2009 +0100
@@ -455,25 +455,22 @@
select = rqlst
# take care, due to outer join support, we may find None
# values for non final relation
- for i, attr, x in attr_desc_iterator(select, col):
+ for i, attr, role in attr_desc_iterator(select, col):
outerselidx = rqlst.subquery_selection_index(select, i)
if outerselidx is None:
continue
- if x == 'subject':
+ if role == 'subject':
rschema = eschema.subjrels[attr]
if rschema.final:
entity[attr] = rowvalues[outerselidx]
continue
- tetype = rschema.objects(etype)[0]
- card = rschema.rproperty(etype, tetype, 'cardinality')[0]
else:
rschema = eschema.objrels[attr]
- tetype = rschema.subjects(etype)[0]
- card = rschema.rproperty(tetype, etype, 'cardinality')[1]
+ rdef = eschema.rdef(attr, role)
# only keep value if it can't be multivalued
- if card in '1?':
+ if rdef.role_cardinality(role) in '1?':
if rowvalues[outerselidx] is None:
- if x == 'subject':
+ if role == 'subject':
rql = 'Any Y WHERE X %s Y, X eid %s'
else:
rql = 'Any Y WHERE Y %s X, X eid %s'
@@ -481,7 +478,7 @@
req.decorate_rset(rrset)
else:
rrset = self._build_entity(row, outerselidx).as_rset()
- entity.set_related_cache(attr, x, rrset)
+ entity.set_related_cache(attr, role, rrset)
return entity
@cached
--- a/schema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/schema.py Fri Nov 20 19:35:54 2009 +0100
@@ -20,7 +20,8 @@
from logilab.common.compat import any
from yams import BadSchemaDefinition, buildobjs as ybo
-from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema
+from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
+ RelationDefinitionSchema, PermissionMixIn
from yams.constraints import (BaseConstraint, StaticVocabularyConstraint,
FormatConstraint)
from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
@@ -127,7 +128,7 @@
ERSchema.display_name = ERSchema_display_name
@cached
-def ERSchema_get_groups(self, action):
+def get_groups(self, action):
"""return the groups authorized to perform <action> on entities of
this type
@@ -140,28 +141,13 @@
assert action in self.ACTIONS, action
#assert action in self._groups, '%s %s' % (self, action)
try:
- return frozenset(g for g in self._groups[action] if isinstance(g, basestring))
+ return frozenset(g for g in self.permissions[action] if isinstance(g, basestring))
except KeyError:
return ()
-ERSchema.get_groups = ERSchema_get_groups
-
-def ERSchema_set_groups(self, action, groups):
- """set the groups allowed to perform <action> on entities of this type. Don't
- change rql expressions for the same action.
-
- :type action: str
- :param action: the name of a permission
-
- :type groups: list or tuple
- :param groups: names of the groups granted to do the given action
- """
- assert action in self.ACTIONS, action
- clear_cache(self, 'ERSchema_get_groups')
- self._groups[action] = tuple(groups) + self.get_rqlexprs(action)
-ERSchema.set_groups = ERSchema_set_groups
+PermissionMixIn.get_groups = get_groups
@cached
-def ERSchema_get_rqlexprs(self, action):
+def get_rqlexprs(self, action):
"""return the rql expressions representing queries to check the user is allowed
to perform <action> on entities of this type
@@ -174,27 +160,13 @@
assert action in self.ACTIONS, action
#assert action in self._rqlexprs, '%s %s' % (self, action)
try:
- return tuple(g for g in self._groups[action] if not isinstance(g, basestring))
+ return tuple(g for g in self.permissions[action] if not isinstance(g, basestring))
except KeyError:
return ()
-ERSchema.get_rqlexprs = ERSchema_get_rqlexprs
-
-def ERSchema_set_rqlexprs(self, action, rqlexprs):
- """set the rql expression allowing to perform <action> on entities of this type. Don't
- change groups for the same action.
-
- :type action: str
- :param action: the name of a permission
+PermissionMixIn.get_rqlexprs = get_rqlexprs
- :type rqlexprs: list or tuple
- :param rqlexprs: the rql expressions allowing the given action
- """
- assert action in self.ACTIONS, action
- clear_cache(self, 'ERSchema_get_rqlexprs')
- self._groups[action] = tuple(self.get_groups(action)) + tuple(rqlexprs)
-ERSchema.set_rqlexprs = ERSchema_set_rqlexprs
-
-def ERSchema_set_permissions(self, action, permissions):
+orig_set_action_permissions = PermissionMixIn.set_action_permissions
+def set_action_permissions(self, action, permissions):
"""set the groups and rql expressions allowing to perform <action> on
entities of this type
@@ -204,22 +176,12 @@
:type permissions: tuple
:param permissions: the groups and rql expressions allowing the given action
"""
- assert action in self.ACTIONS, action
- clear_cache(self, 'ERSchema_get_rqlexprs')
- clear_cache(self, 'ERSchema_get_groups')
- self._groups[action] = tuple(permissions)
-ERSchema.set_permissions = ERSchema_set_permissions
+ orig_set_action_permissions(self, action, tuple(permissions))
+ clear_cache(self, 'get_rqlexprs')
+ clear_cache(self, 'get_groups')
+PermissionMixIn.set_action_permissions = set_action_permissions
-def ERSchema_has_perm(self, session, action, *args, **kwargs):
- """return true if the action is granted globaly or localy"""
- try:
- self.check_perm(session, action, *args, **kwargs)
- return True
- except Unauthorized:
- return False
-ERSchema.has_perm = ERSchema_has_perm
-
-def ERSchema_has_local_role(self, action):
+def has_local_role(self, action):
"""return true if the action *may* be granted localy (eg either rql
expressions or the owners group are used in security definition)
@@ -230,9 +192,83 @@
if self.get_rqlexprs(action):
return True
if action in ('update', 'delete'):
- return self.has_group(action, 'owners')
+ return 'owners' in self.get_groups(action)
return False
-ERSchema.has_local_role = ERSchema_has_local_role
+PermissionMixIn.has_local_role = has_local_role
+
+def may_have_permission(self, action, req):
+ if action != 'read' and not (self.has_local_role('read') or
+ self.has_perm(req, 'read')):
+ return False
+ return self.has_local_role(action) or self.has_perm(req, action)
+PermissionMixIn.may_have_permission = may_have_permission
+
+def has_perm(self, session, action, **kwargs):
+ """return true if the action is granted globaly or localy"""
+ try:
+ self.check_perm(session, action, **kwargs)
+ return True
+ except Unauthorized:
+ return False
+PermissionMixIn.has_perm = has_perm
+
+def check_perm(self, session, action, **kwargs):
+ # NB: session may be a server session or a request object check user is
+ # in an allowed group, if so that's enough internal sessions should
+ # always stop there
+ groups = self.get_groups(action)
+ if session.user.matching_groups(groups):
+ return
+ # if 'owners' in allowed groups, check if the user actually owns this
+ # object, if so that's enough
+ if 'owners' in groups and 'eid' in kwargs and session.user.owns(kwargs['eid']):
+ return
+ # else if there is some rql expressions, check them
+ if any(rqlexpr.check(session, **kwargs)
+ for rqlexpr in self.get_rqlexprs(action)):
+ return
+ raise Unauthorized(action, str(self))
+PermissionMixIn.check_perm = check_perm
+
+
+RelationDefinitionSchema._RPROPERTIES['eid'] = None
+
+def rql_expression(self, expression, mainvars=None, eid=None):
+ """rql expression factory"""
+ if self.rtype.final:
+ return ERQLExpression(expression, mainvars, eid)
+ return RRQLExpression(expression, mainvars, eid)
+RelationDefinitionSchema.rql_expression = rql_expression
+
+orig_check_permission_definitions = RelationDefinitionSchema.check_permission_definitions
+def check_permission_definitions(self):
+ orig_check_permission_definitions(self)
+ schema = self.subject.schema
+ for action, groups in self.permissions.iteritems():
+ for group_or_rqlexpr in groups:
+ if action == 'read' and \
+ isinstance(group_or_rqlexpr, RQLExpression):
+ msg = "can't use rql expression for read permission of %s"
+ raise BadSchemaDefinition(msg % self)
+ elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
+ if schema.reading_from_database:
+ # we didn't have final relation earlier, so turn
+ # RRQLExpression into ERQLExpression now
+ rqlexpr = group_or_rqlexpr
+ newrqlexprs = [x for x in self.get_rqlexprs(action)
+ if not x is rqlexpr]
+ newrqlexprs.append(ERQLExpression(rqlexpr.expression,
+ rqlexpr.mainvars,
+ rqlexpr.eid))
+ self.set_rqlexprs(action, newrqlexprs)
+ else:
+ msg = "can't use RRQLExpression on %s, use an ERQLExpression"
+ raise BadSchemaDefinition(msg % self)
+ elif not self.final and \
+ isinstance(group_or_rqlexpr, ERQLExpression):
+ msg = "can't use ERQLExpression on %s, use a RRQLExpression"
+ raise BadSchemaDefinition(msg % self)
+RelationDefinitionSchema.check_permission_definitions = check_permission_definitions
def system_etypes(schema):
@@ -256,8 +292,8 @@
eid = getattr(edef, 'eid', None)
self.eid = eid
# take care: no _groups attribute when deep-copying
- if getattr(self, '_groups', None):
- for groups in self._groups.itervalues():
+ if getattr(self, 'permissions', None):
+ for groups in self.permissions.itervalues():
for group_or_rqlexpr in groups:
if isinstance(group_or_rqlexpr, RRQLExpression):
msg = "can't use RRQLExpression on an entity type, use an ERQLExpression (%s)"
@@ -304,7 +340,7 @@
if rschema.final:
if rschema == 'has_text':
has_has_text = True
- elif self.rproperty(rschema, 'fulltextindexed'):
+ elif self.rdef(rschema).get('fulltextindexed'):
may_need_has_text = True
elif rschema.fulltext_container:
if rschema.fulltext_container == 'subject':
@@ -329,32 +365,12 @@
"""return True if this entity type is used to build the schema"""
return self.type in SCHEMA_TYPES
- def check_perm(self, session, action, eid=None):
- # NB: session may be a server session or a request object
- user = session.user
- # check user is in an allowed group, if so that's enough
- # internal sessions should always stop there
- if user.matching_groups(self.get_groups(action)):
- return
- # if 'owners' in allowed groups, check if the user actually owns this
- # object, if so that's enough
- if eid is not None and 'owners' in self.get_groups(action) and \
- user.owns(eid):
- return
- # else if there is some rql expressions, check them
- if any(rqlexpr.check(session, eid)
- for rqlexpr in self.get_rqlexprs(action)):
- return
- raise Unauthorized(action, str(self))
-
def rql_expression(self, expression, mainvars=None, eid=None):
"""rql expression factory"""
return ERQLExpression(expression, mainvars, eid)
class CubicWebRelationSchema(RelationSchema):
- RelationSchema._RPROPERTIES['eid'] = None
- _perms_checked = False
def __init__(self, schema=None, rdef=None, eid=None, **kwargs):
if rdef is not None:
@@ -369,73 +385,52 @@
def meta(self):
return self.type in META_RTYPES
- def update(self, subjschema, objschema, rdef):
- super(CubicWebRelationSchema, self).update(subjschema, objschema, rdef)
- if not self._perms_checked and self._groups:
- for action, groups in self._groups.iteritems():
- for group_or_rqlexpr in groups:
- if action == 'read' and \
- isinstance(group_or_rqlexpr, RQLExpression):
- msg = "can't use rql expression for read permission of "\
- "a relation type (%s)"
- raise BadSchemaDefinition(msg % self.type)
- elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
- if self.schema.reading_from_database:
- # we didn't have final relation earlier, so turn
- # RRQLExpression into ERQLExpression now
- rqlexpr = group_or_rqlexpr
- newrqlexprs = [x for x in self.get_rqlexprs(action) if not x is rqlexpr]
- newrqlexprs.append(ERQLExpression(rqlexpr.expression,
- rqlexpr.mainvars,
- rqlexpr.eid))
- self.set_rqlexprs(action, newrqlexprs)
- else:
- msg = "can't use RRQLExpression on a final relation "\
- "type (eg attribute relation), use an ERQLExpression (%s)"
- raise BadSchemaDefinition(msg % self.type)
- elif not self.final and \
- isinstance(group_or_rqlexpr, ERQLExpression):
- msg = "can't use ERQLExpression on a relation type, use "\
- "a RRQLExpression (%s)"
- raise BadSchemaDefinition(msg % self.type)
- self._perms_checked = True
-
- def cardinality(self, subjtype, objtype, target):
- card = self.rproperty(subjtype, objtype, 'cardinality')
- return (target == 'subject' and card[0]) or \
- (target == 'object' and card[1])
-
def schema_relation(self):
"""return True if this relation type is used to build the schema"""
return self.type in SCHEMA_TYPES
- def physical_mode(self):
- """return an appropriate mode for physical storage of this relation type:
- * 'subjectinline' if every possible subject cardinalities are 1 or ?
- * 'objectinline' if 'subjectinline' mode is not possible but every
- possible object cardinalities are 1 or ?
- * None if neither 'subjectinline' and 'objectinline'
- """
- assert not self.final
- return self.inlined and 'subjectinline' or None
+ def may_have_permission(self, action, req, eschema=None, role=None):
+ if eschema is not None:
+ for tschema in rschema.targets(eschema, role):
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if rdef.may_have_permission(action, req):
+ return True
+ else:
+ for rdef in self.rdefs.itervalues():
+ if rdef.may_have_permission(action, req):
+ return True
+ return False
- def check_perm(self, session, action, *args, **kwargs):
- # NB: session may be a server session or a request object check user is
- # in an allowed group, if so that's enough internal sessions should
- # always stop there
- if session.user.matching_groups(self.get_groups(action)):
- return
- # else if there is some rql expressions, check them
- if any(rqlexpr.check(session, *args, **kwargs)
- for rqlexpr in self.get_rqlexprs(action)):
- return
- raise Unauthorized(action, str(self))
+ def has_perm(self, session, action, **kwargs):
+ """return true if the action is granted globaly or localy"""
+ if 'fromeid' in kwargs:
+ subjtype = session.describe(kwargs['fromeid'])
+ else:
+ subjtype = None
+ if 'toeid' in kwargs:
+ objtype = session.describe(kwargs['toeid'])
+ else:
+ objtype = Nono
+ if objtype and subjtype:
+ return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+ elif subjtype:
+ for tschema in rschema.targets(subjtype, 'subject'):
+ rdef = rschema.rdef(subjtype, tschema)
+ if not rdef.has_perm(action, req, **kwargs):
+ return False
+ elif objtype:
+ for tschema in rschema.targets(objtype, 'object'):
+ rdef = rschema.rdef(tschema, objtype)
+ if not rdef.has_perm(action, req, **kwargs):
+ return False
+ else:
+ for rdef in self.rdefs.itervalues():
+ if not rdef.has_perm(action, req, **kwargs):
+ return False
- def rql_expression(self, expression, mainvars=None, eid=None):
- """rql expression factory"""
- if self.final:
- return ERQLExpression(expression, mainvars, eid)
- return RRQLExpression(expression, mainvars, eid)
+ @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)')
+ def cardinality(self, subjtype, objtype, target):
+ return self.rdef(subjtype, objtype).role_cardinality(target)
class CubicWebSchema(Schema):
@@ -460,13 +455,10 @@
ybo.register_base_types(self)
rschema = self.add_relation_type(ybo.RelationType('eid'))
rschema.final = True
- rschema.set_default_groups()
rschema = self.add_relation_type(ybo.RelationType('has_text'))
rschema.final = True
- rschema.set_default_groups()
rschema = self.add_relation_type(ybo.RelationType('identity'))
rschema.final = False
- rschema.set_default_groups()
def add_entity_type(self, edef):
edef.name = edef.name.encode()
@@ -508,11 +500,10 @@
rdef.name = rdef.name.lower()
rdef.subject = bw_normalize_etype(rdef.subject)
rdef.object = bw_normalize_etype(rdef.object)
- if super(CubicWebSchema, self).add_relation_def(rdef):
+ rdefs = super(CubicWebSchema, self).add_relation_def(rdef)
+ if rdefs:
try:
- self._eid_index[rdef.eid] = (self.eschema(rdef.subject),
- self.rschema(rdef.name),
- self.eschema(rdef.object))
+ self._eid_index[rdef.eid] = rdefs
except AttributeError:
pass # not a serialized schema
@@ -523,7 +514,9 @@
def del_relation_def(self, subjtype, rtype, objtype):
for k, v in self._eid_index.items():
- if v == (subjtype, rtype, objtype):
+ if not isinstance(v, RelationDefinitionSchema):
+ continue
+ if v.subject == subjtype and v.rtype == rtype and v.object == objtype:
del self._eid_index[k]
break
super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype)
@@ -646,7 +639,7 @@
if len(self.rqlst.defined_vars[mainvar].references()) <= 2:
_LOGGER.warn('You did not use the %s variable in your RQL '
'expression %s', mainvar, self)
- # syntax tree used by read security (inserted in queries when necessary
+ # syntax tree used by read security (inserted in queries when necessary)
self.snippet_rqlst = parse(self.minimal_rql, print_errors=False).children[0]
def __str__(self):
@@ -654,6 +647,11 @@
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.full_rql)
+ def __cmp__(self, other):
+ if hasattr(other, 'expression'):
+ return cmp(other.expression, self.expression)
+ return False
+
def __deepcopy__(self, memo):
return self.__class__(self.expression, self.mainvars)
def __getstate__(self):
@@ -755,7 +753,7 @@
for eaction, var, col in has_perm_defs:
for i in xrange(len(rset)):
eschema = get_eschema(rset.description[i][col])
- eschema.check_perm(session, eaction, rset[i][col])
+ eschema.check_perm(session, eaction, eid=rset[i][col])
if self.eid is not None:
session.local_perm_cache[key] = True
return True
@@ -806,10 +804,24 @@
mainvars.append('S')
if 'O' in defined:
mainvars.append('O')
+ if 'U' in defined:
+ mainvars.append('U')
if not mainvars:
raise Exception('unable to guess selection variables')
mainvars = ','.join(mainvars)
RQLExpression.__init__(self, expression, mainvars, eid)
+ # graph of links between variable, used by rql rewriter
+ self.vargraph = {}
+ for relation in self.rqlst.get_nodes(nodes.Relation):
+ try:
+ rhsvarname = relation.children[1].children[0].variable.name
+ lhsvarname = relation.children[0].name
+ except AttributeError:
+ pass
+ else:
+ self.vargraph.setdefault(lhsvarname, []).append(rhsvarname)
+ self.vargraph.setdefault(rhsvarname, []).append(lhsvarname)
+ #self.vargraph[(lhsvarname, rhsvarname)] = relation.r_type
@property
def full_rql(self):
--- a/schemas/Bookmark.py Sun Nov 08 21:53:18 2009 +0100
+++ b/schemas/Bookmark.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,7 +13,7 @@
class Bookmark(EntityType):
"""bookmarks are used to have user's specific internal links"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users',),
'delete': ('managers', 'owners',),
@@ -29,7 +29,7 @@
class bookmarked_by(RelationType):
- permissions = {'read': ('managers', 'users', 'guests',),
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
# test user in users group to avoid granting permission to anonymous user
'add': ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
'delete': ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
--- a/schemas/base.py Sun Nov 08 21:53:18 2009 +0100
+++ b/schemas/base.py Fri Nov 20 19:35:54 2009 +0100
@@ -16,7 +16,7 @@
class CWUser(WorkflowableEntityType):
"""define a CubicWeb user"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', ERQLExpression('X identity U')),
'add': ('managers',),
'delete': ('managers',),
@@ -37,12 +37,12 @@
in_group = SubjectRelation('CWGroup', cardinality='+*',
constraints=[RQLConstraint('NOT O name "owners"')],
- description=_('groups grant permissions to the user'))
+ description=_('groups grant __permissions__ to the user'))
class EmailAddress(EntityType):
"""an electronic mail address associated to a short alias"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',), # XXX if P use_email X, U has_read_permission P
'add': ('managers', 'users',),
'delete': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
@@ -59,7 +59,7 @@
class use_email(RelationType):
""" """
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', RRQLExpression('U has_update_permission S'),),
'delete': ('managers', RRQLExpression('U has_update_permission S'),),
@@ -68,12 +68,12 @@
class primary_email(RelationType):
"""the prefered email"""
- permissions = use_email.permissions
+ __permissions__ = use_email.__permissions__
class prefered_form(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
- # XXX should have update permissions on both subject and object,
+ # XXX should have update __permissions__ on both subject and object,
# though by doing this we will probably have no way to add
# this relation in the web ui. The easiest way to acheive this
# is probably to be able to have "U has_update_permission O" as
@@ -85,13 +85,13 @@
class in_group(RelationType):
"""core relation indicating a user's groups"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class owned_by(RelationType):
"""core relation indicating owners of an entity. This relation
implicitly put the owner into the owners group for the entity
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('S owned_by U'),),
'delete': ('managers', RRQLExpression('S owned_by U'),),
@@ -104,7 +104,7 @@
class created_by(RelationType):
"""core relation indicating the original creator of an entity"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -134,12 +134,37 @@
subject = '*'
object = 'String'
+
+class CWProperty(EntityType):
+ """used for cubicweb configuration. Once a property has been created you
+ can't change the key.
+ """
+ __permissions__ = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', 'users',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners',),
+ }
+ # key is a reserved word for mysql
+ pkey = String(required=True, internationalizable=True, maxsize=256,
+ description=_('defines what\'s the property is applied for. '
+ 'You must select this first to be able to set '
+ 'value'))
+ value = String(internationalizable=True, maxsize=256)
+
+ for_user = SubjectRelation('CWUser', cardinality='?*', composite='object',
+ description=_('user for which this property is '
+ 'applying. If this relation is not '
+ 'set, the property is considered as'
+ ' a global property'))
+
+
# XXX find a better relation name
class for_user(RelationType):
"""link a property to the user which want this property customization. Unless
you're a site manager, this relation will be handled automatically.
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -150,10 +175,11 @@
composite = 'object'
cardinality = '?*'
+
class CWPermission(EntityType):
"""entity type that may be used to construct some advanced security configuration
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
description=_('name or identifier of the permission'))
@@ -168,7 +194,7 @@
"""link a permission to the entity. This permission should be used in the
security definition of the entity's type to be useful.
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -176,7 +202,7 @@
class require_group(RelationType):
"""used to grant a permission to a group"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -196,7 +222,7 @@
NOTE: You'll have to explicitly declare which entity types can have a
same_as relation
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users'),
'delete': ('managers', 'owners'),
@@ -216,7 +242,7 @@
Also, checkout the AppObject.get_cache() method.
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'update': ('managers', 'users',), # XXX
@@ -233,9 +259,9 @@
class identical_to(RelationType):
"""identical to"""
symetric = True
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
- # XXX should have update permissions on both subject and object,
+ # XXX should have update __permissions__ on both subject and object,
# though by doing this we will probably have no way to add
# this relation in the web ui. The easiest way to acheive this
# is probably to be able to have "U has_update_permission O" as
@@ -248,7 +274,7 @@
class see_also(RelationType):
"""generic relation to link one entity to another"""
symetric = True
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', RRQLExpression('U has_update_permission S'),),
'delete': ('managers', RRQLExpression('U has_update_permission S'),),
--- a/schemas/bootstrap.py Sun Nov 08 21:53:18 2009 +0100
+++ b/schemas/bootstrap.py Fri Nov 20 19:35:54 2009 +0100
@@ -17,7 +17,7 @@
# access to this
class CWEType(EntityType):
"""define an entity type, used to build the instance schema"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
description = RichString(internationalizable=True,
@@ -28,7 +28,7 @@
class CWRType(EntityType):
"""define a relation type, used to build the instance schema"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
description = RichString(internationalizable=True,
@@ -48,7 +48,7 @@
used to build the instance schema
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
relation_type = SubjectRelation('CWRType', cardinality='1*',
constraints=[RQLConstraint('O final TRUE')],
composite='object')
@@ -85,7 +85,7 @@
used to build the instance schema
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
relation_type = SubjectRelation('CWRType', cardinality='1*',
constraints=[RQLConstraint('O final FALSE')],
composite='object')
@@ -115,8 +115,8 @@
# not restricted since it has to be read when checking allowed transitions
class RQLExpression(EntityType):
- """define a rql expression used to define permissions"""
- permissions = META_ETYPE_PERMS
+ """define a rql expression used to define __permissions__"""
+ __permissions__ = META_ETYPE_PERMS
exprtype = String(required=True, vocabulary=['ERQLExpression', 'RRQLExpression'])
mainvars = String(maxsize=8,
description=_('name of the main variables which should be '
@@ -131,11 +131,11 @@
'relation\'subject, object and to '
'the request user. '))
- read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+?', composite='subject',
+ read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='+?', composite='subject',
description=_('rql expression allowing to read entities/relations of this type'))
- add_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
+ add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
description=_('rql expression allowing to add entities/relations of this type'))
- delete_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
+ delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
description=_('rql expression allowing to delete entities/relations of this type'))
update_permission = ObjectRelation('CWEType', cardinality='*?', composite='subject',
description=_('rql expression allowing to update entities of this type'))
@@ -143,14 +143,14 @@
class CWConstraint(EntityType):
"""define a schema constraint"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
cstrtype = SubjectRelation('CWConstraintType', cardinality='1*')
value = String(description=_('depends on the constraint type'))
class CWConstraintType(EntityType):
"""define a schema constraint type"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
@@ -158,15 +158,15 @@
# not restricted since it has to be read when checking allowed transitions
class CWGroup(EntityType):
"""define a CubicWeb users group"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
- read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+*',
+ read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='+*',
description=_('groups allowed to read entities/relations of this type'))
- add_permission = ObjectRelation(('CWEType', 'CWRType'),
+ add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
description=_('groups allowed to add entities/relations of this type'))
- delete_permission = ObjectRelation(('CWEType', 'CWRType'),
+ delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
description=_('groups allowed to delete entities/relations of this type'))
update_permission = ObjectRelation('CWEType',
description=_('groups allowed to update entities of this type'))
@@ -192,50 +192,50 @@
class relation_type(RelationType):
"""link a relation definition to its relation type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class from_entity(RelationType):
"""link a relation definition to its subject entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class to_entity(RelationType):
"""link a relation definition to its object entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class constrained_by(RelationType):
"""constraints applying on this relation"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class cstrtype(RelationType):
"""constraint factory"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class read_permission(RelationType):
"""core relation giving to a group the permission to read an entity or
relation type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class add_permission(RelationType):
"""core relation giving to a group the permission to add an entity or
relation type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class delete_permission(RelationType):
"""core relation giving to a group the permission to delete an entity or
relation type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class update_permission(RelationType):
"""core relation giving to a group the permission to update an entity type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class is_(RelationType):
@@ -244,7 +244,7 @@
name = 'is'
# don't explicitly set composite here, this is handled anyway
#composite = 'object'
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': (),
'delete': (),
@@ -259,7 +259,7 @@
"""
# don't explicitly set composite here, this is handled anyway
#composite = 'object'
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': (),
'delete': (),
@@ -270,7 +270,7 @@
class specializes(RelationType):
name = 'specializes'
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
--- a/schemas/workflow.py Sun Nov 08 21:53:18 2009 +0100
+++ b/schemas/workflow.py Fri Nov 20 19:35:54 2009 +0100
@@ -15,7 +15,7 @@
HOOKS_RTYPE_PERMS)
class Workflow(EntityType):
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256)
@@ -33,7 +33,7 @@
class default_workflow(RelationType):
"""default workflow for an entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
subject = 'CWEType'
object = 'Workflow'
@@ -45,7 +45,7 @@
"""used to associate simple states to an entity type and/or to define
workflows
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256)
@@ -64,7 +64,7 @@
class BaseTransition(EntityType):
"""abstract base class for transitions"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256)
@@ -126,7 +126,7 @@
class TrInfo(EntityType):
"""workflow history item"""
# 'add' security actually done by hooks
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',), # XXX U has_read_permission O ?
'add': ('managers', 'users', 'guests',),
'delete': (), # XXX should we allow managers to delete TrInfo?
@@ -142,11 +142,11 @@
# get actor and date time using owned_by and creation_date
class from_state(RelationType):
- permissions = HOOKS_RTYPE_PERMS.copy()
+ __permissions__ = HOOKS_RTYPE_PERMS.copy()
inlined = True
class to_state(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers',),
'delete': (),
@@ -155,7 +155,7 @@
class by_transition(RelationType):
# 'add' security actually done by hooks
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users', 'guests',),
'delete': (),
@@ -164,11 +164,11 @@
class workflow_of(RelationType):
"""link a workflow to one or more entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class state_of(RelationType):
"""link a state to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class transition_of(RelationType):
@@ -178,40 +178,40 @@
class subworkflow(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class exit_point(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class subworkflow_state(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class initial_state(RelationType):
"""indicate which state should be used by default when an entity using
states is created
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class destination_state(RelationType):
"""destination state of a transition"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class allowed_transition(RelationType):
"""allowed transition from this state"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
# "abstract" relations, set by WorkflowableEntityType ##########################
class custom_workflow(RelationType):
"""allow to set a specific workflow for an entity"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
cardinality = '?*'
constraints = [RQLConstraint('S is ET, O workflow_of ET')]
@@ -221,7 +221,7 @@
class wf_info_for(RelationType):
"""link a transition information to its object"""
# 'add' security actually done by hooks
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users', 'guests',),
'delete': (),
@@ -236,7 +236,7 @@
class in_state(RelationType):
"""indicate the current state of an entity"""
- permissions = HOOKS_RTYPE_PERMS
+ __permissions__ = HOOKS_RTYPE_PERMS
# not inlined intentionnaly since when using ldap sources, user'state
# has to be stored outside the CWUser table
--- a/schemaviewer.py Sun Nov 08 21:53:18 2009 +0100
+++ b/schemaviewer.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,6 +13,7 @@
I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
+
class SchemaViewer(object):
"""return an ureport layout for some part of a schema"""
def __init__(self, req=None, encoding=None):
@@ -68,7 +69,8 @@
_ = self.req._
data = [_('attribute'), _('type'), _('default'), _('constraints')]
for rschema, aschema in eschema.attribute_definitions():
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+ rdef = eschema.rdef(rschema)
+ if not rdef.may_have_permission('read', self.req):
continue
aname = rschema.type
if aname == 'eid':
@@ -78,7 +80,7 @@
defaultval = eschema.default(aname)
if defaultval is not None:
default = self.to_string(defaultval)
- elif eschema.rproperty(rschema, 'cardinality')[0] == '1':
+ elif rdef.cardinality[0] == '1':
default = _('required field')
else:
default = ''
@@ -119,20 +121,23 @@
t_vars = []
rels = []
first = True
- for rschema, targetschemas, x in eschema.relation_definitions():
+ for rschema, targetschemas, role in eschema.relation_definitions():
if rschema.type in skiptypes:
continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
- continue
rschemaurl = self.rschema_link_url(rschema)
for oeschema in targetschemas:
+ rdef = rschema.role_rdef(eschema, oeschema, role)
+ if not rdef.may_have_permission('read', self.req):
+ continue
label = rschema.type
- if x == 'subject':
+ if role == 'subject':
cards = rschema.rproperty(eschema, oeschema, 'cardinality')
else:
cards = rschema.rproperty(oeschema, eschema, 'cardinality')
cards = cards[::-1]
- label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label, display_name(self.req, label, x), CARD_MAP[cards[0]])
+ label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label,
+ display_name(self.req, label, role),
+ CARD_MAP[cards[0]])
rlink = Link(rschemaurl, label)
elink = Link(self.eschema_link_url(oeschema), oeschema.type)
if first:
--- a/selectors.py Sun Nov 08 21:53:18 2009 +0100
+++ b/selectors.py Fri Nov 20 19:35:54 2009 +0100
@@ -667,19 +667,6 @@
self.target_etype = target_etype
self.action = action
- @lltrace
- def __call__(self, cls, req, *args, **kwargs):
- rschema = req.vreg.schema.rschema(self.rtype)
- if not (rschema.has_perm(req, self.action)
- or rschema.has_local_role(self.action)):
- return 0
- if self.action != 'read':
- if not (rschema.has_perm(req, 'read')
- or rschema.has_local_role('read')):
- return 0
- score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
- return score
-
def score_class(self, eclass, req):
eschema = eclass.e_schema
try:
@@ -691,12 +678,13 @@
return 0
if self.target_etype is not None:
try:
- if self.role == 'subject':
- return int(self.target_etype in rschema.objects(eschema))
- else:
- return int(self.target_etype in rschema.subjects(eschema))
+ rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
+ if not rdef.may_have_permission(self.action, req):
+ return 0
except KeyError:
return 0
+ else:
+ return rschema.may_have_permission(self.action, req, eschema, self.role)
return 1
--- a/server/__init__.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/__init__.py Fri Nov 20 19:35:54 2009 +0100
@@ -29,7 +29,7 @@
DBG_SQL = 2 # executed sql
DBG_REPO = 4 # repository events
DBG_MS = 8 # multi-sources
-DBG_MORE = 16 # repository events
+DBG_MORE = 16 # more verbosity
# current debug mode
DEBUG = 0
--- a/server/hookhelper.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/hookhelper.py Fri Nov 20 19:35:54 2009 +0100
@@ -11,6 +11,17 @@
from cubicweb import RepositoryError
+def entity_oldnewvalue(entity, attr):
+ """returns the couple (old attr value, new attr value)
+ NOTE: will only work in a before_update_entity hook
+ """
+ # get new value and remove from local dict to force a db query to
+ # fetch old value
+ newvalue = entity.pop(attr, None)
+ oldvalue = getattr(entity, attr)
+ if newvalue is not None:
+ entity[attr] = newvalue
+ return oldvalue, newvalue
@deprecated('[3.6] entity_name is deprecated, use entity.name')
def entity_name(session, eid):
--- a/server/migractions.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/migractions.py Fri Nov 20 19:35:54 2009 +0100
@@ -104,9 +104,14 @@
if migrscript.endswith('.sql'):
if self.execscript_confirm(migrscript):
sqlexec(open(migrscript).read(), self.session.system_sql)
- else:
+ elif migrscript.endswith('.py'):
return super(ServerMigrationHelper, self).cmd_process_script(
migrscript, funcname, *args, **kwargs)
+ else:
+ print
+ print ('-> ignoring %s, only .py and .sql scripts are considered' %
+ migrscript)
+ print
self.commit()
except:
self.rollback()
@@ -305,35 +310,33 @@
# schema synchronization internals ########################################
- def _synchronize_permissions(self, ertype):
+ def _synchronize_permissions(self, erschema, teid):
"""permission synchronization for an entity or relation type"""
- if ertype in VIRTUAL_RTYPES:
+ if erschema in VIRTUAL_RTYPES:
return
- newrschema = self.fs_schema[ertype]
- teid = self.repo.schema[ertype].eid
- if 'update' in newrschema.ACTIONS or newrschema.final:
+ assert teid, erschema
+ if 'update' in erschema.ACTIONS or erschema.final:
# entity type
exprtype = u'ERQLExpression'
else:
# relation type
exprtype = u'RRQLExpression'
- assert teid, ertype
gm = self.group_mapping()
confirm = self.verbosity >= 2
# * remove possibly deprecated permission (eg in the persistent schema
# but not in the new schema)
# * synchronize existing expressions
# * add new groups/expressions
- for action in newrschema.ACTIONS:
+ for action in erschema.ACTIONS:
perm = '%s_permission' % action
# handle groups
- newgroups = list(newrschema.get_groups(action))
+ newgroups = list(erschema.get_groups(action))
for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, '
'T eid %%(x)s' % perm, {'x': teid}, 'x',
ask_confirm=False):
if not gname in newgroups:
if not confirm or self.confirm('remove %s permission of %s to %s?'
- % (action, ertype, gname)):
+ % (action, erschema, gname)):
self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
% (perm, teid),
{'x': geid}, 'x', ask_confirm=False)
@@ -341,18 +344,18 @@
newgroups.remove(gname)
for gname in newgroups:
if not confirm or self.confirm('grant %s permission of %s to %s?'
- % (action, ertype, gname)):
+ % (action, erschema, gname)):
self.rqlexec('SET T %s G WHERE G eid %%(x)s, T eid %s'
% (perm, teid),
{'x': gm[gname]}, 'x', ask_confirm=False)
# handle rql expressions
- newexprs = dict((expr.expression, expr) for expr in newrschema.get_rqlexprs(action))
+ newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action))
for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, '
'T eid %s' % (perm, teid),
ask_confirm=False):
if not expression in newexprs:
if not confirm or self.confirm('remove %s expression for %s permission of %s?'
- % (expression, action, ertype)):
+ % (expression, action, erschema)):
# deleting the relation will delete the expression entity
self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
% (perm, teid),
@@ -362,7 +365,7 @@
for expression in newexprs.values():
expr = expression.expression
if not confirm or self.confirm('add %s expression for %s permission of %s?'
- % (expr, action, ertype)):
+ % (expr, action, erschema)):
self.rqlexec('INSERT RQLExpression X: X exprtype %%(exprtype)s, '
'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
'WHERE T eid %%(x)s' % perm,
@@ -394,9 +397,7 @@
for subj, obj in rschema.iter_rdefs():
if not reporschema.has_rdef(subj, obj):
continue
- self._synchronize_rdef_schema(subj, rschema, obj)
- if syncperms:
- self._synchronize_permissions(rtype)
+ self._synchronize_rdef_schema(subj, rschema, obj, syncperms=syncperms)
def _synchronize_eschema(self, etype, syncperms=True):
"""synchronize properties of the persistent entity schema against
@@ -446,9 +447,9 @@
continue
self._synchronize_rdef_schema(subj, rschema, obj)
if syncperms:
- self._synchronize_permissions(etype)
+ self._synchronize_permissions(eschema, repoeschema.eid)
- def _synchronize_rdef_schema(self, subjtype, rtype, objtype):
+ def _synchronize_rdef_schema(self, subjtype, rtype, objtype, syncperms=True):
"""synchronize properties of the persistent relation definition schema
against its current definition:
* order and other properties
@@ -467,12 +468,14 @@
self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
ask_confirm=confirm)
# constraints
- newconstraints = list(rschema.rproperty(subjtype, objtype, 'constraints'))
+ rdef = rschema.rdef(subjtype, objtype)
+ repordef = reporschema.rdef(subjtype, objtype)
+ newconstraints = list(rdef.constraints)
# 1. remove old constraints and update constraints of the same type
# NOTE: don't use rschema.constraint_by_type because it may be
# out of sync with newconstraints when multiple
# constraints of the same type are used
- for cstr in reporschema.rproperty(subjtype, objtype, 'constraints'):
+ for cstr in repordef.constraints:
for newcstr in newconstraints:
if newcstr.type() == cstr.type():
break
@@ -496,6 +499,8 @@
self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
newcstr),
ask_confirm=confirm)
+ if syncperms:
+ self._synchronize_permissions(rdef, repordef.eid)
# base actions ############################################################
@@ -526,10 +531,17 @@
sourcescfg[cube] = ask_source_config(cube)
self.config.write_sources_file(sourcescfg)
clear_cache(self.config, 'read_sources_file')
+ # ensure added cube is in config cubes
+ # XXX worth restoring on error?
+ if not cube in self.config._cubes:
+ self.config._cubes += (cube,)
if not update_database:
self.commit()
return
newcubes_schema = self.config.load_schema(construction_mode='non-strict')
+ # XXX we have to replace fs_schema, used in cmd_add_relation_type
+ # etc. and fsschema of migration script contexts
+ self.fs_schema = self._create_context()['fsschema'] = newcubes_schema
new = set()
# execute pre-create files
for pack in reversed(newcubes):
@@ -881,7 +893,8 @@
if isinstance(ertype, (tuple, list)):
assert len(ertype) == 3, 'not a relation definition'
assert syncprops, 'can\'t update permission for a relation definition'
- self._synchronize_rdef_schema(*ertype)
+ self._synchronize_rdef_schema(ertype[0], ertype[1], ertype[2],
+ syncperms=syncperms)
elif syncprops:
erschema = self.repo.schema[ertype]
if isinstance(erschema, CubicWebRelationSchema):
@@ -890,13 +903,14 @@
else:
self._synchronize_eschema(erschema, syncperms=syncperms)
else:
- self._synchronize_permissions(ertype)
+ erschema = self.repo.schema[ertype]
+ self._synchronize_permissions(self.fs_schema[ertype], erschema.eid)
else:
for etype in self.repo.schema.entities():
if syncprops:
self._synchronize_eschema(etype, syncperms=syncperms)
else:
- self._synchronize_permissions(etype)
+ self._synchronize_permissions(self.fs_schema[etype], erschema.eid)
if commit:
self.commit()
--- a/server/msplanner.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/msplanner.py Fri Nov 20 19:35:54 2009 +0100
@@ -1470,11 +1470,19 @@
def visit_constant(self, node, newroot, terms):
return copy_node(newroot, node), node
+ def visit_comparison(self, node, newroot, terms):
+ subparts, node = self._visit_children(node, newroot, terms)
+ copy = copy_node(newroot, node, subparts)
+ # ignore comparison operator when fetching non final query
+ if not self.final and isinstance(node.children[0], VariableRef):
+ copy.operator = '='
+ return copy, node
+
def visit_default(self, node, newroot, terms):
subparts, node = self._visit_children(node, newroot, terms)
return copy_node(newroot, node, subparts), node
- visit_comparison = visit_mathexpression = visit_constant = visit_function = visit_default
+ visit_mathexpression = visit_constant = visit_function = visit_default
visit_sort = visit_sortterm = visit_default
def _visit_children(self, node, newroot, terms):
--- a/server/querier.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/querier.py Fri Nov 20 19:35:54 2009 +0100
@@ -71,14 +71,23 @@
# XXX has_text may have specific perm ?
if rel.r_type in READ_ONLY_RTYPES:
continue
- if not schema.rschema(rel.r_type).has_access(user, 'read'):
+ rschema = schema.rschema(rel.r_type)
+ if rschema.final:
+ eschema = schema.eschema(solution[rel.children[0].name])
+ rdef = eschema.rdef(rschema)
+ else:
+ rdef = rschema.rdef(solution[rel.children[0].name],
+ solution[rel.children[1].children[0].name])
+ if not user.matching_groups(rdef.get_groups('read')):
raise Unauthorized('read', rel.r_type)
localchecks = {}
# iterate on defined_vars and not on solutions to ignore column aliases
for varname in rqlst.defined_vars:
etype = solution[varname]
eschema = schema.eschema(etype)
- if not eschema.has_access(user, 'read'):
+ if eschema.final:
+ continue
+ if not user.matching_groups(eschema.get_groups('read')):
erqlexprs = eschema.get_rqlexprs('read')
if not erqlexprs:
ex = Unauthorized('read', etype)
--- a/server/repository.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/repository.py Fri Nov 20 19:35:54 2009 +0100
@@ -1049,7 +1049,7 @@
continue
rschema = eschema.subjrels[attr]
if rschema.final:
- if eschema.rproperty(attr, 'fulltextindexed'):
+ if getattr(eschema.rdef(attr), 'fulltextindexed', False):
need_fti_update = True
only_inline_rels = False
else:
--- a/server/rqlannotation.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/rqlannotation.py Fri Nov 20 19:35:54 2009 +0100
@@ -10,6 +10,7 @@
from logilab.common.compat import any
+from rql import BadRQLQuery
from rql.nodes import Relation, VariableRef, Constant, Variable, Or
from rql.utils import common_parent
@@ -177,10 +178,20 @@
"""given a list of rqlst relations, select one which will be used as main
relation for the rhs variable
"""
- for rel in relations:
+ principal = None
+ # sort for test predictability
+ for rel in sorted(relations, key=lambda x: (x.children[0].name, x.r_type)):
+ # only equality relation with a variable as rhs may be principal
+ if rel.operator() not in ('=', 'IS') \
+ or not isinstance(rel.children[1].children[0], VariableRef):
+ continue
if rel.sqlscope is rel.stmt:
return rel
principal = rel
+ if principal is None:
+ print iter(relations).next().root
+ raise BadRQLQuery('unable to find principal in %s' % ', '.join(
+ r.as_string() for r in relations))
return principal
--- a/server/schemaserial.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/schemaserial.py Fri Nov 20 19:35:54 2009 +0100
@@ -7,6 +7,7 @@
"""
__docformat__ = "restructuredtext en"
+import os
import sys
import os
from itertools import chain
@@ -256,7 +257,7 @@
actperms.append(erschema.rql_expression(*something))
else: # group name
actperms.append(something)
- erschema.set_permissions(action, actperms)
+ erschema.set_action_permissions(action, actperms)
def deserialize_rdef_constraints(session):
@@ -516,25 +517,51 @@
yield erperms2rql(schema[rtype], groupmapping)
def erperms2rql(erschema, groupmapping):
+ if hasattr(erschema, 'iter_rdefs'):
+ # relation schema
+ if erschema.final:
+ etype = 'CWAttribute'
+ else:
+ etype = 'CWRelation'
+ for subject, object in erschema.iter_rdefs():
+ permissions = erschema.rproperty(subject, object, 'permissions')
+ for rql, args in _erperms2rql(erschema.rproperties(subject, object),
+ groupmapping):
+ args['st'] = str(subject)
+ args['rt'] = str(erschema)
+ args['ot'] = str(object)
+ yield rql + 'X is %s, X from_entity ST, X to_entity OT, '\
+ 'X relation_type RT, RT name %%(rt)s, ST name %%(st)s, '\
+ 'OT name %%(ot)s' % etype, args
+ else:
+ # entity schema
+ for rql, args in _erperms2rql(erschema, groupmapping):
+ args['name'] = str(eschema)
+ yield rql + 'X is CWEType, X name %(name)s', args
+
+def _erperms2rql(erschema, groupmapping):
"""return rql insert statements to enter the entity or relation
schema's permissions in the database as
[read|add|delete|update]_permission relations between CWEType/CWRType
and CWGroup entities
"""
- etype = isinstance(erschema, schemamod.EntitySchema) and 'CWEType' or 'CWRType'
for action in erschema.ACTIONS:
- for group in sorted(erschema.get_groups(action)):
- try:
- yield ('SET X %s_permission Y WHERE X is %s, X name "%s", Y eid %s'
- % (action, etype, erschema, groupmapping[group]), None)
- except KeyError:
- continue
- for rqlexpr in sorted(erschema.get_rqlexprs(action)):
- yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
- 'E mainvars %%(v)s, X %s_permission E '
- 'WHERE X is %s, X name "%s"' % (action, etype, erschema),
- {'e': unicode(rqlexpr.expression), 'v': unicode(rqlexpr.mainvars),
- 't': unicode(rqlexpr.__class__.__name__)})
+ for group_or_rqlexpr in erschema.action_permissions(action):
+ if isinstance(group_or_rqlexpr, basestring):
+ # group
+ try:
+ yield ('SET X %s_permission Y WHERE Y eid %%(g)s' % action,
+ {'g': groupmapping[group_or_rqlexpr]})
+ except KeyError:
+ continue
+ else:
+ # rqlexpr
+ rqlexpr = group_or_rqlexpr
+ yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
+ 'E mainvars %%(v)s, X %s_permission E WHERE ' % action,
+ {'e': unicode(rqlexpr.expression),
+ 'v': unicode(rqlexpr.mainvars),
+ 't': unicode(rqlexpr.__class__.__name__)})
def updateeschema2rql(eschema):
--- a/server/serverconfig.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/serverconfig.py Fri Nov 20 19:35:54 2009 +0100
@@ -10,7 +10,7 @@
import os
from os.path import join, exists
-from logilab.common.configuration import REQUIRED, Method, Configuration, \
+from logilab.common.configuration import Method, Configuration, \
ini_format_section
from logilab.common.decorators import wproperty, cached, clear_cache
@@ -22,7 +22,7 @@
USER_OPTIONS = (
('login', {'type' : 'string',
- 'default': REQUIRED,
+ 'default': 'admin',
'help': "cubicweb manager account's login "
'(this user will be created)',
'inputlevel': 0,
--- a/server/serverctl.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/serverctl.py Fri Nov 20 19:35:54 2009 +0100
@@ -133,6 +133,9 @@
config.input_config('pyro', inputlevel)
print '\n'+underline_title('Configuring the sources')
sourcesfile = config.sources_file()
+ # XXX hack to make Method('default_instance_id') usable in db option
+ # defs (in native.py)
+ Configuration.default_instance_id = staticmethod(lambda: config.appid)
sconfig = Configuration(options=SOURCE_TYPES['native'].options)
sconfig.adapter = 'native'
sconfig.input_config(inputlevel=inputlevel)
@@ -465,6 +468,12 @@
from cubicweb.server.server import RepositoryServer
appid = pop_arg(args, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
+ if sys.platform == 'win32':
+ if not self.config.debug:
+ from logging import getLogger
+ logger = getLogger('cubicweb.ctl')
+ logger.info('Forcing debug mode on win32 platform')
+ self.config.debug = True
debug = self.config.debug
# create the server
server = RepositoryServer(config, debug)
--- a/server/sources/native.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/sources/native.py Fri Nov 20 19:35:54 2009 +0100
@@ -18,12 +18,14 @@
from base64 import b64decode, b64encode
from logilab.common.cache import Cache
-from logilab.common.configuration import REQUIRED
+from logilab.common.configuration import Method
from logilab.common.adbh import get_adv_func_helper
+from logilab.common.shellutils import getlogin
from indexer import get_indexer
from cubicweb import UnknownEid, AuthenticationError, Binary, server
+from cubicweb.cwconfig import CubicWebNoAppConfiguration
from cubicweb.server.utils import crypt_password
from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
from cubicweb.server.rqlannotation import set_qdata
@@ -115,13 +117,13 @@
}),
('db-name',
{'type' : 'string',
- 'default': REQUIRED,
+ 'default': Method('default_instance_id'),
'help': 'database name',
'group': 'native-source', 'inputlevel': 0,
}),
('db-user',
{'type' : 'string',
- 'default': 'cubicweb',
+ 'default': CubicWebNoAppConfiguration.mode == 'user' and getlogin() or 'cubicweb',
'help': 'database user',
'group': 'native-source', 'inputlevel': 0,
}),
--- a/server/sources/rql2sql.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/sources/rql2sql.py Fri Nov 20 19:35:54 2009 +0100
@@ -131,37 +131,56 @@
"""cleanup solutions: remove solutions where invariant variables are taking
different types
"""
- newsolutions = _new_solutions(rqlst, solutions)
+ newsols = _new_solutions(rqlst, solutions)
existssols = {}
unstable = set()
+ invariants = {}
for vname, var in rqlst.defined_vars.iteritems():
- vtype = newsolutions[0][vname]
+ vtype = newsols[0][vname]
if var._q_invariant or vname in varmap:
- for i in xrange(len(newsolutions)-1, 0, -1):
- if vtype != newsolutions[i][vname]:
- newsolutions.pop(i)
- elif not var.scope is rqlst:
+ # remove invariant variable from solutions to remove duplicates
+ # later, then reinserting a type for the variable even later
+ for sol in newsols:
+ invariants.setdefault(id(sol), {})[vname] = sol.pop(vname)
+ elif var.scope is not rqlst:
# move appart variables which are in a EXISTS scope and are variating
try:
thisexistssols, thisexistsvars = existssols[var.scope]
except KeyError:
- thisexistssols = [newsolutions[0]]
+ thisexistssols = [newsols[0]]
thisexistsvars = set()
existssols[var.scope] = thisexistssols, thisexistsvars
- for i in xrange(len(newsolutions)-1, 0, -1):
- if vtype != newsolutions[i][vname]:
- thisexistssols.append(newsolutions.pop(i))
+ for i in xrange(len(newsols)-1, 0, -1):
+ if vtype != newsols[i][vname]:
+ thisexistssols.append(newsols.pop(i))
thisexistsvars.add(vname)
else:
# remember unstable variables
- for i in xrange(1, len(newsolutions)):
- if vtype != newsolutions[i][vname]:
+ for i in xrange(1, len(newsols)):
+ if vtype != newsols[i][vname]:
unstable.add(vname)
- if len(newsolutions) > 1:
- if rewrite_unstable_outer_join(rqlst, newsolutions, unstable, schema):
+ if invariants:
+ # filter out duplicates
+ newsols_ = []
+ for sol in newsols:
+ if not sol in newsols_:
+ newsols_.append(sol)
+ newsols = newsols_
+ # reinsert solutions for invariants
+ for sol in newsols:
+ for invvar, vartype in invariants[id(sol)].iteritems():
+ sol[invvar] = vartype
+ for sol in existssols:
+ try:
+ for invvar, vartype in invariants[id(sol)].iteritems():
+ sol[invvar] = vartype
+ except KeyError:
+ continue
+ if len(newsols) > 1:
+ if rewrite_unstable_outer_join(rqlst, newsols, unstable, schema):
# remove variables extracted to subqueries from solutions
- newsolutions = _new_solutions(rqlst, newsolutions)
- return newsolutions, existssols, unstable
+ newsols = _new_solutions(rqlst, newsols)
+ return newsols, existssols, unstable
def relation_info(relation):
lhs, rhs = relation.get_variable_parts()
@@ -252,10 +271,10 @@
self.actual_tables[-1].append(tsql)
self.outer_tables = {}
self.duplicate_switches = []
- self.attr_vars = {}
self.aliases = {}
self.restrictions = []
self._restr_stack = []
+ self.ignore_varmap = False
def add_restriction(self, restr):
if restr:
@@ -848,23 +867,23 @@
nothing to do here.
"""
contextrels = {}
- attrvars = self._state.attr_vars
for var in rhs_vars:
- try:
- contextrels[var.name] = attrvars[var.name]
- except KeyError:
- attrvars[var.name] = relation
if var.name in self._varmap:
# ensure table is added
self._var_info(var.variable)
+ principal = var.variable.stinfo.get('principal')
+ if principal is not None and principal is not relation:
+ contextrels[var.name] = relation
if not contextrels:
- relation.children[1].accept(self, contextrels)
return ''
- # at least one variable is already in attr_vars, this means we have to
- # generate unification expression
+ # we have to generate unification expression
lhssql = self._inlined_var_sql(relation.children[0].variable,
relation.r_type)
- return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels))
+ try:
+ self._state.ignore_varmap = True
+ return '%s%s' % (lhssql, relation.children[1].accept(self))
+ finally:
+ self._state.ignore_varmap = False
def _visit_attribute_relation(self, rel):
"""generate SQL for an attribute relation"""
@@ -932,8 +951,8 @@
return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args),
jointo, not_) + restriction
- def visit_comparison(self, cmp, contextrels=None):
- """generate SQL for a comparaison"""
+ def visit_comparison(self, cmp):
+ """generate SQL for a comparison"""
if len(cmp.children) == 2:
# XXX occurs ?
lhs, rhs = cmp.children
@@ -950,16 +969,15 @@
and rhs.eval(self._args) is None):
if lhs is None:
return ' IS NULL'
- return '%s IS NULL' % lhs.accept(self, contextrels)
+ return '%s IS NULL' % lhs.accept(self)
elif isinstance(rhs, Function) and rhs.name == 'IN':
assert operator == '='
operator = ' '
if lhs is None:
- return '%s%s'% (operator, rhs.accept(self, contextrels))
- return '%s%s%s'% (lhs.accept(self, contextrels), operator,
- rhs.accept(self, contextrels))
+ return '%s%s'% (operator, rhs.accept(self))
+ return '%s%s%s'% (lhs.accept(self), operator, rhs.accept(self))
- def visit_mathexpression(self, mexpr, contextrels=None):
+ def visit_mathexpression(self, mexpr):
"""generate SQL for a mathematic expression"""
lhs, rhs = mexpr.get_parts()
# check for string concatenation
@@ -969,17 +987,16 @@
operator = '||'
except CoercionError:
pass
- return '(%s %s %s)'% (lhs.accept(self, contextrels), operator,
- rhs.accept(self, contextrels))
+ return '(%s %s %s)'% (lhs.accept(self), operator, rhs.accept(self))
- def visit_function(self, func, contextrels=None):
+ def visit_function(self, func):
"""generate SQL name for a function"""
# function_description will check function is supported by the backend
sqlname = self.dbms_helper.func_sqlname(func.name)
- return '%s(%s)' % (sqlname, ', '.join(c.accept(self, contextrels)
+ return '%s(%s)' % (sqlname, ', '.join(c.accept(self)
for c in func.children))
- def visit_constant(self, constant, contextrels=None):
+ def visit_constant(self, constant):
"""generate SQL name for a constant"""
value = constant.value
if constant.type is None:
@@ -1004,12 +1021,12 @@
self._query_attrs[_id] = value
return '%%(%s)s' % _id
- def visit_variableref(self, variableref, contextrels=None):
+ def visit_variableref(self, variableref):
"""get the sql name for a variable reference"""
# use accept, .variable may be a variable or a columnalias
- return variableref.variable.accept(self, contextrels)
+ return variableref.variable.accept(self)
- def visit_columnalias(self, colalias, contextrels=None):
+ def visit_columnalias(self, colalias):
"""get the sql name for a subquery column alias"""
if colalias.name in self._varmap:
sql = self._varmap[colalias.name]
@@ -1020,20 +1037,21 @@
return sql
return colalias._q_sql
- def visit_variable(self, variable, contextrels=None):
+ def visit_variable(self, variable):
"""get the table name and sql string for a variable"""
- if contextrels is None and variable.name in self._state.done:
+ #if contextrels is None and variable.name in self._state.done:
+ if variable.name in self._state.done:
if self._in_wrapping_query:
return 'T1.%s' % self._state.aliases[variable.name]
return variable._q_sql
self._state.done.add(variable.name)
vtablename = None
- if contextrels is None and variable.name in self._varmap:
+ if not self._state.ignore_varmap and variable.name in self._varmap:
sql, vtablename = self._var_info(variable)
elif variable.stinfo['attrvar']:
# attribute variable (systematically used in rhs of final
# relation(s)), get table name and sql from any rhs relation
- sql = self._linked_var_sql(variable, contextrels)
+ sql = self._linked_var_sql(variable)
elif variable._q_invariant:
# since variable is invariant, we know we won't found final relation
principal = variable.stinfo['principal']
@@ -1056,7 +1074,7 @@
self.dbms_helper.fti_uid_attr)
elif principal in variable.stinfo['rhsrelations']:
if self.schema.rschema(principal.r_type).inlined:
- sql = self._linked_var_sql(variable, contextrels)
+ sql = self._linked_var_sql(variable)
else:
sql = '%s.eid_to' % self._relation_table(principal)
else:
@@ -1076,8 +1094,14 @@
# generate extra join
try:
if not var.stinfo['principal'] is relation:
- # need a predicable result for tests
- return '%s=%s' % tuple(sorted((sql, var.accept(self))))
+ op = relation.operator()
+ if op == '=':
+ # need a predicable result for tests
+ args = sorted( (sql, var.accept(self)) )
+ args.insert(1, op)
+ else:
+ args = (sql, op, var.accept(self))
+ return '%s%s%s' % tuple(args)
except KeyError:
# no principal defined, relation is necessarily the principal and
# so nothing to return here
@@ -1123,14 +1147,13 @@
#self._state.done.add(var.name)
return sql
- def _linked_var_sql(self, variable, contextrels=None):
- if contextrels is None:
+ def _linked_var_sql(self, variable):
+ if not self._state.ignore_varmap:
try:
return self._varmap[variable.name]
except KeyError:
pass
- rel = (contextrels and contextrels.get(variable.name) or
- variable.stinfo.get('principal') or
+ rel = (variable.stinfo.get('principal') or
iter(variable.stinfo['rhsrelations']).next())
linkedvar = rel.children[0].variable
if rel.r_type == 'eid':
--- a/server/test/data/migratedapp/schema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/test/data/migratedapp/schema.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,7 +13,7 @@
ERQLExpression, RRQLExpression)
class Affaire(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
'update': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
@@ -27,7 +27,7 @@
concerne = SubjectRelation('Societe')
class concerne(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('O owned_by U')),
@@ -42,7 +42,7 @@
class Note(Para):
__specializes_schema__ = True
- permissions = {'read': ('managers', 'users', 'guests',),
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
'update': ('managers', 'owners',),
'delete': ('managers', ),
'add': ('managers',
@@ -63,7 +63,7 @@
summary = String(maxsize=512)
class ecrit_par(RelationType):
- permissions = {'read': ('managers', 'users', 'guests',),
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
'delete': ('managers', ),
'add': ('managers',
RRQLExpression('O require_permission P, P name "add_note", '
@@ -105,7 +105,7 @@
connait = SubjectRelation('Personne', symetric=True)
class Societe(WorkflowableEntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'update': ('managers', 'owners'),
'delete': ('managers', 'owners'),
--- a/server/test/data/schema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/test/data/schema.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,7 +13,7 @@
ERQLExpression, RRQLExpression)
class Affaire(WorkflowableEntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',
ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
@@ -39,7 +39,7 @@
class Societe(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
@@ -121,28 +121,28 @@
symetric = True
class concerne(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('O owned_by U')),
}
class travaille(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('O owned_by U')),
}
class para(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', ERQLExpression('X in_state S, S name "todo"')),
'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
}
class test(RelationType):
- permissions = {'read': ('managers', 'users', 'guests'),
+ __permissions__ = {'read': ('managers', 'users', 'guests'),
'delete': ('managers',),
'add': ('managers',)}
--- a/server/test/unittest_ldapuser.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/test/unittest_ldapuser.py Fri Nov 20 19:35:54 2009 +0100
@@ -6,6 +6,8 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+import socket
+
from logilab.common.testlib import TestCase, unittest_main, mock_object
from cubicweb.devtools import TestServerConfiguration
from cubicweb.devtools.testlib import CubicWebTC
@@ -13,6 +15,14 @@
from cubicweb.server.sources.ldapuser import *
+if socket.gethostbyname('ldap').startswith('172'):
+ SYT = 'syt'
+ ADIM = 'adim'
+else:
+ SYT = 'sthenault'
+ ADIM = 'adimascio'
+
+
def nopwd_authenticate(self, session, login, upassword):
"""used to monkey patch the source to get successful authentication without
upassword checking
@@ -68,15 +78,15 @@
def test_base(self):
# check a known one
- e = self.sexecute('CWUser X WHERE X login "syt"').get_entity(0, 0)
- self.assertEquals(e.login, 'syt')
+ e = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
+ self.assertEquals(e.login, SYT)
e.complete()
self.assertEquals(e.creation_date, None)
self.assertEquals(e.modification_date, None)
self.assertEquals(e.firstname, None)
self.assertEquals(e.surname, None)
self.assertEquals(e.in_group[0].name, 'users')
- self.assertEquals(e.owned_by[0].login, 'syt')
+ self.assertEquals(e.owned_by[0].login, SYT)
self.assertEquals(e.created_by, ())
self.assertEquals(e.primary_email[0].address, 'Sylvain Thenault')
# email content should be indexed on the user
@@ -84,31 +94,33 @@
self.assertEquals(rset.rows, [[e.eid]])
def test_not(self):
- eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
rset = self.sexecute('CWUser X WHERE NOT X eid %s' % eid)
self.assert_(rset)
self.assert_(not eid in (r[0] for r in rset))
def test_multiple(self):
- seid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
- aeid = self.sexecute('CWUser X WHERE X login "adim"')[0][0]
- rset = self.sexecute('CWUser X, Y WHERE X login "syt", Y login "adim"')
+ seid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ aeid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': ADIM})[0][0]
+ rset = self.sexecute('CWUser X, Y WHERE X login %(syt)s, Y login %(adim)s',
+ {'syt': SYT, 'adim': ADIM})
self.assertEquals(rset.rows, [[seid, aeid]])
- rset = self.sexecute('Any X,Y,L WHERE X login L, X login "syt", Y login "adim"')
- self.assertEquals(rset.rows, [[seid, aeid, 'syt']])
+ rset = self.sexecute('Any X,Y,L WHERE X login L, X login %(syt)s, Y login %(adim)s',
+ {'syt': SYT, 'adim': ADIM})
+ self.assertEquals(rset.rows, [[seid, aeid, SYT]])
def test_in(self):
- seid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
- aeid = self.sexecute('CWUser X WHERE X login "adim"')[0][0]
- rset = self.sexecute('Any X,L ORDERBY L WHERE X login IN("syt", "adim"), X login L')
- self.assertEquals(rset.rows, [[aeid, 'adim'], [seid, 'syt']])
+ seid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ aeid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': ADIM})[0][0]
+ rset = self.sexecute('Any X,L ORDERBY L WHERE X login IN("%s", "%s"), X login L' % (SYT, ADIM))
+ self.assertEquals(rset.rows, [[aeid, ADIM], [seid, SYT]])
def test_relations(self):
- eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
rset = self.sexecute('Any X,E WHERE X is CWUser, X login L, X primary_email E')
self.assert_(eid in (r[0] for r in rset))
rset = self.sexecute('Any X,L,E WHERE X is CWUser, X login L, X primary_email E')
- self.assert_('syt' in (r[1] for r in rset))
+ self.assert_(SYT in (r[1] for r in rset))
def test_count(self):
nbusers = self.sexecute('Any COUNT(X) WHERE X is CWUser')[0][0]
@@ -117,15 +129,15 @@
self.assert_(nbusers < 30, nbusers)
def test_upper(self):
- eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
rset = self.sexecute('Any UPPER(L) WHERE X eid %s, X login L' % eid)
- self.assertEquals(rset[0][0], 'SYT')
+ self.assertEquals(rset[0][0], SYT.upper())
def test_unknown_attr(self):
- eid = self.sexecute('CWUser X WHERE X login "syt"')[0][0]
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
rset = self.sexecute('Any L,C,M WHERE X eid %s, X login L, '
'X creation_date C, X modification_date M' % eid)
- self.assertEquals(rset[0][0], 'syt')
+ self.assertEquals(rset[0][0], SYT)
self.assertEquals(rset[0][1], None)
self.assertEquals(rset[0][2], None)
@@ -138,54 +150,55 @@
self.assertEquals(logins, sorted(logins))
def test_or(self):
- rset = self.sexecute('DISTINCT Any X WHERE X login "syt" OR (X in_group G, G name "managers")')
+ rset = self.sexecute('DISTINCT Any X WHERE X login %(login)s OR (X in_group G, G name "managers")',
+ {'login': SYT})
self.assertEquals(len(rset), 2, rset.rows) # syt + admin
def test_nonregr_set_owned_by(self):
# test that when a user coming from ldap is triggering a transition
# the related TrInfo has correct owner information
- self.sexecute('SET X in_group G WHERE X login "syt", G name "managers"')
+ self.sexecute('SET X in_group G WHERE X login %(syt)s, G name "managers"', {'syt': SYT})
self.commit()
- syt = self.sexecute('CWUser X WHERE X login "syt"').get_entity(0, 0)
+ syt = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
self.assertEquals([g.name for g in syt.in_group], ['managers', 'users'])
self.patch_authenticate()
- cnx = self.login('syt', 'dummypassword')
+ cnx = self.login(SYT, 'dummypassword')
cu = cnx.cursor()
- alf = cu.execute('Any X WHERE X login "alf"').get_entity(0, 0)
- alf.fire_transition('deactivate')
+ adim = cu.execute('CWUser X WHERE X login %(login)s', {'login': ADIM}).get_entity(0, 0)
+ adim.fire_transition('deactivate')
try:
cnx.commit()
- alf = self.sexecute('CWUser X WHERE X login "alf"').get_entity(0, 0)
- self.assertEquals(alf.in_state[0].name, 'deactivated')
- trinfo = alf.latest_trinfo()
- self.assertEquals(trinfo.owned_by[0].login, 'syt')
+ adim.clear_all_caches()
+ self.assertEquals(adim.in_state[0].name, 'deactivated')
+ trinfo = adim.latest_trinfo()
+ self.assertEquals(trinfo.owned_by[0].login, SYT)
# select from_state to skip the user's creation TrInfo
rset = self.sexecute('Any U ORDERBY D DESC WHERE WF wf_info_for X,'
'WF creation_date D, WF from_state FS,'
'WF owned_by U?, X eid %(x)s',
- {'x': alf.eid}, 'x')
+ {'x': adim.eid}, 'x')
self.assertEquals(rset.rows, [[syt.eid]])
finally:
# restore db state
self.restore_connection()
- alf = self.sexecute('Any X WHERE X login "alf"').get_entity(0, 0)
- alf.fire_transition('activate')
- self.sexecute('DELETE X in_group G WHERE X login "syt", G name "managers"')
+ adim = self.sexecute('CWUser X WHERE X login %(login)s', {'login': ADIM}).get_entity(0, 0)
+ adim.fire_transition('activate')
+ self.execute('DELETE X in_group G WHERE X login %(syt)s, G name "managers"', {'syt': SYT})
def test_same_column_names(self):
self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')
def test_multiple_entities_from_different_sources(self):
self.create_user('cochon', req=self.session)
- self.failUnless(self.sexecute('Any X,Y WHERE X login "syt", Y login "cochon"'))
+ self.failUnless(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}))
def test_exists1(self):
self.add_entity('CWGroup', name=u'bougloup1', req=self.session)
self.add_entity('CWGroup', name=u'bougloup2', req=self.session)
self.sexecute('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"')
- self.sexecute('SET U in_group G WHERE G name = "bougloup1", U login "syt"')
+ self.sexecute('SET U in_group G WHERE G name = "bougloup1", U login %(syt)s', {'syt': SYT})
rset = self.sexecute('Any L,SN ORDERBY L WHERE X in_state S, S name SN, X login L, EXISTS(X in_group G, G name ~= "bougloup%")')
- self.assertEquals(rset.rows, [['admin', 'activated'], ['syt', 'activated']])
+ self.assertEquals(rset.rows, [['admin', 'activated'], [SYT, 'activated']])
def test_exists2(self):
self.create_user('comme', req=self.session)
@@ -199,10 +212,10 @@
self.create_user('cochon', req=self.session)
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"'))
- self.sexecute('SET X copain Y WHERE X login "syt", Y login "cochon"')
- self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "syt", Y login "cochon"'))
+ self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
+ self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login %(syt)s, Y login "cochon"', {'syt': SYT}))
rset = self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon"))')
- self.assertEquals(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', 'syt']])
+ self.assertEquals(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', SYT]])
def test_exists4(self):
self.create_user('comme', req=self.session)
@@ -211,7 +224,7 @@
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
- self.sexecute('SET X copain Y WHERE X login "syt", Y login "billy"')
+ self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "billy"', {'syt': SYT})
# search for group name, login where
# CWUser copain with "comme" or "cochon" AND same login as the copain
# OR
@@ -223,7 +236,7 @@
'EXISTS(X in_state S, S name "activated", NOT X copain T2, T2 login "billy")')
all = self.sexecute('Any GN, L WHERE X in_group G, X login L, G name GN')
all.rows.remove(['users', 'comme'])
- all.rows.remove(['users', 'syt'])
+ all.rows.remove(['users', SYT])
self.assertEquals(sorted(rset.rows), sorted(all.rows))
def test_exists5(self):
@@ -233,17 +246,17 @@
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
- self.sexecute('SET X copain Y WHERE X login "syt", Y login "cochon"')
+ self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
rset= self.sexecute('Any L WHERE X login L, '
'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
'NOT EXISTS(X copain T2, T2 login "billy")')
- self.assertEquals(sorted(rset.rows), [['cochon'], ['syt']])
+ self.assertEquals(sorted(rset.rows), [['cochon'], [SYT]])
rset= self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, '
'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
'NOT EXISTS(X copain T2, T2 login "billy")')
self.assertEquals(sorted(rset.rows), [['guests', 'cochon'],
['users', 'cochon'],
- ['users', 'syt']])
+ ['users', SYT]])
def test_cd_restriction(self):
rset = self.sexecute('CWUser X WHERE X creation_date > "2009-02-01"')
@@ -265,21 +278,21 @@
def test_security1(self):
cu = self._init_security_test()
- rset = cu.execute('Any X WHERE X login "syt"')
+ rset = cu.execute('CWUser X WHERE X login %(login)s', {'login': SYT})
self.assertEquals(rset.rows, [])
rset = cu.execute('Any X WHERE X login "iaminguestsgrouponly"')
self.assertEquals(len(rset.rows), 1)
def test_security2(self):
cu = self._init_security_test()
- rset = cu.execute('Any X WHERE X has_text "syt"')
+ rset = cu.execute('Any X WHERE X has_text %(syt)s', {'syt': SYT})
self.assertEquals(rset.rows, [])
rset = cu.execute('Any X WHERE X has_text "iaminguestsgrouponly"')
self.assertEquals(len(rset.rows), 1)
def test_security3(self):
cu = self._init_security_test()
- rset = cu.execute('Any F WHERE X has_text "syt", X firstname F')
+ rset = cu.execute('Any F WHERE X has_text %(syt)s, X firstname F', {'syt': SYT})
self.assertEquals(rset.rows, [])
rset = cu.execute('Any F WHERE X has_text "iaminguestsgrouponly", X firstname F')
self.assertEquals(rset.rows, [[None]])
--- a/server/test/unittest_msplanner.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/test/unittest_msplanner.py Fri Nov 20 19:35:54 2009 +0100
@@ -706,7 +706,7 @@
self._test('Any V, MAX(VR) WHERE V is Card, V creation_date VR, '
'(V creation_date TODAY OR (V creation_date < TODAY AND NOT EXISTS('
'X is Card, X creation_date < TODAY, X creation_date >= VR)))',
- [('FetchStep', [('Any VR WHERE X creation_date < TODAY, X creation_date >= VR, X is Card',
+ [('FetchStep', [('Any VR WHERE X creation_date < TODAY, X creation_date VR, X is Card',
[{'X': 'Card', 'VR': 'Datetime'}])],
[self.cards, self.system], None,
{'VR': 'table0.C0', 'X.creation_date': 'table0.C0'}, []),
@@ -1349,7 +1349,7 @@
def test_attr_unification_neq_1(self):
self._test('Any X,Y WHERE X is Bookmark, Y is Card, X creation_date D, Y creation_date > D',
[('FetchStep',
- [('Any Y,D WHERE Y creation_date > D, Y is Card',
+ [('Any Y,D WHERE Y creation_date D, Y is Card',
[{'D': 'Datetime', 'Y': 'Card'}])],
[self.cards,self.system], None,
{'D': 'table0.C1', 'Y': 'table0.C0', 'Y.creation_date': 'table0.C1'}, []),
--- a/server/test/unittest_rql2sql.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/test/unittest_rql2sql.py Fri Nov 20 19:35:54 2009 +0100
@@ -10,13 +10,13 @@
import sys
-from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.testlib import TestCase, unittest_main, mock_object
from rql import BadRQLQuery
from indexer import get_indexer
#from cubicweb.server.sources.native import remove_unused_solutions
-from cubicweb.server.sources.rql2sql import SQLGenerator
+from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions
from rql.utils import register_function, FunctionDescr
# add a dumb registered procedure
@@ -165,6 +165,9 @@
'''SELECT _X.cw_eid
FROM cw_Personne AS _X
WHERE _X.cw_prenom=lulu AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS _G WHERE rel_owned_by0.eid_from=_X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=_G.cw_eid AND ((_G.cw_name=lulufanclub) OR (_G.cw_name=managers)))'''),
+
+
+
]
ADVANCED= [
@@ -868,10 +871,21 @@
]
VIRTUAL_VARS = [
- ("Personne P WHERE P travaille S, S tel T, S fax T, S is Societe;",
+
+ ('Any X WHERE X is CWUser, X creation_date > D1, Y creation_date D1, Y login "SWEB09"',
+ '''SELECT _X.cw_eid
+FROM cw_CWUser AS _X, cw_CWUser AS _Y
+WHERE _X.cw_creation_date>_Y.cw_creation_date AND _Y.cw_login=SWEB09'''),
+
+ ('Any X WHERE X is CWUser, Y creation_date D1, Y login "SWEB09", X creation_date > D1',
+ '''SELECT _X.cw_eid
+FROM cw_CWUser AS _X, cw_CWUser AS _Y
+WHERE _Y.cw_login=SWEB09 AND _X.cw_creation_date>_Y.cw_creation_date'''),
+
+ ('Personne P WHERE P travaille S, S tel T, S fax T, S is Societe',
'''SELECT rel_travaille0.eid_from
FROM cw_Societe AS _S, travaille_relation AS rel_travaille0
-WHERE rel_travaille0.eid_to=_S.cw_eid AND _S.cw_fax=_S.cw_tel'''),
+WHERE rel_travaille0.eid_to=_S.cw_eid AND _S.cw_tel=_S.cw_fax'''),
("Personne P where X eid 0, X creation_date D, P datenaiss < D, X is Affaire",
'''SELECT _P.cw_eid
@@ -1590,7 +1604,26 @@
WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS _P WHERE rel_owned_by0.eid_from=_P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS _P WHERE rel_owned_by1.eid_from=_P.cw_eid AND rel_owned_by1.eid_to=1)''')
+class removeUnsusedSolutionsTC(TestCase):
+ def test_invariant_not_varying(self):
+ rqlst = mock_object(defined_vars={})
+ rqlst.defined_vars['A'] = mock_object(scope=rqlst, stinfo={'optrelations':False}, _q_invariant=True)
+ rqlst.defined_vars['B'] = mock_object(scope=rqlst, stinfo={'optrelations':False}, _q_invariant=False)
+ self.assertEquals(remove_unused_solutions(rqlst, [{'A': 'RugbyGroup', 'B': 'RugbyTeam'},
+ {'A': 'FootGroup', 'B': 'FootTeam'}], {}, None),
+ ([{'A': 'RugbyGroup', 'B': 'RugbyTeam'},
+ {'A': 'FootGroup', 'B': 'FootTeam'}],
+ {}, set('B'))
+ )
+ def test_invariant_varying(self):
+ rqlst = mock_object(defined_vars={})
+ rqlst.defined_vars['A'] = mock_object(scope=rqlst, stinfo={'optrelations':False}, _q_invariant=True)
+ rqlst.defined_vars['B'] = mock_object(scope=rqlst, stinfo={'optrelations':False}, _q_invariant=False)
+ self.assertEquals(remove_unused_solutions(rqlst, [{'A': 'RugbyGroup', 'B': 'RugbyTeam'},
+ {'A': 'FootGroup', 'B': 'RugbyTeam'}], {}, None),
+ ([{'A': 'RugbyGroup', 'B': 'RugbyTeam'}], {}, set())
+ )
if __name__ == '__main__':
unittest_main()
--- a/server/test/unittest_security.py Sun Nov 08 21:53:18 2009 +0100
+++ b/server/test/unittest_security.py Fri Nov 20 19:35:54 2009 +0100
@@ -14,13 +14,13 @@
def setUp(self):
CubicWebTC.setUp(self)
self.create_user('iaminusersgrouponly')
- self.readoriggroups = self.schema['Personne'].get_groups('read')
- self.addoriggroups = self.schema['Personne'].get_groups('add')
+ self.readoriggroups = self.schema['Personne'].permissions['read']
+ self.addoriggroups = self.schema['Personne'].permissions['add']
def tearDown(self):
CubicWebTC.tearDown(self)
- self.schema['Personne'].set_groups('read', self.readoriggroups)
- self.schema['Personne'].set_groups('add', self.addoriggroups)
+ self.schema['Personne'].set_action_permissions('read', self.readoriggroups)
+ self.schema['Personne'].set_action_permissions('add', self.addoriggroups)
class LowLevelSecurityFunctionTC(BaseSecurityTC):
@@ -29,7 +29,7 @@
rql = u'Personne U where U nom "managers"'
rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0]
origgroups = self.schema['Personne'].get_groups('read')
- self.schema['Personne'].set_groups('read', ('users', 'managers'))
+ self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
self.repo.vreg.rqlhelper.compute_solutions(rqlst)
solution = rqlst.solutions[0]
check_read_access(self.schema, self.session.user, rqlst, solution)
@@ -96,8 +96,8 @@
def test_update_security_2(self):
cnx = self.login('anon')
cu = cnx.cursor()
- self.repo.schema['Personne'].set_groups('read', ('users', 'managers'))
- self.repo.schema['Personne'].set_groups('add', ('guests', 'users', 'managers'))
+ self.repo.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
+ self.repo.schema['Personne'].set_action_permissions('add', ('guests', 'users', 'managers'))
self.assertRaises(Unauthorized, cu.execute, "SET X nom 'bidulechouette' WHERE X is Personne")
#self.assertRaises(Unauthorized, cnx.commit)
# test nothing has actually been inserted
@@ -177,7 +177,7 @@
ent = rset.get_entity(0, 0)
session.set_pool() # necessary
self.assertRaises(Unauthorized,
- ent.e_schema.check_perm, session, 'update', ent.eid)
+ ent.e_schema.check_perm, session, 'update', eid=ent.eid)
self.assertRaises(Unauthorized,
cu.execute, "SET P travaille S WHERE P is Personne, S is Societe")
# test nothing has actually been inserted:
@@ -230,7 +230,7 @@
# read security test
def test_read_base(self):
- self.schema['Personne'].set_groups('read', ('users', 'managers'))
+ self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
cnx = self.login('anon')
cu = cnx.cursor()
self.assertRaises(Unauthorized,
@@ -281,7 +281,7 @@
self.execute("INSERT Personne X: X nom 'bidule'")
self.execute("INSERT Societe X: X nom 'bidule'")
self.commit()
- self.schema['Personne'].set_groups('read', ('managers',))
+ self.schema['Personne'].set_action_permissions('read', ('managers',))
cnx = self.login('iaminusersgrouponly')
cu = cnx.cursor()
rset = cu.execute('Any N WHERE N has_text "bidule"')
@@ -293,7 +293,7 @@
self.execute("INSERT Personne X: X nom 'bidule'")
self.execute("INSERT Societe X: X nom 'bidule'")
self.commit()
- self.schema['Personne'].set_groups('read', ('managers',))
+ self.schema['Personne'].set_action_permissions('read', ('managers',))
cnx = self.login('anon')
cu = cnx.cursor()
rset = cu.execute('Any N,U WHERE N has_text "bidule", N owned_by U?')
@@ -371,8 +371,8 @@
def test_attribute_read_security(self):
# anon not allowed to see users'login, but they can see users
- self.repo.schema['CWUser'].set_groups('read', ('guests', 'users', 'managers'))
- self.repo.schema['login'].set_groups('read', ('users', 'managers'))
+ self.repo.schema['CWUser'].set_action_permissions('read', ('guests', 'users', 'managers'))
+ self.repo.schema['CWUser'].rdef('login').set_action_permissions('read', ('users', 'managers'))
cnx = self.login('anon')
cu = cnx.cursor()
rset = cu.execute('CWUser X')
@@ -494,11 +494,11 @@
# needed to avoid check_perm error
session.set_pool()
# needed to remove rql expr granting update perm to the user
- self.schema['Affaire'].set_rqlexprs('update', ())
+ self.schema['Affaire'].set_action_permissions('update', self.schema['Affaire'].get_groups('update'))
self.assertRaises(Unauthorized,
- self.schema['Affaire'].check_perm, session, 'update', eid)
+ self.schema['Affaire'].check_perm, session, 'update', eid=eid)
cu = cnx.cursor()
- self.schema['Affaire'].set_groups('read', ('users',))
+ self.schema['Affaire'].set_action_permissions('read', ('users',))
try:
aff = cu.execute('Any X WHERE X ref "ARCT01"').get_entity(0, 0)
aff.fire_transition('abort')
@@ -510,7 +510,7 @@
# from the current state but Unauthorized if it exists but user can't pass it
self.assertRaises(ValidationError, user.fire_transition, 'deactivate')
finally:
- self.schema['Affaire'].set_groups('read', ('managers',))
+ self.schema['Affaire'].set_action_permissions('read', ('managers',))
def test_trinfo_security(self):
aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0)
--- a/sobjects/notification.py Sun Nov 08 21:53:18 2009 +0100
+++ b/sobjects/notification.py Fri Nov 20 19:35:54 2009 +0100
@@ -149,14 +149,16 @@
changes = self.req.transaction_data['changes'][self.rset[0][0]]
_ = self.req._
formatted_changes = []
+ entity = self.entity(self.row or 0, self.col or 0)
for attr, oldvalue, newvalue in sorted(changes):
# check current user has permission to see the attribute
rschema = self.vreg.schema[attr]
if rschema.final:
- if not rschema.has_perm(self.req, 'read', eid=self.rset[0][0]):
+ rdef = entity.e_schema.rdef(rschema)
+ if not rdef.has_perm(self.req, 'read', eid=self.rset[0][0]):
continue
# XXX suppose it's a subject relation...
- elif not rschema.has_perm(self.req, 'read', fromeid=self.rset[0][0]):
+ elif not rschema.has_perm(self.req, 'read', fromeid=self.rset[0][0]): # XXX toeid
continue
if attr in self.no_detailed_change_attrs:
msg = _('%s updated') % _(attr)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sobjects/textparsers.py Fri Nov 20 19:35:54 2009 +0100
@@ -0,0 +1,73 @@
+"""hooks triggered on email entities creation:
+
+* look for state change instruction (XXX security)
+* set email content as a comment on an entity when comments are supported and
+ linking information are found
+
+:organization: Logilab
+:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import re
+
+from cubicweb import UnknownEid, typed_eid
+from cubicweb.view import Component
+
+ # XXX use user session if gpg signature validated
+
+class TextAnalyzer(Component):
+ """analyze and extract information from plain text by calling registered
+ text parsers
+ """
+ id = 'textanalyzer'
+
+ def parse(self, caller, text):
+ for parsercls in self.req.vreg['components'].get('textparser', ()):
+ parsercls(self.req).parse(caller, text)
+
+
+class TextParser(Component):
+ """base class for text parser, responsible to extract some information
+ from plain text. When something is done, it usually call the
+
+ .fire_event(something, {event args})
+
+ method on the caller.
+ """
+ id = 'textparser'
+ __abstract__ = True
+
+ def parse(self, caller, text):
+ raise NotImplementedError
+
+
+class ChangeStateTextParser(TextParser):
+ """search some text for change state instruction in the form
+
+ :<transition name>: #?<eid>
+ """
+ instr_rgx = re.compile(':(\w+):\s*#?(\d+)', re.U)
+
+ def parse(self, caller, text):
+ for trname, eid in self.instr_rgx.findall(text):
+ try:
+ entity = self.req.entity_from_eid(typed_eid(eid))
+ except UnknownEid:
+ self.error("can't get entity with eid %s", eid)
+ continue
+ if not hasattr(entity, 'in_state'):
+ self.error('bad change state instruction for eid %s', eid)
+ continue
+ tr = entity.current_workflow and entity.current_workflow.transition_by_name(trname)
+ if tr and tr.may_be_fired(entity.eid):
+ try:
+ trinfo = entity.fire_transition(tr)
+ caller.fire_event('state-changed', {'trinfo': trinfo,
+ 'entity': entity})
+ except:
+ self.exception('while changing state of %s', entity)
+ else:
+ self.error("can't pass transition %s on entity %s",
+ trname, entity)
--- a/test/data/erqlexpr_on_ertype.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/data/erqlexpr_on_ertype.py Fri Nov 20 19:35:54 2009 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import ERQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -18,7 +18,7 @@
toto = SubjectRelation('TuTu')
class TuTu(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -26,7 +26,7 @@
}
class toto(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', ),
'add': ('managers', ERQLExpression('S bla Y'),),
'delete': ('managers',),
--- a/test/data/rewrite/schema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/data/rewrite/schema.py Fri Nov 20 19:35:54 2009 +0100
@@ -2,7 +2,7 @@
from cubicweb.schema import ERQLExpression
class Affaire(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',
ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
@@ -15,7 +15,7 @@
class Societe(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
--- a/test/data/rqlexpr_on_ertype_read.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/data/rqlexpr_on_ertype_read.py Fri Nov 20 19:35:54 2009 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import RRQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -18,7 +18,7 @@
toto = SubjectRelation('TuTu')
class TuTu(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -26,7 +26,7 @@
}
class toto(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', RRQLExpression('S bla Y'), ),
'add': ('managers',),
'delete': ('managers',),
--- a/test/data/rrqlexpr_on_attr.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/data/rrqlexpr_on_attr.py Fri Nov 20 19:35:54 2009 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import RRQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -18,7 +18,7 @@
attr = String()
class attr(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', ),
'add': ('managers', RRQLExpression('S bla Y'),),
'delete': ('managers',),
--- a/test/data/rrqlexpr_on_eetype.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/data/rrqlexpr_on_eetype.py Fri Nov 20 19:35:54 2009 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import RRQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', RRQLExpression('S bla Y'),),
'add': ('managers',),
'update': ('managers',),
--- a/test/unittest_entity.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/unittest_entity.py Fri Nov 20 19:35:54 2009 +0100
@@ -56,7 +56,7 @@
def test_copy_with_nonmeta_composite_inlined(self):
p = self.add_entity('Personne', nom=u'toto')
oe = self.add_entity('Note', type=u'x')
- self.schema['ecrit_par'].set_rproperty('Note', 'Personne', 'composite', 'subject')
+ self.schema['ecrit_par'].rdef('Note', 'Personne').composite = 'subject'
self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
{'t': oe.eid, 'u': p.eid}, ('t','u'))
e = self.add_entity('Note', type=u'z')
@@ -120,10 +120,10 @@
Note = self.vreg['etypes'].etype_class('Note')
peschema = Personne.e_schema
seschema = Societe.e_schema
- peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '1*')
- peschema.subjrels['connait'].set_rproperty(peschema, peschema, 'cardinality', '11')
- peschema.subjrels['evaluee'].set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
- seschema.subjrels['evaluee'].set_rproperty(seschema, Note.e_schema, 'cardinality', '1*')
+ peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '1*'
+ peschema.subjrels['connait'].rdef(peschema, peschema).cardinality = '11'
+ peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema).cardinality = '1*'
+ seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema).cardinality = '1*'
# testing basic fetch_attrs attribute
self.assertEquals(Personne.fetch_rql(user),
'Any X,AA,AB,AC ORDERBY AA ASC '
@@ -158,13 +158,13 @@
self.assertEquals(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ASC '
'WHERE X is Personne, X nom AA, X connait AB?')
# testing optional relation
- peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '?*')
+ peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '?*'
Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
Societe.fetch_attrs = ('nom',)
self.assertEquals(Personne.fetch_rql(user),
'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD')
# testing relation with cardinality > 1
- peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '**')
+ peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '**'
self.assertEquals(Personne.fetch_rql(user),
'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB')
# XXX test unauthorized attribute
@@ -232,6 +232,14 @@
#rql = email.unrelated_rql('use_email', 'Person', 'object')[0]
#self.assertEquals(rql, '')
+ def test_unrelated_rql_security_nonexistant(self):
+ self.login('anon')
+ email = self.vreg['etypes'].etype_class('EmailAddress')(self.request())
+ rql = email.unrelated_rql('use_email', 'CWUser', 'object')[0]
+ self.assertEquals(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
+ 'WHERE S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
+ 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+
def test_unrelated_base(self):
p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
e = self.add_entity('Tag', name=u'x')
--- a/test/unittest_rqlrewrite.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/unittest_rqlrewrite.py Fri Nov 20 19:35:54 2009 +0100
@@ -11,13 +11,15 @@
from rql import parse, nodes, RQLHelper
from cubicweb import Unauthorized
+from cubicweb.schema import RRQLExpression
from cubicweb.rqlrewrite import RQLRewriter
from cubicweb.devtools import repotest, TestServerConfiguration
config = TestServerConfiguration('data/rewrite')
config.bootstrap_cubes()
schema = config.load_schema()
-schema.add_relation_def(mock_object(subject='Card', name='in_state', object='State', cardinality='1*'))
+from yams.buildobjs import RelationDefinition
+schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', object='State', cardinality='1*'))
rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
'has_text': 'fti'})
@@ -32,7 +34,7 @@
return {1: 'CWUser',
2: 'Card'}[eid]
-def rewrite(rqlst, snippets_map, kwargs):
+def rewrite(rqlst, snippets_map, kwargs, existingvars=None):
class FakeVReg:
schema = schema
@staticmethod
@@ -47,12 +49,15 @@
rqlhelper.simplify(rqlst, needcopy)
rewriter = RQLRewriter(mock_object(vreg=FakeVReg, user=(mock_object(eid=1))))
for v, snippets in snippets_map.items():
- snippets_map[v] = [mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0],
- expression='Any X WHERE '+snippet)
+ snippets_map[v] = [isinstance(snippet, basestring)
+ and mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0],
+ expression='Any X WHERE '+snippet)
+ or snippet
for snippet in snippets]
rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs)
solutions = rqlst.children[0].solutions
- rewriter.rewrite(rqlst.children[0], snippets_map.items(), solutions, kwargs)
+ rewriter.rewrite(rqlst.children[0], snippets_map.items(), solutions, kwargs,
+ existingvars)
test_vrefs(rqlst.children[0])
return rewriter.rewritten
@@ -241,6 +246,61 @@
u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)")
+ def test_rrqlexpr_nonexistant_subject_1(self):
+ constraint = RRQLExpression('S owned_by U')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU')
+ self.failUnlessEqual(rqlst.as_string(),
+ u"Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)")
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'OU')
+ self.failUnlessEqual(rqlst.as_string(),
+ u"Any C WHERE C is Card")
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SOU')
+ self.failUnlessEqual(rqlst.as_string(),
+ u"Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)")
+
+ def test_rrqlexpr_nonexistant_subject_2(self):
+ constraint = RRQLExpression('S owned_by U, O owned_by U, O is Card')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU')
+ self.failUnlessEqual(rqlst.as_string(),
+ 'Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'OU')
+ self.failUnlessEqual(rqlst.as_string(),
+ 'Any C WHERE C is Card, B eid %(D)s, EXISTS(A owned_by B, A is Card)')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SOU')
+ self.failUnlessEqual(rqlst.as_string(),
+ 'Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A, D owned_by A, D is Card)')
+
+ def test_rrqlexpr_nonexistant_subject_3(self):
+ constraint = RRQLExpression('U in_group G, G name "users"')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU')
+ self.failUnlessEqual(rqlst.as_string(),
+ u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", D is CWGroup)')
+
+ def test_rrqlexpr_nonexistant_subject_4(self):
+ constraint = RRQLExpression('U in_group G, G name "users", S owned_by U')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU')
+ self.failUnlessEqual(rqlst.as_string(),
+ u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", C owned_by A, D is CWGroup)')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'OU')
+ self.failUnlessEqual(rqlst.as_string(),
+ u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", D is CWGroup)')
+
+ def test_rrqlexpr_nonexistant_subject_5(self):
+ constraint = RRQLExpression('S owned_by Z, O owned_by Z, O is Card')
+ rqlst = parse('Card C')
+ rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'S')
+ self.failUnlessEqual(rqlst.as_string(),
+ u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)")
+
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_schema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/test/unittest_schema.py Fri Nov 20 19:35:54 2009 +0100
@@ -19,7 +19,7 @@
from yams.reader import PyFileReader
from cubicweb.schema import CubicWebSchema, CubicWebEntitySchema, \
- RQLConstraint, CubicWebSchemaLoader, ERQLExpression, RRQLExpression, \
+ RQLConstraint, CubicWebSchemaLoader, RQLExpression, ERQLExpression, RRQLExpression, \
normalize_expression, order_eschemas
from cubicweb.devtools import TestServerConfiguration as TestConfiguration
@@ -44,7 +44,7 @@
schema = CubicWebSchema('Test Schema')
enote = schema.add_entity_type(EntityType('Note'))
eaffaire = schema.add_entity_type(EntityType('Affaire'))
-eperson = schema.add_entity_type(EntityType('Personne', permissions=PERSONNE_PERMISSIONS))
+eperson = schema.add_entity_type(EntityType('Personne', __permissions__=PERSONNE_PERMISSIONS))
esociete = schema.add_entity_type(EntityType('Societe'))
RELS = (
@@ -73,12 +73,13 @@
for rel in RELS:
_from, _type, _to = rel.split()
if not _type.lower() in done:
- if _type == 'concerne':
- schema.add_relation_type(RelationType(_type, permissions=CONCERNE_PERMISSIONS))
- else:
- schema.add_relation_type(RelationType(_type))
+ schema.add_relation_type(RelationType(_type))
done[_type.lower()] = True
- schema.add_relation_def(RelationDefinition(_from, _type, _to))
+ if _type == 'concerne':
+ schema.add_relation_def(RelationDefinition(_from, _type, _to,
+ __permissions__=CONCERNE_PERMISSIONS))
+ else:
+ schema.add_relation_def(RelationDefinition(_from, _type, _to))
class CubicWebSchemaTC(TestCase):
@@ -94,26 +95,24 @@
self.assertEqual(schema.rschema('concerne').type, 'concerne')
def test_entity_perms(self):
- eperson.set_default_groups()
self.assertEqual(eperson.get_groups('read'), set(('managers', 'users', 'guests')))
self.assertEqual(eperson.get_groups('update'), set(('managers', 'owners',)))
self.assertEqual(eperson.get_groups('delete'), set(('managers', 'owners')))
self.assertEqual(eperson.get_groups('add'), set(('managers',)))
self.assertEqual([str(e) for e in eperson.get_rqlexprs('add')],
['Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s'])
- eperson.set_groups('read', ('managers',))
+ eperson.set_action_permissions('read', ('managers',))
self.assertEqual(eperson.get_groups('read'), set(('managers',)))
def test_relation_perms(self):
- rconcerne = schema.rschema('concerne')
- rconcerne.set_default_groups()
+ rconcerne = schema.rschema('concerne').rdef('Personne', 'Societe')
self.assertEqual(rconcerne.get_groups('read'), set(('managers', 'users', 'guests')))
self.assertEqual(rconcerne.get_groups('delete'), set(('managers',)))
self.assertEqual(rconcerne.get_groups('add'), set(('managers', )))
- rconcerne.set_groups('read', ('managers',))
+ rconcerne.set_action_permissions('read', ('managers',))
self.assertEqual(rconcerne.get_groups('read'), set(('managers',)))
self.assertEqual([str(e) for e in rconcerne.get_rqlexprs('add')],
- ['Any S WHERE U has_update_permission S, S eid %(s)s, U eid %(u)s'])
+ ['Any S,U WHERE U has_update_permission S, S eid %(s)s, U eid %(u)s'])
def test_erqlexpression(self):
self.assertRaises(RQLSyntaxError, ERQLExpression, '1')
@@ -124,7 +123,7 @@
self.assertRaises(Exception, RRQLExpression, '1')
self.assertRaises(RQLSyntaxError, RRQLExpression, 'O X Y')
expr = RRQLExpression('U has_update_permission O')
- self.assertEquals(str(expr), 'Any O WHERE U has_update_permission O, O eid %(o)s, U eid %(u)s')
+ self.assertEquals(str(expr), 'Any O,U WHERE U has_update_permission O, O eid %(o)s, U eid %(u)s')
loader = CubicWebSchemaLoader()
config = TestConfiguration('data')
@@ -215,9 +214,9 @@
self.assertListEquals(rels, ['bookmarked_by', 'created_by', 'for_user',
'identity', 'owned_by', 'wf_info_for'])
rschema = schema.rschema('relation_type')
- properties = rschema.rproperties('CWAttribute', 'CWRType')
- self.assertEquals(properties['cardinality'], '1*')
- constraints = properties['constraints']
+ properties = rschema.rdef('CWAttribute', 'CWRType')
+ self.assertEquals(properties.cardinality, '1*')
+ constraints = properties.constraints
self.failUnlessEqual(len(constraints), 1, constraints)
constraint = constraints[0]
self.failUnless(isinstance(constraint, RQLConstraint))
@@ -246,13 +245,13 @@
self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on an entity type, use an ERQLExpression (ToTo)")
def test_erqlexpr_on_rtype(self):
- self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on a relation type, use a RRQLExpression (toto)")
+ self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
def test_rqlexpr_on_rtype_read(self):
- self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of a relation type (toto)")
+ self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of relation ToTo toto TuTu")
def test_rrqlexpr_on_attr(self):
- self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on a final relation type (eg attribute relation), use an ERQLExpression (attr)")
+ self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
class NormalizeExpressionTC(TestCase):
@@ -261,5 +260,9 @@
self.assertEquals(normalize_expression('X bla Y,Y blur Z , Z zigoulou X '),
'X bla Y, Y blur Z, Z zigoulou X')
+class RQLExpressionTC(TestCase):
+ def test_comparison(self):
+ self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0))
+ self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0))
if __name__ == '__main__':
unittest_main()
--- a/utils.py Sun Nov 08 21:53:18 2009 +0100
+++ b/utils.py Fri Nov 20 19:35:54 2009 +0100
@@ -53,6 +53,13 @@
def days_in_month(date_):
return monthrange(date_.year, date_.month)[1]
+def days_in_year(date_):
+ feb = pydatetime.date(date_.year, 2, 1)
+ if days_in_month(feb) == 29:
+ return 366
+ else:
+ return 365
+
def previous_month(date_, nbmonth=1):
while nbmonth:
date_ = first_day(date_) - ONEDAY
@@ -142,6 +149,20 @@
return dict1
+# use networkX instead ?
+# http://networkx.lanl.gov/reference/algorithms.traversal.html#module-networkx.algorithms.traversal.astar
+def transitive_closure_of(entity, relname, _seen=None):
+ if _seen is None:
+ _seen = set()
+ _seen.add(entity.eid)
+ yield entity
+ for child in getattr(entity, relname):
+ if child.eid in _seen:
+ continue
+ for subchild in transitive_closure_of(child, relname, _seen):
+ yield subchild
+
+
class SizeConstrainedList(list):
"""simple list that makes sure the list does not get bigger
than a given size.
@@ -215,8 +236,16 @@
def add_raw(self, rawheader):
self.write(rawheader)
- def define_var(self, var, value):
- self.jsvars.append( (var, value) )
+ def define_var(self, var, value, override=True):
+ """adds a javascript var declaration / assginment in the header
+
+ :param var: the variable name
+ :param value: the variable value (as a raw python value,
+ it will be jsonized later)
+ :param override: if False, don't set the variable value if the variable
+ is already defined. Default is True.
+ """
+ self.jsvars.append( (var, value, override) )
def add_post_inline_script(self, content):
self.post_inlined_scripts.append(content)
@@ -270,8 +299,12 @@
# 1/ variable declaration if any
if self.jsvars:
w(u'<script type="text/javascript"><!--//--><![CDATA[//><!--\n')
- for var, value in self.jsvars:
- w(u'%s = %s;\n' % (var, dumps(value)))
+ for var, value, override in self.jsvars:
+ vardecl = u'%s = %s;' % (var, dumps(value))
+ if not override:
+ vardecl = (u'if (typeof %s == "undefined") {%s}' %
+ (var, vardecl))
+ w(vardecl + u'\n')
w(u'//--><!]]></script>\n')
# 2/ css files
for cssfile, media in self.cssfiles:
@@ -338,7 +371,7 @@
def can_do_pdf_conversion(__answer=[None]):
"""pdf conversion depends on
- * pyxmltrf (python package)
+ * pysixt (python package)
* fop 0.9x
"""
if __answer[0] is not None:
--- a/view.py Sun Nov 08 21:53:18 2009 +0100
+++ b/view.py Fri Nov 20 19:35:54 2009 +0100
@@ -34,35 +34,35 @@
style CDATA #IMPLIED
title CDATA #IMPLIED
- cubicweb:sortvalue CDATA #IMPLIED
- cubicweb:target CDATA #IMPLIED
- cubicweb:limit CDATA #IMPLIED
- cubicweb:type CDATA #IMPLIED
- cubicweb:loadtype CDATA #IMPLIED
- cubicweb:wdgtype CDATA #IMPLIED
+ cubicweb:accesskey CDATA #IMPLIED
+ cubicweb:actualrql CDATA #IMPLIED
+ cubicweb:dataurl CDATA #IMPLIED
+ cubicweb:displayactions CDATA #IMPLIED
+ cubicweb:facetName CDATA #IMPLIED
+ cubicweb:facetargs CDATA #IMPLIED
+ cubicweb:fallbackvid CDATA #IMPLIED
+ cubicweb:fname CDATA #IMPLIED
cubicweb:initfunc CDATA #IMPLIED
cubicweb:inputid CDATA #IMPLIED
- cubicweb:tindex CDATA #IMPLIED
cubicweb:inputname CDATA #IMPLIED
- cubicweb:value CDATA #IMPLIED
+ cubicweb:limit CDATA #IMPLIED
+ cubicweb:loadtype CDATA #IMPLIED
+ cubicweb:loadurl CDATA #IMPLIED
+ cubicweb:maxlength CDATA #IMPLIED
cubicweb:required CDATA #IMPLIED
- cubicweb:accesskey CDATA #IMPLIED
- cubicweb:maxlength CDATA #IMPLIED
+ cubicweb:rooteid CDATA #IMPLIED
+ cubicweb:rql CDATA #IMPLIED
+ cubicweb:size CDATA #IMPLIED
+ cubicweb:sortvalue CDATA #IMPLIED
+ cubicweb:target CDATA #IMPLIED
+ cubicweb:tindex CDATA #IMPLIED
+ cubicweb:tlunit CDATA #IMPLIED
+ cubicweb:type CDATA #IMPLIED
+ cubicweb:uselabel CDATA #IMPLIED
+ cubicweb:value CDATA #IMPLIED
cubicweb:variables CDATA #IMPLIED
- cubicweb:displayactions CDATA #IMPLIED
- cubicweb:fallbackvid CDATA #IMPLIED
- cubicweb:fname CDATA #IMPLIED
cubicweb:vid CDATA #IMPLIED
- cubicweb:rql CDATA #IMPLIED
- cubicweb:actualrql CDATA #IMPLIED
- cubicweb:rooteid CDATA #IMPLIED
- cubicweb:dataurl CDATA #IMPLIED
- cubicweb:size CDATA #IMPLIED
- cubicweb:tlunit CDATA #IMPLIED
- cubicweb:loadurl CDATA #IMPLIED
- cubicweb:uselabel CDATA #IMPLIED
- cubicweb:facetargs CDATA #IMPLIED
- cubicweb:facetName CDATA #IMPLIED
+ cubicweb:wdgtype CDATA #IMPLIED
"> ] '''
TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n' % CW_XHTML_EXTENSIONS
@@ -305,21 +305,27 @@
def create_url(self, etype, **kwargs):
""" return the url of the entity creation form for a given entity type"""
- return self._cw.build_url('add/%s'%etype, **kwargs)
+ return self._cw.build_url('add/%s' % etype, **kwargs)
- def field(self, label, value, row=True, show_label=True, w=None, tr=True):
- """ read-only field """
+ def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False):
+ """read-only field"""
if w is None:
w = self.w
- if row:
- w(u'<div class="row">')
+ if table:
+ w(u'<tr class="entityfield">')
+ else:
+ w(u'<div class="entityfield">')
if show_label and label:
if tr:
label = display_name(self._cw, label)
- w(u'<span class="label">%s</span>' % label)
- w(u'<div class="field">%s</div>' % value)
- if row:
- w(u'</div>')
+ if table:
+ w(u'<th>%s</th>' % label)
+ else:
+ w(u'<span>%s</span> ' % label)
+ if table:
+ w(u'<td>%s</td></tr>' % value)
+ else:
+ w(u'<span>%s</span></div>' % value)
--- a/web/__init__.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/__init__.py Fri Nov 20 19:35:54 2009 +0100
@@ -23,12 +23,12 @@
class stdmsgs(object):
"""standard ui message (in a class for bw compat)"""
- BUTTON_OK = _('button_ok')
- BUTTON_APPLY = _('button_apply')
- BUTTON_CANCEL = _('button_cancel')
- BUTTON_DELETE = _('button_delete')
- YES = _('yes')
- NO = _('no')
+ BUTTON_OK = (_('button_ok'), 'OK_ICON')
+ BUTTON_APPLY = (_('button_apply'), 'APPLY_ICON')
+ BUTTON_CANCEL = (_('button_cancel'), 'CANCEL_ICON')
+ BUTTON_DELETE = (_('button_delete'), 'TRASH_ICON')
+ YES = (_('yes'), None)
+ NO = (_('no'), None)
def eid_param(name, eid):
--- a/web/component.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/component.py Fri Nov 20 19:35:54 2009 +0100
@@ -40,14 +40,14 @@
help=_('display the component or not')),
_('order'): dict(type='Int', default=99,
help=_('display order of the component')),
- _('context'): dict(type='String', default='header',
+ _('context'): dict(type='String', default='navtop',
vocabulary=(_('navtop'), _('navbottom'),
- _('navcontenttop'), _('navcontentbottom')),
- #vocabulary=(_('header'), _('incontext'), _('footer')),
+ _('navcontenttop'), _('navcontentbottom'),
+ _('ctxtoolbar')),
help=_('context where this component should be displayed')),
}
- context = 'navcontentbottom' # 'footer' | 'header' | 'incontext'
+ context = 'navcontentbottom'
def call(self, view=None):
return self.cell_call(0, 0, view=view)
Binary file web/data/cancel.png has changed
--- a/web/data/cubicweb.ajax.js Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/cubicweb.ajax.js Fri Nov 20 19:35:54 2009 +0100
@@ -62,6 +62,9 @@
if (typeof roundedCorners != 'undefined') {
roundedCorners(node);
}
+ if (typeof setFormsTarget != 'undefined') {
+ setFormsTarget(node);
+ }
loadDynamicFragments(node);
// XXX simulates document.ready, but the former
// only runs once, this one potentially many times
@@ -139,14 +142,29 @@
for(var i=0; i<fragments.length; i++) {
var fragment = fragments[i];
fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>';
+ // if cubicweb:loadurl is set, just pick the url et send it to loadxhtml
+ var url = getNodeAttribute(fragment, 'cubicweb:loadurl');
+ if (url) {
+ jQuery(fragment).loadxhtml(url);
+ continue;
+ }
+ // else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc.
var rql = getNodeAttribute(fragment, 'cubicweb:rql');
- var vid = getNodeAttribute(fragment, 'cubicweb:vid');
+ var items = getNodeAttribute(fragment, 'cubicweb:vid').split('&');
+ var vid = items[0];
var extraparams = {};
+ // case where vid='myvid¶m1=val1¶m2=val2': this is a deprecated abuse-case
+ if (items.length > 1) {
+ console.log("[3.5] you're using extraargs in cubicweb:vid attribute, this is deprecated, consider using loadurl instead");
+ for (var j=1; j<items.length; j++) {
+ var keyvalue = items[j].split('=');
+ extraparams[keyvalue[0]] = keyvalue[1];
+ }
+ }
var actrql = getNodeAttribute(fragment, 'cubicweb:actualrql');
if (actrql) { extraparams['actualrql'] = actrql; }
var fbvid = getNodeAttribute(fragment, 'cubicweb:fallbackvid');
if (fbvid) { extraparams['fallbackvid'] = fbvid; }
-
replacePageChunk(fragment.id, rql, vid, extraparams);
}
}
--- a/web/data/cubicweb.css Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/cubicweb.css Fri Nov 20 19:35:54 2009 +0100
@@ -237,19 +237,6 @@
width: 100%;
}
-a.help{
- display: block;
- margin: 0px 5px 0px 8px;
- height: 17px;
- width: 17px;
- background: url('help.png') 0% 0% no-repeat;
-}
-
-a.help:hover {
- background-position: 0px -16px;
- text-decoration: none;
-}
-
/* FIXME appear with 4px width in IE6 */
div#stateheader{
min-width: 66%;
@@ -508,6 +495,7 @@
div#userActionsBox a.popupMenu {
color: black;
text-decoration: underline;
+ padding-right: 2em;
}
/* download box XXX move to its own file? */
@@ -595,20 +583,23 @@
/* basic entity view */
-div.row {
- clear: both;
- padding-bottom:0.4px
-}
-
-div.row span.label{
- padding-right:1em
+tr.entityfield th {
+ text-align: left;
+ padding-right: 0.5em;
}
div.field {
- margin-left: 0.2em;
display: inline;
}
+div.ctxtoolbar {
+ float: right;
+ padding-left: 24px;
+ position: relative;
+}
+div.toolbarButton {
+ display: inline;
+}
/***************************************/
/* messages */
--- a/web/data/cubicweb.edition.js Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/cubicweb.edition.js Fri Nov 20 19:35:54 2009 +0100
@@ -363,6 +363,9 @@
}
return true;
}
+ if (onfailure && !onfailure(result, formid, cbargs)) {
+ return false;
+ }
unfreezeFormButtons(formid);
// Failures
_clearPreviousErrors(formid);
@@ -376,9 +379,6 @@
_displayValidationerrors(formid, descr[0], descr[1]);
updateMessage(_('please correct errors below'));
document.location.hash = '#header';
- if (onfailure) {
- onfailure(formid, cbargs);
- }
return false;
}
@@ -416,8 +416,9 @@
* NOTE3: there is a XHTML module allowing iframe elements but there
* is still the problem of the form's `target` attribute
*/
-function setFormsTarget() {
- jQuery('form.entityForm').each(function () {
+function setFormsTarget(node) {
+ var $node = jQuery(node || document.body);
+ $node.find('form.entityForm').each(function () {
var form = jQuery(this);
var target = form.attr('cubicweb:target');
if (target) {
@@ -431,7 +432,7 @@
});
}
-jQuery(document).ready(setFormsTarget);
+jQuery(document).ready(function() {setFormsTarget();});
/*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/cubicweb.fckcwconfig-full.js Fri Nov 20 19:35:54 2009 +0100
@@ -0,0 +1,34 @@
+// cf /usr/share/fckeditor/fckconfig.js
+
+FCKConfig.AutoDetectLanguage = false ;
+
+FCKConfig.ToolbarSets["Default"] = [
+ // removed : 'Save','NewPage','DocProps','-','Templates','-','Preview'
+ ['Source'],
+ // removed: 'Print','-','SpellCheck'
+ ['Cut','Copy','Paste','PasteText','PasteWord'],
+ ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
+ //['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
+ '/',
+ // ,'StrikeThrough','-','Subscript','Superscript'
+ ['Bold','Italic','Underline'],
+ // ,'-','Outdent','Indent','Blockquote'
+ ['OrderedList','UnorderedList'],
+ // ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
+ ['Link','Unlink','Anchor'],
+ // removed : 'Image','Flash','Smiley','PageBreak'
+ ['Table','Rule','SpecialChar']
+ // , '/',
+ // ['Style','FontFormat','FontName','FontSize'],
+ // ['TextColor','BGColor'],
+ //,'ShowBlocks'
+ // ['FitWindow','-','About'] // No comma for the last row.
+] ;
+
+// 'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','Form',
+FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','BulletedList','NumberedList','Table'] ;
+
+FCKConfig.LinkUpload = false ;
+FCKConfig.ImageUpload = false ;
+FCKConfig.FlashUpload = false ;
+
--- a/web/data/cubicweb.fckcwconfig.js Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/cubicweb.fckcwconfig.js Fri Nov 20 19:35:54 2009 +0100
@@ -1,34 +1,15 @@
-// cf /usr/share/fckeditor/fckconfig.js
-
-FCKConfig.AutoDetectLanguage = false ;
-
-FCKConfig.ToolbarSets["Default"] = [
- // removed : 'Save','NewPage','DocProps','-','Templates','-','Preview'
- ['Source'],
- // removed: 'Print','-','SpellCheck'
- ['Cut','Copy','Paste','PasteText','PasteWord'],
- ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
- //['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
- '/',
- // ,'StrikeThrough','-','Subscript','Superscript'
- ['Bold','Italic','Underline'],
- // ,'-','Outdent','Indent','Blockquote'
- ['OrderedList','UnorderedList'],
- ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
- ['Link','Unlink','Anchor'],
- // removed : 'Image','Flash','Smiley','PageBreak'
- ['Table','Rule','SpecialChar'],
- '/',
- ['Style','FontFormat','FontName','FontSize'],
- ['TextColor','BGColor'],
- //,'ShowBlocks'
- ['FitWindow','-','About'] // No comma for the last row.
-] ;
-
-// 'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','Form',
-FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','BulletedList','NumberedList','Table'] ;
-
-FCKConfig.LinkUpload = false ;
-FCKConfig.ImageUpload = false ;
-FCKConfig.FlashUpload = false ;
-
+
+FCKConfig.AutoDetectLanguage = false ;
+
+FCKConfig.ToolbarSets["Default"] = [
+['Bold','Italic','Underline'],
+['OrderedList','UnorderedList'],
+['Link'],
+['Table']
+] ;
+// 'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','Form',
+FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','BulletedList','NumberedList','Table'] ;
+
+FCKConfig.LinkUpload = false ;
+FCKConfig.ImageUpload = false ;
+FCKConfig.FlashUpload = false ;
--- a/web/data/cubicweb.form.css Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/cubicweb.form.css Fri Nov 20 19:35:54 2009 +0100
@@ -12,7 +12,6 @@
color: #ff4500;
padding-bottom : 0.4em;
text-transform: capitalize;
- background: url("bg_trame_grise.png") left bottom repeat-x;
margin-bottom: 0.6em
}
@@ -209,10 +208,6 @@
text-align: center;
}
-div.trame_grise {
- background: url("bg_trame_grise.png") left top repeat-x;
-}
-
div.notice {
display: none;
font-style: italic;
@@ -231,7 +226,7 @@
cursor: default;
}
-input.validateButton {
+input.validateButton {
margin: 1em 1em 0px 0px;
border: 1px solid #edecd2;
border-color:#edecd2 #cfceb7 #cfceb7 #edecd2;
--- a/web/data/cubicweb.htmlhelpers.js Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/cubicweb.htmlhelpers.js Fri Nov 20 19:35:54 2009 +0100
@@ -42,11 +42,11 @@
}
/* builds an url from an object (used as a dictionnary)
- * Notable difference with MochiKit's queryString: asURL does not
- * *url_quote* each value found in the dictionnary
*
* >>> asURL({'rql' : "RQL", 'x': [1, 2], 'itemvid' : "oneline"})
* rql=RQL&vid=list&itemvid=oneline&x=1&x=2
+ * >>> asURL({'rql' : "a&b", 'x': [1, 2], 'itemvid' : "oneline"})
+ * rql=a%26b&x=1&x=2&itemvid=oneline
*/
function asURL(props) {
var chunks = [];
@@ -55,10 +55,10 @@
// generate a list of couple key=value if key is multivalued
if (isArrayLike(value)) {
for (var i=0; i<value.length;i++) {
- chunks.push(key + '=' + value[i]);
+ chunks.push(key + '=' + urlEncode(value[i]));
}
} else {
- chunks.push(key + '=' + value);
+ chunks.push(key + '=' + urlEncode(value));
}
}
return chunks.join('&');
--- a/web/data/external_resources Sun Nov 08 21:53:18 2009 +0100
+++ b/web/data/external_resources Fri Nov 20 19:35:54 2009 +0100
@@ -38,6 +38,8 @@
#FCKEDITOR_PATH = /usr/share/fckeditor/
+PUCE_UP = DATADIR/puce_up.png
+PUCE_DOWN = DATADIR/puce_down.png
# icons for entity types
BOOKMARK_ICON = DATADIR/icon_bookmark.gif
@@ -53,3 +55,8 @@
UPLOAD_ICON = DATADIR/upload.gif
GMARKER_ICON = DATADIR/gmap_blue_marker.png
UP_ICON = DATADIR/up.gif
+
+OK_ICON = DATADIR/ok.png
+CANCEL_ICON = DATADIR/cancel.png
+APPLY_ICON = DATADIR/plus.png
+TRASH_ICON = DATADIR/trash_can_small.png
Binary file web/data/nomail.gif has changed
Binary file web/data/ok.png has changed
Binary file web/data/pen_icon.png has changed
Binary file web/data/plus.png has changed
Binary file web/data/puce_up.png has changed
Binary file web/data/trash_can.png has changed
Binary file web/data/trash_can_small.png has changed
--- a/web/formfields.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/formfields.py Fri Nov 20 19:35:54 2009 +0100
@@ -81,6 +81,8 @@
role of the entity in the relation (eg 'subject' or 'object')
:fieldset:
optional fieldset to which this field belongs to
+ :order:
+ key used by automatic forms to sort fields
"""
# default widget associated to this class of fields. May be overriden per
@@ -94,7 +96,7 @@
def __init__(self, name=None, id=None, label=None, help=None,
widget=None, required=False, initial=None,
choices=None, sort=True, internationalizable=False,
- eidparam=False, role='subject', fieldset=None):
+ eidparam=False, role='subject', fieldset=None, order=None):
self.name = name
self.id = id or name
self.label = label or name
@@ -108,6 +110,7 @@
self.role = role
self.fieldset = fieldset
self.init_widget(widget)
+ self.order = order
# ordering number for this field instance
self.creation_rank = Field.__creation_rank
Field.__creation_rank += 1
@@ -178,12 +181,7 @@
renderer
"""
widget = self.get_widget(form)
- try:
- return widget.render(form, self, renderer)
- except TypeError:
- warn('[3.3] %s: widget.render now take the renderer as third argument, '
- 'please update implementation' % widget, DeprecationWarning)
- return widget.render(form, self)
+ return widget.render(form, self, renderer)
def vocabulary(self, form):
"""return vocabulary for this field. This method will be called by
@@ -203,7 +201,10 @@
else:
vocab = form.form_field_vocabulary(self)
if self.internationalizable:
- vocab = [(form._cw._(label), value) for label, value in vocab]
+ # the short-cirtcuit 'and' boolean operator is used here to permit
+ # a valid empty string in vocabulary without attempting to translate
+ # it by gettext (which can lead to weird strings display)
+ vocab = [(label and form._cw._(label), value) for label, value in vocab]
if self.sort:
vocab = vocab_sort(vocab)
return vocab
--- a/web/formwidgets.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/formwidgets.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,6 +13,7 @@
from cubicweb.common import tags, uilib
from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
+from logilab.mtconverter import xml_escape
class FieldWidget(object):
"""abstract widget class"""
@@ -472,7 +473,12 @@
setdomid=None, settabindex=None,
name='', value='', onclick=None, cwaction=None):
super(Button, self).__init__(attrs, setdomid, settabindex)
- self.label = label
+ if isinstance(label, tuple):
+ self.label = label[0]
+ self.icon = label[1]
+ else:
+ self.label = label
+ self.icon = None
self.name = name
self.value = ''
self.onclick = onclick
@@ -494,7 +500,12 @@
attrs['id'] = self.name
if self.settabindex and not 'tabindex' in attrs:
attrs['tabindex'] = form._cw.next_tabindex()
- return tags.input(value=label, type=self.type, **attrs)
+ if self.icon:
+ img = tags.img(src=form._cw.external_resource(self.icon), alt=self.icon)
+ else:
+ img = u''
+ return tags.button(img + xml_escape(label), escapecontent=False,
+ value=label, type=self.type, **attrs)
class SubmitButton(Button):
--- a/web/htmlwidgets.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/htmlwidgets.py Fri Nov 20 19:35:54 2009 +0100
@@ -12,7 +12,8 @@
from logilab.mtconverter import xml_escape
from cubicweb.utils import UStringIO
-from cubicweb.common.uilib import toggle_action
+from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
+from cubicweb.web import jsonize
# XXX HTMLWidgets should have access to req (for datadir / static urls,
# i18n strings, etc.)
@@ -250,9 +251,46 @@
def add_attr(self, attr, value):
self.cell_attrs[attr] = value
+class SimpleTableModel(object):
+ """
+ uses a list of lists as a storage backend
+
+ NB: the model expectes the cellvid passed to
+ TableColumn.append_renderer to be a callable accepting a single
+ argument and returning a unicode object
+ """
+ def __init__(self, rows):
+ self._rows = rows
+
+
+ def get_rows(self):
+ return self._rows
+
+ def render_cell(self, cellvid, rowindex, colindex, w):
+ value = self._rows[rowindex][colindex]
+ w(cellvid(value))
+
+ @htmlescape
+ @jsonize
+ def sortvalue(self, rowindex, colindex):
+ value = self._rows[rowindex][colindex]
+ if value is None:
+ return u''
+ elif isinstance(value, int):
+ return u'%09d'%value
+ else:
+ return unicode(value)
+
class TableWidget(HTMLWidget):
+ """
+ Display data in a Table with sortable column.
+ When using remember to include the required css and js with:
+
+ self.req.add_js('jquery.tablesorter.js')
+ self.req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
+ """
highlight = "onmouseover=\"addElementClass(this, 'highlighted');\" " \
"onmouseout=\"removeElementClass(this, 'highlighted');\""
@@ -304,20 +342,20 @@
def _render(self):
try:
- pourcent = self.done*100./self.total
+ percent = self.done*100./self.total
except ZeroDivisionError:
- pourcent = 0
- real_pourcent = pourcent
- if pourcent > 100 :
+ percent = 0
+ real_percent = percent
+ if percent > 100 :
color = 'done'
- pourcent = 100
+ percent = 100
elif self.todo + self.done > self.total :
color = 'overpassed'
else:
color = 'inprogress'
- if pourcent < 0:
- pourcent = 0
- self.w(u'<div class="progressbarback" title="%i %%">' % real_pourcent)
- self.w(u'<div class="progressbar %s" style="width: %spx; align: left;" ></div>' % (color, pourcent))
+ if percent < 0:
+ percent = 0
+ self.w(u'<div class="progressbarback" title="%i %%">' % real_percent)
+ self.w(u'<div class="progressbar %s" style="width: %spx; align: left;" ></div>' % (color, percent))
self.w(u'</div>')
--- a/web/request.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/request.py Fri Nov 20 19:35:54 2009 +0100
@@ -27,7 +27,7 @@
from cubicweb.dbapi import DBAPIRequest
from cubicweb.common.mail import header
from cubicweb.common.uilib import remove_html_tags
-from cubicweb.utils import SizeConstrainedList, HTMLHead
+from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
RequestError, StatusResponse)
@@ -87,7 +87,20 @@
self.next_tabindex = self.tabindexgen.next
# page id, set by htmlheader template
self.pageid = None
+ self.varmaker = rqlvar_maker()
self.datadir_url = self._datadir_url()
+ self._set_pageid()
+
+ def _set_pageid(self):
+ """initialize self.pageid
+ if req.form provides a specific pageid, use it, otherwise build a
+ new one.
+ """
+ pid = self.form.get('pageid')
+ if pid is None:
+ pid = make_uid(id(self))
+ self.pageid = pid
+ self.html_headers.define_var('pageid', pid, override=False)
@property
def varmaker(self):
--- a/web/uicfg.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/uicfg.py Fri Nov 20 19:35:54 2009 +0100
@@ -113,30 +113,24 @@
from cubicweb.web import formwidgets
-def card_from_role(card, role):
- if role == 'subject':
- return card[0]
- assert role in ('object', 'sobject'), repr(role)
- return card[1]
-
# primary view configuration ##################################################
def init_primaryview_section(rtag, sschema, rschema, oschema, role):
if rtag.get(sschema, rschema, oschema, role) is None:
- card = card_from_role(rschema.rproperty(sschema, oschema, 'cardinality'), role)
- composed = rschema.rproperty(sschema, oschema, 'composite') == neg_role(role)
+ rdef = rschema.rdef(sschema, oschema)
if rschema.final:
if rschema.meta or sschema.is_metadata(rschema) \
or oschema.type in ('Password', 'Bytes'):
section = 'hidden'
else:
section = 'attributes'
- elif card in '1+':
- section = 'attributes'
- elif composed:
- section = 'relations'
else:
- section = 'sideboxes'
+ if rdef.role_cardinality(role) in '1+':
+ section = 'attributes'
+ elif rdef.composite == neg_role(role):
+ section = 'relations'
+ else:
+ section = 'sideboxes'
rtag.tag_relation((sschema, rschema, oschema, role), section)
primaryview_section = RelationTags('primaryview_section',
@@ -200,6 +194,8 @@
def init(self, schema, check=True):
for eschema in schema.entities():
+ if eschema.final:
+ continue
if eschema.schema_entity():
self.setdefault(eschema, 'schema')
elif eschema.is_subobject(strict=True):
@@ -357,12 +353,18 @@
for rschema, targetschemas, role in eschema.relation_definitions(True):
# check category first, potentially lower cost than checking
# permission which may imply rql queries
- if tag is not None:
- targetschemas = [tschema for tschema in targetschemas
- if tag in self.etype_get(eschema, rschema,
- role, tschema)]
- if not targetschemas:
+ _targetschemas = []
+ for tschema in targetschemas:
+ if not rtags.etype_get(eschema, rschema, role, tschema) in categories:
+ continue
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if not ((not strict and rdef.has_local_role(permission)) or
+ rdef.has_perm(entity.req, permission, fromeid=eid)):
+ continue
+ _targetschemas.append(tschema)
+ if not _targetschemas:
continue
+ targetschemas = _targetschemas
if permission is not None:
# tag allowing to hijack the permission machinery when
# permission is not verifiable until the entity is actually
@@ -371,12 +373,9 @@
yield (rschema, targetschemas, role)
continue
if rschema.final:
- if not rschema.has_perm(entity._cw, permission, eid):
+ if not eschema.rdef(rschema).has_perm(entity._cw, permission, eid):
continue
elif role == 'subject':
- if not ((not strict and rschema.has_local_role(permission)) or
- rschema.has_perm(entity._cw, permission, fromeid=eid)):
- continue
# on relation with cardinality 1 or ?, we need delete perm as well
# if the relation is already set
if (permission == 'add'
@@ -386,9 +385,6 @@
toeid=entity.related(rschema.type, role)[0][0])):
continue
elif role == 'object':
- if not ((not strict and rschema.has_local_role(permission)) or
- rschema.has_perm(entity._cw, permission, toeid=eid)):
- continue
# on relation with cardinality 1 or ?, we need delete perm as well
# if the relation is already set
if (permission == 'add'
@@ -421,9 +417,8 @@
if rschema in META_RTYPES:
rtag.tag_relation((sschema, rschema, oschema, role), False)
return
- card = rschema.rproperty(sschema, oschema, 'cardinality')[role == 'object']
- if not card in '?1' and \
- rschema.rproperty(sschema, oschema, 'composite') == role:
+ rdef = rschema.rdef(sschema, oschema)
+ if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
rtag.tag_relation((sschema, rschema, oschema, role), True)
actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
--- a/web/views/actions.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/actions.py Fri Nov 20 19:35:54 2009 +0100
@@ -272,20 +272,21 @@
for rschema in rschemas:
if rschema.final:
continue
- # check the relation can be added as well
- # XXX consider autoform_permissions_overrides?
- if role == 'subject'and not rschema.has_perm(req, 'add',
- fromeid=entity.eid):
- continue
- if role == 'object'and not rschema.has_perm(req, 'add',
- toeid=entity.eid):
- continue
- # check the target types can be added as well
for teschema in rschema.targets(eschema, role):
if not appearsin_addmenu.etype_get(eschema, rschema,
role, teschema):
continue
- if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
+ rdef = rschema.role_rdef(eschema, teschema, role)
+ # check the relation can be added
+ # XXX consider autoform_permissions_overrides?
+ if role == 'subject'and not rdef.has_perm(
+ req, 'add', fromeid=entity.eid):
+ continue
+ if role == 'object'and not rdef.has_perm(
+ req, 'add', toeid=entity.eid):
+ continue
+ # check the target types can be added as well
+ if teschema.may_have_permission('add', req):
yield rschema, teschema, role
def linkto_url(self, entity, rtype, etype, target):
--- a/web/views/autoform.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/autoform.py Fri Nov 20 19:35:54 2009 +0100
@@ -50,6 +50,94 @@
# class methods mapping schema relations to fields in the form ############
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/web/views/autoform.py
+=======
+ @classmethod
+ def erelations_by_category(cls, entity, categories=None, permission=None,
+ rtags=None, strict=False):
+ """return a list of (relation schema, target schemas, role) matching
+ categories and permission
+
+ `strict`:
+ bool telling if having local role is enough (strict = False) or not
+ """
+ if categories is not None:
+ if not isinstance(categories, (list, tuple, set, frozenset)):
+ categories = (categories,)
+ if not isinstance(categories, (set, frozenset)):
+ categories = frozenset(categories)
+ eschema = entity.e_schema
+ if rtags is None:
+ rtags = cls.rcategories
+ permsoverrides = cls.rpermissions_overrides
+ if entity.has_eid():
+ eid = entity.eid
+ else:
+ eid = None
+ strict = False
+ for rschema, targetschemas, role in eschema.relation_definitions(True):
+ # check category first, potentially lower cost than checking
+ # permission which may imply rql queries
+ if categories is not None:
+ _targetschemas = []
+ for tschema in targetschemas:
+ if not rtags.etype_get(eschema, rschema, role, tschema) in categories:
+ continue
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if not ((not strict and rdef.has_local_role(permission)) or
+ rdef.has_perm(entity.req, permission, fromeid=eid)):
+ continue
+ _targetschemas.append(tschema)
+ if not _targetschemas:
+ continue
+ targetschemas = _targetschemas
+ if permission is not None:
+ # tag allowing to hijack the permission machinery when
+ # permission is not verifiable until the entity is actually
+ # created...
+ if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+ yield (rschema, targetschemas, role)
+ continue
+ if rschema.final:
+ if not eschema.rdef(rschema).has_perm(entity.req, permission, eid=eid):
+ continue
+ elif role == 'subject':
+ # on relation with cardinality 1 or ?, we need delete perm as well
+ # if the relation is already set
+ if (permission == 'add'
+ and rschema.rdef(eschema, targetschemas[0]).role_cardinality(role) in '1?'
+ and eid and entity.related(rschema.type, role)
+ and not rschema.has_perm(entity.req, 'delete', fromeid=eid,
+ toeid=entity.related(rschema.type, role)[0][0])):
+ continue
+ elif role == 'object':
+ # on relation with cardinality 1 or ?, we need delete perm as well
+ # if the relation is already set
+ if (permission == 'add'
+ and rschema.rdef(targetschemas[0], eschema).role_cardinality(role) in '1?'
+ and eid and entity.related(rschema.type, role)
+ and not rschema.has_perm(entity.req, 'delete', toeid=eid,
+ fromeid=entity.related(rschema.type, role)[0][0])):
+ continue
+ yield (rschema, targetschemas, role)
+
+ @classmethod
+ def esrelations_by_category(cls, entity, categories=None, permission=None,
+ strict=False):
+ """filter out result of relations_by_category(categories, permission) by
+ removing final relations
+
+ return a sorted list of (relation's label, relation'schema, role)
+ """
+ result = []
+ for rschema, ttypes, role in cls.erelations_by_category(
+ entity, categories, permission, strict=strict):
+ if rschema.final:
+ continue
+ result.append((rschema.display_name(entity.req, role), rschema, role))
+ return sorted(result)
+
+>>>>>>> /tmp/autoform.py~other.rHDQ-C
@iclassmethod
def field_by_name(cls_or_self, name, role='subject', eschema=None):
"""return field with the given name and role. If field is not explicitly
@@ -101,6 +189,8 @@
continue
self.maxrelitems = self._cw.property_value('navigation.related-limit')
self.force_display = bool(self._cw.form.get('__force_display'))
+ fnum = len(self.fields)
+ self.fields.sort(key=lambda f: f.order is None and fnum or f.order)
@property
def related_limit(self):
--- a/web/views/basecomponents.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/basecomponents.py Fri Nov 20 19:35:54 2009 +0100
@@ -215,23 +215,30 @@
self.w(u' | '.join(html))
self.w(u'</div>')
-class PdfViewComponent(component.Component):
+
+class PdfViewComponent(component.EntityVComponent):
__regid__ = 'pdfview'
__select__ = yes()
- context = 'header'
- cw_property_defs = {
- _('visible'): dict(type='Boolean', default=True,
- help=_('display the pdf icon or not')),
- }
+ context = 'ctxtoolbar'
- def call(self, vid):
- entity = self.entity(0,0)
- url = entity.absolute_url(vid=vid, __template='pdf-main-template')
- self.w(u'<a href="%s" class="otherView"><img src="data/pdf_icon.gif" alt="%s"/></a>' %
- (xml_escape(url), self._cw._('download page as pdf')))
+ def cell_call(self, row, col, view):
+ entity = self.entity(row, col)
+ url = entity.absolute_url(vid=view.id, __template='pdf-main-template')
+ iconurl = self.req.build_url('data/pdf_icon.gif')
+ label = self.req._('Download page as pdf')
+ self.w(u'<a href="%s" title="%s" class="toolbarButton"><img src="%s" alt="%s"/></a>' %
+ (xml_escape(url), label, iconurl, label))
+class MetaDataComponent(component.EntityVComponent):
+ id = 'metadata'
+ context = 'navbottom'
+ order = 1
+
+ def cell_call(self, row, col, view=None):
+ self.wview('metadata', self.rset, row=row, col=col)
+
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
--- a/web/views/basecontrollers.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/basecontrollers.py Fri Nov 20 19:35:54 2009 +0100
@@ -131,8 +131,8 @@
req.set_message(req._("You have no access to this view or it can not "
"be used to display the current data."))
self.warning("the view %s can not be applied to this query", vid)
- vid = vid_from_rset(req, rset, self._cw.vreg.schema)
- view = self._cw.vreg['views'].select(vid, req, rset=rset)
+ vid = req.form.get('fallbackvid') or vid_from_rset(req, rset, req.vreg.schema)
+ view = req.vreg['views'].select(vid, req, rset=rset)
return view, rset
def add_to_breadcrumbs(self, view):
@@ -249,8 +249,12 @@
note: it's the responsability of js_* methods to set the correct
response content type
"""
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/web/views/basecontrollers.py
self._cw.json_request = True
self._cw.pageid = self._cw.form.get('pageid')
+=======
+ self.req.json_request = True
+>>>>>>> /tmp/basecontrollers.py~other.K2YVFx
try:
fname = self._cw.form['fname']
func = getattr(self, 'js_%s' % fname)
--- a/web/views/basetemplates.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/basetemplates.py Fri Nov 20 19:35:54 2009 +0100
@@ -13,7 +13,7 @@
from cubicweb.appobject import objectify_selector
from cubicweb.selectors import match_kwargs
from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW
-from cubicweb.utils import make_uid, UStringIO, can_do_pdf_conversion
+from cubicweb.utils import UStringIO, can_do_pdf_conversion
from cubicweb.schema import display_name
# main templates ##############################################################
@@ -315,7 +315,6 @@
self.stylesheets()
self.javascripts()
self.alternates()
- self.pageid()
def favicon(self):
favicon = self._cw.external_resource('FAVICON', None)
@@ -343,12 +342,6 @@
self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
% xml_escape(urlgetter.feed_url()))
- def pageid(self):
- req = self._cw
- pid = make_uid(id(req))
- req.pageid = pid
- req.html_headers.define_var('pageid', pid)
-
class HTMLPageHeader(View):
"""default html page header"""
@@ -387,11 +380,6 @@
'loggeduserlink', self._cw, rset=self.cw_rset)
if comp and comp.cw_propval('visible'):
comp.render(w=self.w)
- self.w(u'</td><td>')
- helpcomp = self._cw.vreg['components'].select_or_none(
- 'help', self._cw, rset=self.cw_rset)
- if helpcomp and helpcomp.cw_propval('visible'):
- helpcomp.render(w=self.w)
self.w(u'</td>')
# lastcolumn
self.w(u'<td id="lastcolumn">')
@@ -417,7 +405,7 @@
class HTMLPageFooter(View):
- """default html page footer: include logo if any, and close the HTML body
+ """default html page footer: include footer actions
"""
__regid__ = 'footer'
--- a/web/views/baseviews.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/baseviews.py Fri Nov 20 19:35:54 2009 +0100
@@ -174,7 +174,7 @@
entity = self.cw_rset.get_entity(row, col)
self.w(u'<div class="metadata">')
if self.show_eid:
- self.w(u'#%s - ' % entity.eid)
+ self.w(u'%s #%s - ' % (entity.dc_type(), entity.eid))
if entity.modification_date != entity.creation_date:
self.w(u'<span>%s</span> ' % _('latest update on'))
self.w(u'<span class="value">%s</span>, '
--- a/web/views/cwuser.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/cwuser.py Fri Nov 20 19:35:54 2009 +0100
@@ -16,6 +16,14 @@
uicfg.primaryview_section.tag_attribute(('CWUser', 'login'), 'hidden')
+uicfg.primaryview_section.tag_attribute(('CWGroup', 'name'), 'hidden')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations')
+uicfg.primaryview_section.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations')
+
class UserPreferencesEntityAction(action.Action):
__regid__ = 'prefs'
__select__ = (one_line_rset() & implements('CWUser') &
--- a/web/views/editforms.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/editforms.py Fri Nov 20 19:35:54 2009 +0100
@@ -51,8 +51,8 @@
domid = 'deleteconf'
copy_nav_params = True
- form_buttons = [Button(stdmsgs.YES, cwaction='delete'),
- Button(stdmsgs.NO, cwaction='cancel')]
+ form_buttons = [Button(stdmsgs.BUTTON_DELETE, cwaction='delete'),
+ Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
@property
def action(self):
return self._cw.build_url('edit')
@@ -113,7 +113,7 @@
_onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
"'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
_cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
- _defaultlandingzone = (u'<img title="%(msg)s" src="data/file.gif" '
+ _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
'alt="%(msg)s"/>')
_landingzonemsg = _('click to edit this field')
# default relation vids according to cardinality
@@ -197,14 +197,17 @@
"""
w = self.w
divid = form.event_args['divid']
- w(u'<div id="%s-reledit" class="field">' % form.event_args['divid'])
- w(u'<div id="%s" class="editableField" onclick="%s" title="%s">' % (
+ w(u'<div id="%s-reledit" class="field" '
+ u'onmouseout="addElementClass(jQuery(\'#%s\'), \'hidden\')" '
+ u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
+ % (divid, divid, divid))
+ w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
+ w(form.form_render(renderer=renderer))
+ w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
divid, xml_escape(self._onclick % form.event_args),
self.req._(self._landingzonemsg)))
w(lzone)
w(u'</div>')
- w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
- w(form.form_render(renderer=renderer))
w(u'</div>')
def _compute_best_vid(self, eschema, rschema, role):
@@ -269,10 +272,8 @@
dispctrl = uicfg.primaryview_display_ctrl.etype_get(eschema, rtype, role)
vid = dispctrl.get('vid', 'reledit')
if vid != 'reledit': # reledit explicitly disabled
- self.wview(vid, entity.related(rtype, role), 'null')
return False
if eschema.role_rproperty(role, rschema, 'composite') == role:
- self.wview(rvid, entity.related(rtype, role), 'null')
return False
return super(AutoClickAndEditFormView, self).should_edit_relation(
entity, rschema, role, rvid)
@@ -426,6 +427,7 @@
form = self._cw.vreg['forms'].select('edition', self._cw,
rset=self.cw_rset, row=row,
formtype='muledit',
+ copy_nav_params=False,
mainform=False)
# XXX rely on the EntityCompositeFormRenderer to put the eid input
form.remove_field(form.field_by_name('eid'))
@@ -442,7 +444,9 @@
should be the eid
"""
#self.form_title(entity)
- form = self._cw.vreg['forms'].select(self.__regid__, self._cw, rset=self.cw_rset)
+ form = self._cw.vreg['forms'].select(self.__regid__, self._cw,
+ rset=self.cw_rset,
+ copy_nav_params=True)
self.w(form.form_render())
@@ -577,4 +581,3 @@
self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
% (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
self.w(u'</div>')
- self.w(u'<div class="trame_grise"> </div>')
--- a/web/views/forms.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/forms.py Fri Nov 20 19:35:54 2009 +0100
@@ -89,6 +89,7 @@
if mainform:
self.form_add_hidden('__errorurl', self.session_key())
self.form_add_hidden('__domid', self.domid)
+ # XXX why do we need two different variables (mainform and copy_nav_params ?)
if self.copy_nav_params:
for param in NAV_FORM_PARAMETERS:
if not param in kwargs:
--- a/web/views/ibreadcrumbs.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/ibreadcrumbs.py Fri Nov 20 19:35:54 2009 +0100
@@ -30,22 +30,29 @@
title = _('contentnavigation_breadcrumbs')
help = _('contentnavigation_breadcrumbs_description')
separator = u' > '
+ link_template = u'<a href="%s">%s</a>'
def call(self, view=None, first_separator=True):
entity = self.cw_rset.get_entity(0, 0)
path = entity.breadcrumbs(view)
if path:
- self.w(u'<span id="breadcrumbs" class="pathbar">')
+ self.open_breadcrumbs()
if first_separator:
self.w(self.separator)
self.render_breadcrumbs(entity, path)
- self.w(u'</span>')
+ self.close_breadcrumbs()
+
+ def open_breadcrumbs(self):
+ self.w(u'<span id="breadcrumbs" class="pathbar">')
+
+ def close_breadcrumbs(self):
+ self.w(u'</span>')
def render_breadcrumbs(self, contextentity, path):
root = path.pop(0)
if isinstance(root, Entity):
- self.w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
- root.dc_type('plural')))
+ self.w(self.link_template % (self._cw.build_url(root.__regid__),
+ root.dc_type('plural')))
self.w(self.separator)
self.wpath_part(root, contextentity, not path)
for i, parent in enumerate(path):
@@ -62,7 +69,7 @@
elif isinstance(part, tuple):
url, title = part
textsize = self._cw.property_value('navigation.short-line-size')
- self.w(u'<a href="%s">%s</a>' % (
+ self.w(self.link_template % (
xml_escape(url), xml_escape(uilib.cut(title, textsize))))
else:
textsize = self._cw.property_value('navigation.short-line-size')
--- a/web/views/idownloadable.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/idownloadable.py Fri Nov 20 19:35:54 2009 +0100
@@ -25,7 +25,7 @@
return 0
return 1
-def download_box(w, entity, title=None, label=None):
+def download_box(w, entity, title=None, label=None, footer=u''):
req = entity._cw
w(u'<div class="sideBox">')
if title is None:
@@ -37,8 +37,8 @@
% (xml_escape(entity.download_url()),
req.external_resource('DOWNLOAD_ICON'),
_('download icon'), xml_escape(label or entity.dc_title())))
- w(u'</div>')
- w(u'</div>\n</div>\n')
+ w(u'%s</div>' % footer)
+ w(u'</div></div>\n')
class DownloadBox(EntityBoxTemplate):
@@ -113,7 +113,8 @@
except TransformError:
pass
except Exception, ex:
- msg = self._cw._("can't display data, unexpected error: %s") % ex
+ msg = self._cw._("can't display data, unexpected error: %s") \
+ % xml_escape(str(ex))
self.w('<div class="error">%s</div>' % msg)
self.w(u'</div>')
--- a/web/views/owl.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/owl.py Fri Nov 20 19:35:54 2009 +0100
@@ -71,10 +71,15 @@
if writeprefix:
self.w(OWL_CLOSING_ROOT)
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/web/views/owl.py
def should_display_rschema(self, rschema):
return not rschema in self.skiptypes and (
rschema.has_local_role('read') or
rschema.has_perm(self._cw, 'read'))
+=======
+ def should_display_rschema(self, eschema, rschema, tschemas, role):
+ return rschema.may_have_permissions('read', self.req, eschema, role)
+>>>>>>> /tmp/owl.py~other.-maWGS
def visit_schema(self, skiptypes):
"""get a layout for a whole schema"""
@@ -94,7 +99,7 @@
self.w(u'<owl:Class rdf:ID="%s">'% eschema)
self.w(u'<!-- relations -->')
for rschema, targetschemas, role in eschema.relation_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, targetschemas, role):
continue
for oeschema in targetschemas:
if role == 'subject':
@@ -112,7 +117,7 @@
self.w(u'<!-- attributes -->')
for rschema, aschema in eschema.attribute_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, (aschema,), 'subject'):
continue
self.w(u'''<rdfs:subClassOf>
<owl:Restriction>
@@ -125,7 +130,7 @@
def visit_property_schema(self, eschema):
"""get a layout for property entity OWL schema"""
for rschema, targetschemas, role in eschema.relation_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, targetschemas, role):
continue
for oeschema in targetschemas:
self.w(u'''<owl:ObjectProperty rdf:ID="%s">
@@ -135,7 +140,7 @@
def visit_property_object_schema(self, eschema):
for rschema, aschema in eschema.attribute_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, (aschema,), 'subject'):
continue
self.w(u'''<owl:DatatypeProperty rdf:ID="%s">
<rdfs:domain rdf:resource="#%s"/>
@@ -174,7 +179,8 @@
for rschema, aschema in eschema.attribute_definitions():
if rschema.meta:
continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self._cw, 'read')):
+ rdef = rschema.rdef(eschema, aschema)
+ if not rdef.may_have_permission('read', self._cw):
continue
aname = rschema.type
if aname == 'eid':
@@ -189,7 +195,12 @@
for rschema, targetschemas, role in eschema.relation_definitions():
if rschema.meta:
continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self._cw, 'read')):
+ for tschema in targetschemas:
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if rdef.may_have_permission('read', self.req):
+ break
+ else:
+ # no read perms to any relation of this type. Skip.
continue
if role == 'object':
attr = 'reverse_%s' % rschema.type
--- a/web/views/plots.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/plots.py Fri Nov 20 19:35:54 2009 +0100
@@ -118,7 +118,8 @@
{'plotdefs': '\n'.join(plotdefs),
'figid': figid,
'plotdata': ','.join(plotdata),
- 'mode': self.timemode and "'time'" or 'null'})
+ 'mode': self.timemode and "'time'" or 'null'},
+ jsoncall=req.form.get('jsoncall', False))
class PlotView(baseviews.AnyRsetView):
--- a/web/views/primary.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/primary.py Fri Nov 20 19:35:54 2009 +0100
@@ -46,7 +46,8 @@
def render_entity(self, entity):
self.render_entity_title(entity)
- self.render_entity_metadata(entity)
+ # XXX uncomment this in 3.6
+ #self.render_entity_toolbox(entity)
# entity's attributes and relations, excluding meta data
# if the entity isn't meta itself
boxes = self._prepare_side_boxes(entity)
@@ -87,10 +88,13 @@
"""default implementation return dc_title"""
title = xml_escape(entity.dc_title())
if title:
- self.w(u'<h1><span class="etype">%s</span> %s</h1>'
- % (entity.dc_type().capitalize(), title))
+ self.w(u'<h1>%s</h1>' % title)
+
+ def render_entity_toolbox(self, entity):
+ self.content_navigation_components('ctxtoolbar')
def render_entity_metadata(self, entity):
+ # XXX deprecated
entity.view('metadata', w=self.w)
def render_entity_summary(self, entity):
@@ -103,7 +107,11 @@
return u''
def render_entity_attributes(self, entity, siderelations=None):
- for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'):
+ entity_attributes = self._section_def(entity, 'attributes')
+ if not entity_attributes:
+ return
+ self.w(u'<table>')
+ for rschema, tschemas, role, dispctrl in entity_attributes:
vid = dispctrl.get('vid', 'reledit')
if rschema.final or vid == 'reledit':
value = entity.view(vid, rtype=rschema.type, role=role)
@@ -115,7 +123,8 @@
value = None
if self.skip_none and (value is None or value == ''):
continue
- self._render_attribute(rschema, value)
+ self._render_attribute(rschema, value, role=role, table=True)
+ self.w(u'</table>')
def render_entity_relations(self, entity, siderelations=None):
for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):
@@ -191,13 +200,13 @@
initargs={'dispctrl': dispctrl})
self.w(u'</div>')
- def _render_attribute(self, rschema, value, role='subject'):
+ def _render_attribute(self, rschema, value, role='subject', table=False):
if rschema.final:
show_label = self.show_attr_label
else:
show_label = self.show_rel_label
label = display_name(self._cw, rschema.type, role)
- self.field(label, value, show_label=show_label, tr=False)
+ self.field(label, value, show_label=show_label, tr=False, table=table)
class RelatedView(EntityView):
@@ -223,7 +232,7 @@
# else show links to display related entities
else:
rql = self.cw_rset.printable_rql()
- self.cw_rset.limit(maxrelated) # remove extra entity
+ self.cw_rset.limit(limit) # remove extra entity
self.w(u'<div>')
self.wview('simplelist', self.cw_rset)
self.w(u'[<a href="%s">%s</a>]' % (self._cw.build_url(rql=rql),
@@ -232,18 +241,20 @@
## default primary ui configuration ###########################################
+_pvs = uicfg.primaryview_section
for rtype in ('eid', 'creation_date', 'modification_date', 'cwuri',
'is', 'is_instance_of', 'identity',
'owned_by', 'created_by', 'in_state',
'wf_info_for', 'by_transition', 'from_state', 'to_state',
'require_permission', 'from_entity', 'to_entity',
'see_also'):
- uicfg.primaryview_section.tag_subject_of(('*', rtype, '*'), 'hidden')
- uicfg.primaryview_section.tag_object_of(('*', rtype, '*'), 'hidden')
-uicfg.primaryview_section.tag_subject_of(('*', 'use_email', '*'), 'attributes')
-uicfg.primaryview_section.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
+ _pvs.tag_subject_of(('*', rtype, '*'), 'hidden')
+ _pvs.tag_object_of(('*', rtype, '*'), 'hidden')
+
+_pvs.tag_subject_of(('*', 'use_email', '*'), 'attributes')
+_pvs.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
for attr in ('name', 'final'):
- uicfg.primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
+ _pvs.tag_attribute(('CWEType', attr), 'hidden')
for attr in ('name', 'final', 'symetric', 'inlined'):
- uicfg.primaryview_section.tag_attribute(('CWRType', attr), 'hidden')
+ _pvs.tag_attribute(('CWRType', attr), 'hidden')
--- a/web/views/schema.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/schema.py Fri Nov 20 19:35:54 2009 +0100
@@ -358,13 +358,11 @@
def should_display_schema(self, rschema):
return (super(RestrictedSchemaVisitorMixIn, self).should_display_schema(rschema)
- and (rschema.has_local_role('read')
- or rschema.has_perm(self._cw, 'read')))
+ and rschema.may_have_permission('read', self._cw))
- def should_display_attr(self, rschema):
+ def should_display_attr(self, eschema, rschema):
return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(rschema)
- and (rschema.has_local_role('read')
- or rschema.has_perm(self._cw, 'read')))
+ and eschema.rdef(rschema).may_have_permission('read', self._cw))
class FullSchemaVisitor(RestrictedSchemaVisitorMixIn, s2d.FullSchemaVisitor):
--- a/web/views/startup.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/startup.py Fri Nov 20 19:35:54 2009 +0100
@@ -130,8 +130,7 @@
"""
req = self._cw
for eschema in eschemas:
- if eschema.final or (not eschema.has_perm(req, 'read') and
- not eschema.has_local_role('read')):
+ if eschema.final or not eschema.may_have_permission('read', req):
continue
etype = eschema.type
label = display_name(req, etype, 'plural')
--- a/web/views/tableview.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/tableview.py Fri Nov 20 19:35:54 2009 +0100
@@ -15,6 +15,7 @@
from cubicweb.selectors import nonempty_rset, match_form_params
from cubicweb.utils import make_uid
from cubicweb.view import EntityView, AnyRsetView
+from cubicweb.common import tags
from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
from cubicweb.web import jsonize
from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
@@ -179,9 +180,8 @@
def render_actions(self, divid, actions):
box = MenuWidget('', 'tableActionsBox', _class='', islist=False)
- label = '<img src="%s" alt="%s"/>' % (
- self._cw.datadir_url + 'liveclipboard-icon.png',
- xml_escape(self._cw._('action(s) on this selection')))
+ label = tags.img(src=self._cw.external_resource('PUCE_DOWN'),
+ alt=xml_escape(self._cw._('action(s) on this selection')))
menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
ident='%sActions' % divid)
box.append(menu)
--- a/web/views/tabs.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/tabs.py Fri Nov 20 19:35:54 2009 +0100
@@ -20,8 +20,6 @@
class LazyViewMixin(object):
"""provides two convenience methods for the tab machinery
can also be used to lazy-load arbitrary views
- caveat : lazyview is not recursive, i.e : you can't (successfully)
- lazyload a view that in turns does the same
"""
def _prepare_bindings(self, vid, reloadable):
@@ -190,7 +188,8 @@
def cell_call(self, row, col):
entity = self.complete_entity(row, col)
self.render_entity_title(entity)
- self.render_entity_metadata(entity)
+ # XXX uncomment this in 3.6
+ #self.render_entity_toolbox(entity)
self.render_tabs(self.tabs, self.default_tab, entity)
@@ -203,7 +202,5 @@
def render_entity_title(self, entity):
pass
-
- def render_entity_metadata(self, entity):
+ def render_entity_toolbox(self, entity):
pass
-
--- a/web/views/timetable.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/timetable.py Fri Nov 20 19:35:54 2009 +0100
@@ -36,14 +36,13 @@
dates = {}
users = []
users_max = {}
-
# XXX: try refactoring with calendar.py:OneMonthCal
for row in xrange(self.cw_rset.rowcount):
task = self.cw_rset.get_entity(row, 0)
- if len(self.cw_rset[row])>1:
+ if len(self.cw_rset[row]) > 1:
user = self.cw_rset.get_entity(row, 1)
else:
- user = u"*"
+ user = ALL_USERS
the_dates = []
if task.start and task.stop:
if task.start.toordinal() == task.stop.toordinal():
@@ -137,10 +136,10 @@
columns = []
for user, width in zip(users, widths):
self.w(u'<th colspan="%s">' % max(MIN_COLS, width))
- if user != u"*":
+ if user is ALL_USERS:
+ self.w('*')
+ else:
user.view('oneline', w=self.w)
- else:
- self.w(user)
self.w(u'</th>')
self.w(u'</tr>\n')
return columns
--- a/web/views/treeview.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/treeview.py Fri Nov 20 19:35:54 2009 +0100
@@ -180,7 +180,7 @@
# the local node info
self.wview(vid, self.cw_rset, row=row, col=col, **morekwargs)
if is_open and not is_leaf: # => rql is defined
- self.wview(parentvid, self._cw.execute(rql), subvid=vid,
+ self.wview(parentvid, entity.children(entities=False), subvid=vid,
treeid=treeid, initial_load=False, **morekwargs)
w(u'</li>')
--- a/web/views/urlrewrite.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/urlrewrite.py Fri Nov 20 19:35:54 2009 +0100
@@ -7,6 +7,7 @@
"""
import re
+from cubicweb import typed_eid
from cubicweb.appobject import AppObject
@@ -148,10 +149,14 @@
def rgx_action(rql=None, args=None, cachekey=None, argsgroups=(), setuser=False,
form=None, formgroups=(), transforms={}, controller=None):
- def do_build_rset(inputurl, uri, req, schema):
+ def do_build_rset(inputurl, uri, req, schema,
+ cachekey=cachekey # necessary to avoid UnboundLocalError
+ ):
if rql:
kwargs = args and args.copy() or {}
if argsgroups:
+ if cachekey is not None and isinstance(cachekey, basestring):
+ cachekey = (cachekey,)
match = inputurl.match(uri)
for key in argsgroups:
value = match.group(key)
@@ -159,6 +164,8 @@
kwargs[key] = transforms[key](value)
except KeyError:
kwargs[key] = value
+ if cachekey is not None and key in cachekey:
+ kwargs[key] = typed_eid(value)
if setuser:
kwargs['u'] = req.user.eid
rset = req.execute(rql, kwargs, cachekey)
--- a/web/views/wdoc.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/wdoc.py Fri Nov 20 19:35:54 2009 +0100
@@ -237,6 +237,17 @@
self.w(rest_publish(self, '\n'.join(restdata)))
+class HelpAction(action.Action):
+ id = 'help'
+ __select__ = yes()
+
+ category = 'footer'
+ order = 0
+ title = _('Help')
+
+ def url(self):
+ return self.req.build_url('doc/main')
+
class ChangeLogAction(action.Action):
__regid__ = 'changelog'
__select__ = yes()
--- a/web/views/workflow.py Sun Nov 08 21:53:18 2009 +0100
+++ b/web/views/workflow.py Fri Nov 20 19:35:54 2009 +0100
@@ -16,13 +16,14 @@
from cubicweb import Unauthorized, view
from cubicweb.selectors import (implements, has_related_entities, one_line_rset,
- relation_possible, match_form_params)
+ relation_possible, match_form_params,
+ entity_implements)
from cubicweb.interfaces import IWorkflowable
from cubicweb.view import EntityView
from cubicweb.schema import display_name
from cubicweb.web import uicfg, stdmsgs, action, component, form, action
from cubicweb.web import formfields as ff, formwidgets as fwdgs
-from cubicweb.web.views import TmpFileViewMixin, forms, primary
+from cubicweb.web.views import TmpFileViewMixin, forms, primary, autoform
_pvs = uicfg.primaryview_section
_pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
@@ -50,8 +51,8 @@
__regid__ = 'changestate'
form_renderer_id = 'base' # don't want EntityFormRenderer
- form_buttons = [fwdgs.SubmitButton(stdmsgs.YES),
- fwdgs.Button(stdmsgs.NO, cwaction='cancel')]
+ form_buttons = [fwdgs.SubmitButton(),
+ fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
class ChangeStateFormView(form.FormViewMixIn, view.EntityView):
@@ -169,6 +170,31 @@
# workflow entity types views ##################################################
+_pvs = uicfg.primaryview_section
+_pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
+_pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
+_pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
+
+_abaa = uicfg.actionbox_appearsin_addmenu
+_abaa.tag_subject_of(('BaseTransition', 'condition', 'RQLExpression'), False)
+_abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)
+_abaa.tag_object_of(('SubWorkflowExitPoint', 'destination_state', 'State'),
+ False)
+_abaa.tag_object_of(('State', 'state_of', 'Workflow'), True)
+_abaa.tag_object_of(('BaseTransition', 'transition_of', 'Workflow'), False)
+_abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
+_abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
+
+class WorkflowPrimaryView(primary.PrimaryView):
+ __select__ = implements('Workflow')
+
+ def render_entity_attributes(self, entity):
+ self.w(entity.view('reledit', rtype='description'))
+ self.w(u'<img src="%s" alt="%s"/>' % (
+ xml_escape(entity.absolute_url(vid='wfgraph')),
+ xml_escape(self.req._('graphical workflow for %s') % entity.name)))
+
+
class CellView(view.EntityView):
__regid__ = 'cell'
__select__ = implements('TrInfo')
@@ -187,14 +213,53 @@
row=row, col=col)))
-class WorkflowPrimaryView(primary.PrimaryView):
- __select__ = implements('Workflow')
+# workflow entity types edition ################################################
+
+_afs = uicfg.autoform_section
+_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'generated')
+_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'generated')
+_afs.tag_object_of(('State', 'allowed_transition', '*'), 'primary')
+_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'primary')
+
+def workflow_items_for_relation(req, wfeid, wfrelation, targetrelation):
+ wf = req.entity_from_eid(wfeid)
+ rschema = req.vreg.schema[targetrelation]
+ return sorted((e.view('combobox'), e.eid)
+ for e in getattr(wf, 'reverse_%s' % wfrelation)
+ if rschema.has_perm(req, 'add', toeid=e.eid))
+
+
+class TransitionEditionForm(autoform.AutomaticEntityForm):
+ __select__ = entity_implements('Transition')
- def render_entity_attributes(self, entity):
- self.w(entity.view('reledit', rtype='description'))
- self.w(u'<img src="%s" alt="%s"/>' % (
- xml_escape(entity.absolute_url(vid='wfgraph')),
- xml_escape(self._cw._('graphical workflow for %s') % entity.name)))
+ def workflow_states_for_relation(self, targetrelation):
+ eids = self.edited_entity.linked_to('transition_of', 'subject')
+ if eids:
+ return workflow_items_for_relation(self.req, eids[0], 'state_of',
+ targetrelation)
+ return []
+
+ def subject_destination_state_vocabulary(self, rtype, limit=None):
+ if not self.edited_entity.has_eid():
+ return self.workflow_states_for_relation('destination_state')
+ return self.subject_relation_vocabulary(rtype, limit)
+
+ def object_allowed_transition_vocabulary(self, rtype, limit=None):
+ if not self.edited_entity.has_eid():
+ return self.workflow_states_for_relation('allowed_transition')
+ return self.subject_relation_vocabulary(rtype, limit)
+
+
+class StateEditionForm(autoform.AutomaticEntityForm):
+ __select__ = entity_implements('State')
+
+ def subject_allowed_transition_vocabulary(self, rtype, limit=None):
+ if not self.edited_entity.has_eid():
+ eids = self.edited_entity.linked_to('state_of', 'subject')
+ if eids:
+ return workflow_items_for_relation(self.req, eids[0], 'transition_of',
+ 'allowed_transition')
+ return []
# workflow images ##############################################################