# HG changeset patch # User Sylvain Thénault # Date 1258742154 -3600 # Node ID d7a270f50f5493319a3bb246f95df28d7616b247 # Parent 5b75fd66c80edaf903a212a17fb19026dee61a55# Parent 8902b8745918465e2602d43e5d83dbf2ae258f2e backport stable branch (one more time painfully) diff -r 5b75fd66c80e -r d7a270f50f54 __pkginfo__.py --- 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''' diff -r 5b75fd66c80e -r d7a270f50f54 common/migration.py diff -r 5b75fd66c80e -r d7a270f50f54 common/mixins.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 common/tags.py --- 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: diff -r 5b75fd66c80e -r d7a270f50f54 dbapi.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 devtools/devctl.py --- 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') diff -r 5b75fd66c80e -r d7a270f50f54 doc/book/en/annexes/depends.rst --- 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. diff -r 5b75fd66c80e -r d7a270f50f54 doc/book/en/development/datamodel/definition.rst --- 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` diff -r 5b75fd66c80e -r d7a270f50f54 entities/authobjs.py diff -r 5b75fd66c80e -r d7a270f50f54 entities/test/unittest_base.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 entities/test/unittest_wfobjs.py --- 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() diff -r 5b75fd66c80e -r d7a270f50f54 entity.py --- 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: diff -r 5b75fd66c80e -r d7a270f50f54 etwist/server.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 etwist/twctl.py --- 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' diff -r 5b75fd66c80e -r d7a270f50f54 hooks/integrity.py --- 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, diff -r 5b75fd66c80e -r d7a270f50f54 hooks/security.py --- 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) - diff -r 5b75fd66c80e -r d7a270f50f54 hooks/syncschema.py --- 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 ############################################ diff -r 5b75fd66c80e -r d7a270f50f54 hooks/workflow.py --- 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'): diff -r 5b75fd66c80e -r d7a270f50f54 i18n/en.po --- 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" diff -r 5b75fd66c80e -r d7a270f50f54 i18n/es.po --- 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" diff -r 5b75fd66c80e -r d7a270f50f54 i18n/fr.po --- 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 "" diff -r 5b75fd66c80e -r d7a270f50f54 misc/migration/bootstrapmigration_repository.py --- 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') diff -r 5b75fd66c80e -r d7a270f50f54 misc/migration/postcreate.py --- 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}) diff -r 5b75fd66c80e -r d7a270f50f54 rqlrewrite.py --- 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: diff -r 5b75fd66c80e -r d7a270f50f54 rset.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 schema.py --- 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 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 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 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 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 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): diff -r 5b75fd66c80e -r d7a270f50f54 schemas/Bookmark.py --- 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"')), diff -r 5b75fd66c80e -r d7a270f50f54 schemas/base.py --- 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'),), diff -r 5b75fd66c80e -r d7a270f50f54 schemas/bootstrap.py --- 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',), diff -r 5b75fd66c80e -r d7a270f50f54 schemas/workflow.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 schemaviewer.py --- 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: diff -r 5b75fd66c80e -r d7a270f50f54 selectors.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 server/__init__.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 server/hookhelper.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 server/migractions.py --- 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() diff -r 5b75fd66c80e -r d7a270f50f54 server/msplanner.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 server/querier.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 server/repository.py --- 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: diff -r 5b75fd66c80e -r d7a270f50f54 server/rqlannotation.py --- 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 diff -r 5b75fd66c80e -r d7a270f50f54 server/schemaserial.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 server/serverconfig.py --- 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, diff -r 5b75fd66c80e -r d7a270f50f54 server/serverctl.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 server/sources/native.py --- 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, }), diff -r 5b75fd66c80e -r d7a270f50f54 server/sources/rql2sql.py --- 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': diff -r 5b75fd66c80e -r d7a270f50f54 server/test/data/migratedapp/schema.py --- 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'), diff -r 5b75fd66c80e -r d7a270f50f54 server/test/data/schema.py --- 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',)} diff -r 5b75fd66c80e -r d7a270f50f54 server/test/unittest_ldapuser.py --- 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]]) diff -r 5b75fd66c80e -r d7a270f50f54 server/test/unittest_msplanner.py --- 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'}, []), diff -r 5b75fd66c80e -r d7a270f50f54 server/test/unittest_multisources.py diff -r 5b75fd66c80e -r d7a270f50f54 server/test/unittest_rql2sql.py --- 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() diff -r 5b75fd66c80e -r d7a270f50f54 server/test/unittest_security.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 sobjects/notification.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 sobjects/textparsers.py --- /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 + + :: #? + """ + 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) diff -r 5b75fd66c80e -r d7a270f50f54 test/data/erqlexpr_on_ertype.py --- 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',), diff -r 5b75fd66c80e -r d7a270f50f54 test/data/rewrite/schema.py --- 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')), diff -r 5b75fd66c80e -r d7a270f50f54 test/data/rqlexpr_on_ertype_read.py --- 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',), diff -r 5b75fd66c80e -r d7a270f50f54 test/data/rrqlexpr_on_attr.py --- 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',), diff -r 5b75fd66c80e -r d7a270f50f54 test/data/rrqlexpr_on_eetype.py --- 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',), diff -r 5b75fd66c80e -r d7a270f50f54 test/unittest_entity.py --- 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') diff -r 5b75fd66c80e -r d7a270f50f54 test/unittest_rqlrewrite.py --- 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() diff -r 5b75fd66c80e -r d7a270f50f54 test/unittest_schema.py --- 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() diff -r 5b75fd66c80e -r d7a270f50f54 utils.py --- 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'\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: diff -r 5b75fd66c80e -r d7a270f50f54 view.py --- 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'\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'
') + if table: + w(u'') + else: + w(u'
') if show_label and label: if tr: label = display_name(self._cw, label) - w(u'%s' % label) - w(u'
%s
' % value) - if row: - w(u'
') + if table: + w(u'%s' % label) + else: + w(u'%s ' % label) + if table: + w(u'%s' % value) + else: + w(u'%s
' % value) diff -r 5b75fd66c80e -r d7a270f50f54 web/__init__.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 web/component.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 web/data/cancel.png Binary file web/data/cancel.png has changed diff -r 5b75fd66c80e -r d7a270f50f54 web/data/cubicweb.ajax.js --- 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'; + // 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>> 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 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'
' % real_pourcent) - self.w(u'
' % (color, pourcent)) + if percent < 0: + percent = 0 + self.w(u'
' % real_percent) + self.w(u'
' % (color, percent)) self.w(u'
') diff -r 5b75fd66c80e -r d7a270f50f54 web/request.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 web/test/unittest_application.py diff -r 5b75fd66c80e -r d7a270f50f54 web/uicfg.py --- 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', diff -r 5b75fd66c80e -r d7a270f50f54 web/views/actions.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 web/views/autoform.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 web/views/basecomponents.py --- 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'
') -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'%s' % - (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'%s' % + (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,)) diff -r 5b75fd66c80e -r d7a270f50f54 web/views/basecontrollers.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 web/views/basetemplates.py --- 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'\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'') - 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'') # lastcolumn self.w(u'') @@ -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' diff -r 5b75fd66c80e -r d7a270f50f54 web/views/baseviews.py --- 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'\n') + w(u'%s' % footer) + w(u'\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('
%s
' % msg) self.w(u'') diff -r 5b75fd66c80e -r d7a270f50f54 web/views/owl.py --- 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''% eschema) self.w(u'') 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'') 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''' @@ -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''' @@ -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''' @@ -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 diff -r 5b75fd66c80e -r d7a270f50f54 web/views/plots.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 web/views/primary.py --- 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'

%s %s

' - % (entity.dc_type().capitalize(), title)) + self.w(u'

%s

' % 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'') + 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'
') 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'') - 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'
') self.wview('simplelist', self.cw_rset) self.w(u'[%s]' % (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') diff -r 5b75fd66c80e -r d7a270f50f54 web/views/schema.py --- 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): diff -r 5b75fd66c80e -r d7a270f50f54 web/views/startup.py --- 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') diff -r 5b75fd66c80e -r d7a270f50f54 web/views/tableview.py --- 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 = '%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) diff -r 5b75fd66c80e -r d7a270f50f54 web/views/tabs.py --- 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 - diff -r 5b75fd66c80e -r d7a270f50f54 web/views/timetable.py --- 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'' % 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'') self.w(u'\n') return columns diff -r 5b75fd66c80e -r d7a270f50f54 web/views/treeview.py --- 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'') diff -r 5b75fd66c80e -r d7a270f50f54 web/views/urlrewrite.py --- 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) diff -r 5b75fd66c80e -r d7a270f50f54 web/views/wdoc.py --- 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() diff -r 5b75fd66c80e -r d7a270f50f54 web/views/workflow.py --- 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'%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'%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 ##############################################################