# HG changeset patch # User Sylvain Thénault # Date 1246902918 -7200 # Node ID 7ded2a1416e4985e3fea18ad25cefb076b588bd4 # Parent d5987f75c97c1d8722ed3b87668425bafbbbdadd# Parent 8240a7d0d52a1eb11edd6f62181fc0770193ef66 backport stable branch diff -r d5987f75c97c -r 7ded2a1416e4 .hgtags --- a/.hgtags Thu Jul 02 10:36:25 2009 +0200 +++ b/.hgtags Mon Jul 06 19:55:18 2009 +0200 @@ -43,3 +43,8 @@ a356da3e725bfcb59d8b48a89d04be05ea261fd3 3.3.1 e3aeb6e6c3bb5c18e8dcf61bae9d654beda6c036 cubicweb-version-3_3_2 bef5e74e53f9de8220451dca4b5863a24a0216fb cubicweb-debian-version-3_3_2-1 +1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3 +81973c897c9e78e5e52643e03628654916473196 cubicweb-debian-version-3_3_3-1 +1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3 +47b5236774a0cf3b1cfe75f6d4bd2ec989644ace cubicweb-version-3_3_3 +2ba27ce8ecd9828693ec53c517e1c8810cbbe33e cubicweb-debian-version-3_3_3-2 diff -r d5987f75c97c -r 7ded2a1416e4 __pkginfo__.py --- a/__pkginfo__.py Thu Jul 02 10:36:25 2009 +0200 +++ b/__pkginfo__.py Mon Jul 06 19:55:18 2009 +0200 @@ -7,7 +7,7 @@ distname = "cubicweb" modname = "cubicweb" -numversion = (3, 3, 2) +numversion = (3, 3, 3) version = '.'.join(str(num) for num in numversion) license = 'LGPL v2' diff -r d5987f75c97c -r 7ded2a1416e4 common/migration.py --- a/common/migration.py Thu Jul 02 10:36:25 2009 +0200 +++ b/common/migration.py Mon Jul 06 19:55:18 2009 +0200 @@ -20,24 +20,6 @@ from cubicweb import ConfigurationError -def migration_files(config, toupgrade): - """return an orderer list of path of scripts to execute to upgrade - an installed application according to installed cube and cubicweb versions - """ - merged = [] - for cube, fromversion, toversion in toupgrade: - if cube == 'cubicweb': - migrdir = config.migration_scripts_dir() - else: - migrdir = config.cube_migration_scripts_dir(cube) - scripts = filter_scripts(config, migrdir, fromversion, toversion) - merged += [s[1] for s in scripts] - if config.accept_mode('Any'): - migrdir = config.migration_scripts_dir() - merged.insert(0, join(migrdir, 'bootstrapmigration_repository.py')) - return merged - - def filter_scripts(config, directory, fromversion, toversion, quiet=True): """return a list of paths of migration files to consider to upgrade from a version to a greater one @@ -126,6 +108,18 @@ 'interactive_mode': interactive, } + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + cmd = 'cmd_%s' % name + if hasattr(self, cmd): + meth = getattr(self, cmd) + return lambda *args, **kwargs: self.interact(args, kwargs, + meth=meth) + raise + raise AttributeError(name) + def repo_connect(self): return self.config.repository() @@ -144,31 +138,35 @@ return False return orig_accept_mode(mode) self.config.accept_mode = accept_mode - scripts = migration_files(self.config, toupgrade) - if scripts: - vmap = dict( (pname, (fromver, tover)) for pname, fromver, tover in toupgrade) - self.__context.update({'applcubicwebversion': vcconf['cubicweb'], - 'cubicwebversion': self.config.cubicweb_version(), - 'versions_map': vmap}) - self.scripts_session(scripts) - else: - print 'no migration script to execute' + # may be an iterator + toupgrade = tuple(toupgrade) + vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade) + ctx = self.__context + ctx['versions_map'] = vmap + if self.config.accept_mode('Any') and 'cubicweb' in vmap: + migrdir = self.config.migration_scripts_dir() + self.process_script(join(migrdir, 'bootstrapmigration_repository.py')) + for cube, fromversion, toversion in toupgrade: + if cube == 'cubicweb': + migrdir = self.config.migration_scripts_dir() + else: + migrdir = self.config.cube_migration_scripts_dir(cube) + scripts = filter_scripts(self.config, migrdir, fromversion, toversion) + if scripts: + for version, script in scripts: + self.process_script(script) + self.cube_upgraded(cube, version) + if version != toversion: + self.cube_upgraded(cube, toversion) + else: + self.cube_upgraded(cube, toversion) + + def cube_upgraded(self, cube, version): + pass def shutdown(self): pass - def __getattribute__(self, name): - try: - return object.__getattribute__(self, name) - except AttributeError: - cmd = 'cmd_%s' % name - if hasattr(self, cmd): - meth = getattr(self, cmd) - return lambda *args, **kwargs: self.interact(args, kwargs, - meth=meth) - raise - raise AttributeError(name) - def interact(self, args, kwargs, meth): """execute the given method according to user's confirmation""" msg = 'execute command: %s(%s) ?' % ( @@ -205,7 +203,6 @@ if answer in ('r', 'retry'): return 2 if answer in ('a', 'abort'): - self.rollback() raise SystemExit(1) if shell and answer in ('s', 'shell'): self.interactive_shell() @@ -284,16 +281,6 @@ return None return func(*args, **kwargs) - def scripts_session(self, migrscripts): - """execute some scripts in a transaction""" - try: - for migrscript in migrscripts: - self.process_script(migrscript) - self.commit() - except: - self.rollback() - raise - def cmd_option_renamed(self, oldname, newname): """a configuration option has been renamed""" self._option_changes.append(('renamed', oldname, newname)) diff -r d5987f75c97c -r 7ded2a1416e4 common/test/unittest_migration.py --- a/common/test/unittest_migration.py Thu Jul 02 10:36:25 2009 +0200 +++ b/common/test/unittest_migration.py Mon Jul 06 19:55:18 2009 +0200 @@ -13,7 +13,8 @@ from cubicweb.devtools.apptest import TestEnvironment from cubicweb.cwconfig import CubicWebConfiguration -from cubicweb.common.migration import migration_files, filter_scripts +from cubicweb.common.migration import MigrationHelper, filter_scripts +from cubicweb.server.migractions import ServerMigrationHelper class Schema(dict): @@ -39,82 +40,53 @@ self.config.__class__.cubicweb_vobject_path = frozenset() self.config.__class__.cube_vobject_path = frozenset() - def test_migration_files_base(self): - self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)), - ('TEMPLATE', (0,0,2), (0,0,3))]), - [SMIGRDIR+'bootstrapmigration_repository.py', - TMIGRDIR+'0.0.3_Any.py']) - self.assertListEquals(migration_files(self.config, [('cubicweb', (2,4,0), (2,5,0)), - ('TEMPLATE', (0,0,2), (0,0,3))]), - [SMIGRDIR+'bootstrapmigration_repository.py', - SMIGRDIR+'2.5.0_Any.sql', - TMIGRDIR+'0.0.3_Any.py']) - self.assertListEquals(migration_files(self.config, [('cubicweb', (2,5,0), (2,6,0)), - ('TEMPLATE', (0,0,3), (0,0,4))]), - [SMIGRDIR+'bootstrapmigration_repository.py', - SMIGRDIR+'2.6.0_Any.sql', - TMIGRDIR+'0.0.4_Any.py']) + def test_filter_scripts_base(self): + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)), + []) + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)), + [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')]) + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)), + [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')]) + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)), + [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'), + ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')]) + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)), + []) + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)), + [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'), + ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')]) + self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)), + [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')]) -## def test_migration_files_overlap(self): -## self.assertListEquals(migration_files(self.config, (2,4,0), (2,10,2), -## (0,0,2), (0,1,2)), -## [SMIGRDIR+'bootstrapmigration_repository.py', -## TMIGRDIR+'0.0.3_Any.py', -## TMIGRDIR+'0.0.4_Any.py', -## SMIGRDIR+'2.4.0_2.5.0_Any.sql', -## SMIGRDIR+'2.5.1_2.6.0_Any.sql', -## TMIGRDIR+'0.1.0_Any.py', -## TMIGRDIR+'0.1.0_common.py', -## TMIGRDIR+'0.1.0_repository.py', -## TMIGRDIR+'0.1.2_Any.py', -## SMIGRDIR+'2.10.1_2.10.2_Any.sql']) + self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)), + [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')]) + self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)), + [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'), + ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')]) - def test_migration_files_for_mode(self): - from cubicweb.server.migractions import ServerMigrationHelper + def test_filter_scripts_for_mode(self): self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper) - from cubicweb.common.migration import MigrationHelper config = CubicWebConfiguration('data') config.verbosity = 0 self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper)) self.assertIsInstance(config.migration_handler(), MigrationHelper) config = self.config config.__class__.name = 'twisted' - self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]), - [TMIGRDIR+'0.1.0_common.py', - TMIGRDIR+'0.1.0_web.py']) - config.__class__.name = 'repository' - self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]), - [SMIGRDIR+'bootstrapmigration_repository.py', - TMIGRDIR+'0.1.0_Any.py', - TMIGRDIR+'0.1.0_common.py', - TMIGRDIR+'0.1.0_repository.py']) - config.__class__.name = 'all-in-one' - self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]), - [SMIGRDIR+'bootstrapmigration_repository.py', - TMIGRDIR+'0.1.0_Any.py', - TMIGRDIR+'0.1.0_common.py', - TMIGRDIR+'0.1.0_repository.py', - TMIGRDIR+'0.1.0_web.py']) + self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)), + [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'), + ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')]) config.__class__.name = 'repository' - - def test_filter_scripts(self): - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)), - [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')]) - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)), - [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'), - ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')]) - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)), - []) - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)), - [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')]) - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)), - [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'), - ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')]) - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)), - [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')]) - self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,10,2)), - [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'), - ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')]) + self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)), + [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'), + ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'), + ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')]) + config.__class__.name = 'all-in-one' + self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)), + [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'), + ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'), + ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'), + ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')]) + config.__class__.name = 'repository' from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite diff -r d5987f75c97c -r 7ded2a1416e4 cwconfig.py --- a/cwconfig.py Thu Jul 02 10:36:25 2009 +0200 +++ b/cwconfig.py Mon Jul 06 19:55:18 2009 +0200 @@ -194,6 +194,12 @@ 'help': 'web server root url', 'group': 'main', 'inputlevel': 1, }), + ('allow-email-login', + {'type' : 'yn', + 'default': False, + 'help': 'allow users to login with their primary email if set', + 'group': 'main', 'inputlevel': 2, + }), ('use-request-subdomain', {'type' : 'yn', 'default': None, diff -r d5987f75c97c -r 7ded2a1416e4 cwctl.py --- a/cwctl.py Thu Jul 02 10:36:25 2009 +0200 +++ b/cwctl.py Mon Jul 06 19:55:18 2009 +0200 @@ -655,7 +655,7 @@ else: applcubicwebversion = vcconf.get('cubicweb') if cubicwebversion > applcubicwebversion: - toupgrade.append( ('cubicweb', applcubicwebversion, cubicwebversion) ) + toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion)) if not self.config.fs_only and not toupgrade: print 'no software migration needed for application %s' % appid return @@ -682,7 +682,6 @@ 'continue anyway ?'): print 'migration not completed' return - mih.rewrite_vcconfiguration() mih.shutdown() print print 'application migrated' @@ -733,7 +732,8 @@ config.set_sources_mode(sources) mih = config.migration_handler() if args: - mih.scripts_session(args) + for arg in args: + mih.process_script(script) else: mih.interactive_shell() mih.shutdown() diff -r d5987f75c97c -r 7ded2a1416e4 cwvreg.py --- a/cwvreg.py Thu Jul 02 10:36:25 2009 +0200 +++ b/cwvreg.py Mon Jul 06 19:55:18 2009 +0200 @@ -96,6 +96,11 @@ clear_cache(self, 'rqlhelper') # now we can load application's web objects self.register_objects(self.config.vregistry_path()) + # map lowered entity type names to their actual name + self.case_insensitive_etypes = {} + for etype in self.schema.entities(): + etype = str(etype) + self.case_insensitive_etypes[etype.lower()] = etype def update_schema(self, schema): """update .schema attribute on registered objects, necessary for some diff -r d5987f75c97c -r 7ded2a1416e4 dbapi.py --- a/dbapi.py Thu Jul 02 10:36:25 2009 +0200 +++ b/dbapi.py Mon Jul 06 19:55:18 2009 +0200 @@ -63,19 +63,19 @@ 'application' % nsid) return core.getProxyForURI(uri) -def repo_connect(repo, user, password, cnxprops=None): +def repo_connect(repo, login, password, cnxprops=None): """Constructor to create a new connection to the CubicWeb repository. Returns a Connection instance. """ cnxprops = cnxprops or ConnectionProperties('inmemory') - cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops) + cnxid = repo.connect(unicode(login), password, cnxprops=cnxprops) cnx = Connection(repo, cnxid, cnxprops) if cnxprops.cnxtype == 'inmemory': cnx.vreg = repo.vreg return cnx -def connect(database=None, user=None, password=None, host=None, +def connect(database=None, login=None, password=None, host=None, group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True, initlog=True): """Constructor for creating a connection to the CubicWeb repository. @@ -111,11 +111,11 @@ vreg.set_schema(schema) else: vreg = None - cnx = repo_connect(repo, user, password, cnxprops) + cnx = repo_connect(repo, login, password, cnxprops) cnx.vreg = vreg return cnx -def in_memory_cnx(config, user, password): +def in_memory_cnx(config, login, password): """usefull method for testing and scripting to get a dbapi.Connection object connected to an in-memory repository instance """ @@ -128,7 +128,7 @@ repo = get_repository('inmemory', config=config, vreg=vreg) # connection to the CubicWeb repository cnxprops = ConnectionProperties('inmemory') - cnx = repo_connect(repo, user, password, cnxprops=cnxprops) + cnx = repo_connect(repo, login, password, cnxprops=cnxprops) return repo, cnx @@ -245,7 +245,7 @@ @property def user(self): if self._user is None and self.cnx: - self.set_user(self.cnx.user(self)) + self.set_user(self.cnx.user(self, {'lang': self.lang})) return self._user def set_user(self, user): @@ -367,6 +367,10 @@ """raise `BadSessionId` if the connection is no more valid""" self._repo.check_session(self.sessionid) + def set_session_props(self, **props): + """raise `BadSessionId` if the connection is no more valid""" + self._repo.set_session_props(self.sessionid, props) + def get_shared_data(self, key, default=None, pop=False): """return value associated to `key` in shared data""" return self._repo.get_shared_data(self.sessionid, key, default, pop) @@ -434,7 +438,8 @@ def user(self, req=None, props=None): """return the User object associated to this connection""" # cnx validity is checked by the call to .user_info - eid, login, groups, properties = self._repo.user_info(self.sessionid, props) + eid, login, groups, properties = self._repo.user_info(self.sessionid, + props) if req is None: req = self.request() rset = req.eid_rset(eid, 'CWUser') diff -r d5987f75c97c -r 7ded2a1416e4 debian/changelog --- a/debian/changelog Thu Jul 02 10:36:25 2009 +0200 +++ b/debian/changelog Mon Jul 06 19:55:18 2009 +0200 @@ -1,3 +1,16 @@ +cubicweb (3.3.3-2) unstable; urgency=low + + * re-release with "from __future__ import with_statement" commented out to + avoid broken installation if 2.4 is installed + + -- Sylvain Thénault Mon, 06 Jul 2009 17:33:15 +0200 + +cubicweb (3.3.3-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Mon, 06 Jul 2009 13:24:29 +0200 + cubicweb (3.3.2-1) unstable; urgency=low * new upstream release diff -r d5987f75c97c -r 7ded2a1416e4 devtools/apptest.py --- a/devtools/apptest.py Thu Jul 02 10:36:25 2009 +0200 +++ b/devtools/apptest.py Mon Jul 06 19:55:18 2009 +0200 @@ -462,14 +462,13 @@ self.__close = repo.close self.cnxid = self.cnx.sessionid self.session = repo._sessions[self.cnxid] - # XXX copy schema since hooks may alter it and it may be not fully - # cleaned (missing some schema synchronization support) - try: - origschema = repo.__schema - except AttributeError: - origschema = repo.schema - repo.__schema = origschema if self.copy_schema: + # XXX copy schema since hooks may alter it and it may be not fully + # cleaned (missing some schema synchronization support) + try: + origschema = repo.__schema + except AttributeError: + repo.__schema = origschema = repo.schema repo.schema = deepcopy(origschema) repo.set_schema(repo.schema) # reset hooks repo.vreg.update_schema(repo.schema) diff -r d5987f75c97c -r 7ded2a1416e4 doc/book/en/admin/setup.rst --- a/doc/book/en/admin/setup.rst Thu Jul 02 10:36:25 2009 +0200 +++ b/doc/book/en/admin/setup.rst Mon Jul 06 19:55:18 2009 +0200 @@ -46,6 +46,13 @@ There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball. +The repositories are signed with `Logilab's gnupg key`_. To avoid warning on "apt-get update": +1. become root using sudo +2. download http://ftp.logilab.org/dists/logilab-dists-key.asc using e.g. wget +3. run "apt-key add logilab-dists-key.asc" +4. re-run apt-get update (manually or through the package manager, whichever you prefer) + +.. `Logilab's gnupg key` _http://ftp.logilab.org/dists/logilab-dists-key.asc Install from source ``````````````````` diff -r d5987f75c97c -r 7ded2a1416e4 doc/book/en/development/cubes/cc-newcube.rst --- a/doc/book/en/development/cubes/cc-newcube.rst Thu Jul 02 10:36:25 2009 +0200 +++ b/doc/book/en/development/cubes/cc-newcube.rst Mon Jul 06 19:55:18 2009 +0200 @@ -13,16 +13,15 @@ hg add . hg ci -If all went well, you should see the cube you just create in the list -returned by `cubicweb-ctl list` in the section *Available components*, +If all went well, you should see the cube you just created in the list +returned by ``cubicweb-ctl list`` in the section *Available components*, and if it is not the case please refer to :ref:`ConfigurationEnv`. -To use a cube, you have to list it in the variable ``__use__`` -of the file ``__pkginfo__.py`` of the instance. -This variable is used for the instance packaging (dependencies -handled by system utility tools such as APT) and the usable cubes -at the time the base is created (import_erschema('MyCube') will -not properly work otherwise). +To reuse an existing cube, add it to the list named ``__use__`` and defined in +:file:`__pkginfo__.py`. This variable is used for the instance packaging +(dependencies handled by system utility tools such as APT) and the usable cubes +at the time the base is created (import_erschema('MyCube') will not properly +work otherwise). .. note:: Please note that if you do not wish to use default directory diff -r d5987f75c97c -r 7ded2a1416e4 doc/book/en/intro/concepts/index.rst --- a/doc/book/en/intro/concepts/index.rst Thu Jul 02 10:36:25 2009 +0200 +++ b/doc/book/en/intro/concepts/index.rst Mon Jul 06 19:55:18 2009 +0200 @@ -1,5 +1,7 @@ .. -*- coding: utf-8 -*- +.. _Concepts: + The Core Concepts of CubicWeb ============================= @@ -13,8 +15,9 @@ Cubes ----- -A cube is a software component composed of three parts: its data model (schema), -its logic (entities) and its user interface (views). +A cube is a software component made of three parts: its data model +(:file:`schema`), its logic (:file:`entities`) and its user interface +(:file:`views`). A cube can use other cubes as building blocks and assemble them to provide a whole with richer functionnalities than its parts. The cubes `cubicweb-blog`_ @@ -24,12 +27,14 @@ The `CubicWeb Forge`_ offers a large number of cubes developed by the community and available under a free software license. -Available cubes on your system are usually stored in the directory -:file:`/usr/share/cubicweb/cubes` when using a unix system wide -installation. During development, the cubes are found in the -:file:`/path/to/cubicweb_forest/cubes` directory. You can specify additional -locations using the :envvar:`CW_CUBES_PATH` environment variable, using ':' as a -separator. +The command ``cubicweb-ctl list`` displays the list of cubes installed on your +system. + +On a Unix system, the available cubes are usually stored in the directory +:file:`/usr/share/cubicweb/cubes`. During development, the cubes are commonly +found in the directory :file:`/path/to/cubicweb_forest/cubes`. The environment +variable :envvar:`CW_CUBES_PATH` gives additionnal locations where to search for +cubes. .. _`CubicWeb Forge`: http://www.cubicweb.org/project/ .. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog @@ -37,124 +42,113 @@ Instances ----------- +--------- An instance is a runnable application installed on a computer and based on a cube. -The instance directory includes the configuration files. Several instances can -be created based on the same cube. For exemple, several software forges can be -set up on one computer system based on the `cubicweb-forge`_ cube. +The instance directory contains the configuration files. Several instances can +be created and based on the same cube. For exemple, several software forges can +be set up on one computer system based on the `cubicweb-forge`_ cube. .. _`cubicweb-forge`: http://www.cubicweb.org/project/cubicweb-forge -Instances can be of different types: all-in-one, web engine or data repository. For -applications that support high traffic, several web (front-end) and data -(back-end) instances can be set-up to share the load. +Instances can be of three different types: all-in-one, web engine or data +repository. For applications that support high traffic, several web (front-end) +and data (back-end) instances can be set-up to share the load. .. image:: ../../images/archi_globale.en.png +The command ``cubicweb-ctl list`` displays the list of instances installed on +your system. + +On a Unix system, the instances are usually stored in the directory +:file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/` +directory is looked up, as well as the paths in :envvar:`CW_REGISTRY` +environment variable. + The term application can refer to an instance or to a cube, depending on the context. This book will try to avoid using this term and use *cube* and *instance* as appropriate. -(Data) Repository -~~~~~~~~~~~~~~~~~~ +Data Repository +--------------- -The repository (Be carefull not to get confused with a Mercurial repository or a -debian repository!) manages all interactions with various data sources by -providing access to them using uniformly using the Relation Query Language (RQL). The -web interface and the repository communicate using this language. +The data repository [#]_ provides access to one or more data sources (including +SQL databases, LDAP repositories, Mercurial or Subversion version control +systems, other CubicWeb repositories, GAE's DataStore, etc). -Usually, the web server and repository sides are integrated in the same process and -interact directly, without the need for distant calls using Pyro. But, it is -important to note that those two sides, client/server, are disjointed and it is -possible to execute a couple of calls in distinct processes to balance the load -of your web site on one or more machines. - +All interactions with the repository are done using the Relation Query Language +(RQL). The repository federates the data sources and hides them from the +querier, which does not realize when a query spans accross several data sources +and requires running sub-queries and merges to complete. -A data source is a container of data integrated in the *CubicWeb* repository. A -repository has at least one source, named `system`, which contains the schema of -the application, plain-text index and other vital informations for the -system. You'll find source for SQL databases, LDAP servers, other RQL -repositories and even mercurial /svn repositories or `Google App Engine`'s -datastore. +It is common to run the web engine and the repository in the same process (see +instances of type all-in-one above), but this is not a requirement. A repository +can be set up to be accessed remotely using Pyro (`Python Remote Objects`_) and +act as a server. -Web interface -~~~~~~~~~~~~~ -By default the web server provides a generated interface based on its schema. -Entities can be created, displayed, updated and deleted. As display views are not -very fancy, it is usually necessary to develop your own. +Some logic can be attached to events that happen in the repository, like +creation of entities, deletion of relations, etc. This is used for example to +send email notifications when the state of an object changes. See `Hooks` below. + +.. _[#]: not to be confused with a Mercurial repository or a Debian repository. +.. _`Python Remote Objects`: http://pyro.sourceforge.net/ -Instances are defined on your system in the directory :file:`/etc/cubicweb.d` when -using a system wide installation. For people using the mercurial repository of -cubicweb, the :file:`etc` directory is searched in the user home directory. You can -also specify an alternative directory using the :envvar:`CW_REGISTRY` environment -variable. - - +Web Engine +---------- -Schema ------- -** *CubicWeb* is schema driven ** +The web engine replies to http requests and runs the user interface and most of +the application logic. -The schema describes the persistent data model using entities and -relations. It is modeled with a comprehensive language made of Python classes based on -the `yams`_ library. +By default the web engine provides a generated user interface based on the data +model of the instance. Entities can be created, displayed, updated and +deleted. As the default user interface is not very fancy, it is usually +necessary to develop your own. -When you create a new cubicweb instance, the schema is stored in the database, -and it will usually evolves as you upgrade cubicweb and used cubes. +Schema (Data Model) +------------------- -*CubicWeb* provides a certain number of system entities included -sytematically (necessary for the core of *CubicWeb*, notably the schema itself). -You will also find a library of cubes which defines more piece of schema for standard needs. -necessary. +The data model of a cube is described as an entity-relationship schema using a +comprehensive language made of Python classes imported from the yams_ library. -*CubicWeb* add some metadata to every entity type, such as the eid (a global - identifier, unique into an instance), entity's creation date... - +.. _yams: http://www.logilab.org/project/yams/ -Attributes may be of the following types: - `String`, `Int`, `Float`, `Boolean`, `Date`, `Time`, `Datetime`, - `Interval`, `Password`, `Bytes`. - -New in 3.2: RichString +An `entity type` defines a set of attributes and is used in some relations. +Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`, +`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. See +:ref:`yams.BASE_TYPES` for details. -see :ref:`yams.BASE_TYPES` - -Data level security is defined by setting permissions on entity and relation types. - -A schema consist of parts detailed below. +A `relation type` is used to define a binary oriented relation between two +entity types. The left-hand part of a relation is named the `subject` and the +right-hand part is named the `object`. +A `relation definition` is a triple (*subject entity type*, *relation type*, *object +entity type*) associated with a set of properties such as cardinality, +constraints, etc. -Entity type -~~~~~~~~~~~ -An *entity type* defines set of attributes and is used in some relations. It may -have some permissions telling who can read/add/update/delete entities of this type. - -Relation type -~~~~~~~~~~~~~ -A *relation type* is used to define a semantic relation between two entity types. -It may have some permissions telling who can read/add/delete relation of this type. +Permissions can be set on entity types and relation types to control who will be +able to create, read, update or delete entities and relations. -In *CubicWeb* relations are ordered and binary: by convention we name the first -item of a relation the `subject` and the second the `object`. +Some meta-data necessary to the system is added to the data model. That includes +entities like users and groups, the entities used to store the data model +itself and attributes like unique identifier, creation date, creator, etc. -Relation definition -~~~~~~~~~~~~~~~~~~~ -A *relation definition* is a 3-uple (*subject entity type*, *relation type*, *object -entity type*), with an associated set of property such as cardinality, constraints... +When you create a new *CubicWeb* instance, the schema is stored in the database. +When the cubes the instance is based on evolve, they may change their data model +and provide migration scripts that will be executed when the administrator will +run the upgrade process for the instance. - +Registries and Objects +---------------------- -Dynamic objects for reusable components ---------------------------------------- -** Dynamic objects management or how CubicWeb provides really reusable components ** +XXX registry, register, registries, registers ??? Application objects ~~~~~~~~~~~~~~~~~~~ + Beside a few core functionalities, almost every feature of the framework is -acheived by dynamic objects (`application objects` or `appobjects`) stored in a +achieved by dynamic objects (`application objects` or `appobjects`) stored in a two-levels registry (the `vregistry`). Each object is affected to a registry with an identifier in this registry. You may have more than one object sharing an identifier in the same registry, At runtime, appobjects are selected in the @@ -168,6 +162,7 @@ The `vregistry` ~~~~~~~~~~~~~~~ + At startup, the `registry` or registers base, inspects a number of directories looking for compatible classes definition. After a recording process, the objects are assigned to registers so that they can be selected dynamically while the @@ -175,23 +170,22 @@ Selectors ~~~~~~~~~ -Each appobject has a selector, which is used to score how well it suits to a -given context by returning a score. A score of 0 means the object doesn't apply -to the context. The score is used to choose the most pertinent object: the "more" -the appobject suits the context the higher the score. + +Each appobject has a selector, that is used to compute how well the object fits +a given context. The better the object fits the context, the higher the score. -CubicWeb provides a set of basic selectors which may be parametrized and combined -using binary `&` and `|` operators to provide a custom selector (which can be -itself reused...). +CubicWeb provides a set of basic selectors that may be parametrized. Selectors +can be combined with the binary operators `&` and `|` to build more complex +selector that can be combined too. -There is 3 current ways to retreive some appobject from the repository: +There are three common ways to retrieve some appobject from the repository: * get the most appropriate objects by specifying a registry and an identifier. In that case, the object with the greatest score is selected. There should always be a single appobject with a greater score than others. * get all appobjects applying to a context by specifying a registry.In - that case, every objects with the a postive score are selected. + that case, every object with the a postive score is selected. * get the object within a particular registry/identifier. In that case no selection process is involved, the vregistry will expect to find a single @@ -218,9 +212,10 @@ The RQL query language ---------------------- -**No needs for a complicated ORM when you've a powerful query language** -All the persistant data in a CubicWeb application is retreived and modified by using the +**No need for a complicated ORM when you have a powerful query language** + +All the persistant data in a CubicWeb application is retrieved and modified by using the Relation Query Language. This query language is inspired by SQL but is on a higher level in order to @@ -228,16 +223,19 @@ db-api ~~~~~~ + The repository exposes a `db-api`_ like api but using the RQL instead of SQL. XXX feed me Result set ~~~~~~~~~~ + XXX feed me Views ----- + ** *CubicWeb* is data driven ** XXX feed me. @@ -248,7 +246,3 @@ ** *CubicWeb* provides an extensible data repository ** XXX feed me. - - -.. _`Python Remote Object`: http://pyro.sourceforge.net/ -.. _`yams`: http://www.logilab.org/project/yams/ diff -r d5987f75c97c -r 7ded2a1416e4 doc/book/en/intro/tutorial/components.rst --- a/doc/book/en/intro/tutorial/components.rst Thu Jul 02 10:36:25 2009 +0200 +++ b/doc/book/en/intro/tutorial/components.rst Mon Jul 06 19:55:18 2009 +0200 @@ -23,6 +23,8 @@ * classtags: Tag (to tag anything) +* comment: Comment (to attach comment threads to entities) + * file: File (to allow users to upload and store binary or text files) * link: Link (to collect links to web resources) @@ -37,10 +39,6 @@ * zone: Zone (to define places within larger places, for example a city in a state in a country) -The available system entities are: - -* comment: Comment (to attach comment threads to entities) - Adding comments to BlogDemo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -r d5987f75c97c -r 7ded2a1416e4 i18n/en.po --- a/i18n/en.po Thu Jul 02 10:36:25 2009 +0200 +++ b/i18n/en.po Mon Jul 06 19:55:18 2009 +0200 @@ -154,6 +154,12 @@ msgid "1?" msgstr "1 0..1" +#, python-format +msgid "" +"
This schema of the data model excludes the meta-data, but you " +"can also display a complete schema with meta-data.
" +msgstr "" + msgid "?*" msgstr "0..1 0..n" @@ -169,12 +175,18 @@ msgid "AND" msgstr "" +msgid "Add permissions" +msgstr "" + msgid "Any" msgstr "" msgid "Application" msgstr "" +msgid "Attributes" +msgstr "" + msgid "Bookmark" msgstr "Bookmark" @@ -284,6 +296,9 @@ msgid "Decimal_plural" msgstr "Decimal numbers" +msgid "Delete permissions" +msgstr "" + msgid "Do you want to delete the following element(s) ?" msgstr "" @@ -395,6 +410,9 @@ msgid "RQLExpression_plural" msgstr "RQL expressions" +msgid "Read permissions" +msgstr "" + msgid "Recipients:" msgstr "" @@ -408,6 +426,9 @@ msgid "Schema %s" msgstr "" +msgid "Schema of the data model" +msgstr "" + msgid "Search for" msgstr "" @@ -449,6 +470,9 @@ msgid "The view %s could not be found" msgstr "" +msgid "There is no workflow defined for this entity." +msgstr "" + #, python-format msgid "This %s" msgstr "" @@ -526,6 +550,9 @@ msgid "Unable to find anything named \"%s\" in the schema !" msgstr "" +msgid "Update permissions" +msgstr "" + msgid "Used by:" msgstr "" @@ -981,6 +1008,9 @@ msgid "bookmarks" msgstr "" +msgid "bookmarks are used to have user's specific internal links" +msgstr "" + msgid "boxes" msgstr "" @@ -1111,6 +1141,9 @@ msgid "changes applied" msgstr "" +msgid "click here to see created entity" +msgstr "" + msgid "click on the box to cancel the deletion" msgstr "" @@ -1132,12 +1165,6 @@ msgid "components_appliname_description" msgstr "display the application title in the page's header" -msgid "components_applmessages" -msgstr "application messages" - -msgid "components_applmessages_description" -msgstr "display the application messages" - msgid "components_breadcrumbs" msgstr "breadcrumbs" @@ -1419,6 +1446,18 @@ msgid "currently attached file: %s" msgstr "" +msgid "cwetype-schema-image" +msgstr "schema" + +msgid "cwetype-schema-permissions" +msgstr "permissions" + +msgid "cwetype-schema-text" +msgstr "description" + +msgid "cwetype-workflow" +msgstr "workflow" + msgid "data directory url" msgstr "" @@ -1538,9 +1577,6 @@ msgid "detach attached file %s" msgstr "" -msgid "detailed schema view" -msgstr "" - msgid "display order of the action" msgstr "" @@ -1624,6 +1660,9 @@ msgid "entity edited" msgstr "" +msgid "entity linked" +msgstr "" + msgid "entity type" msgstr "" @@ -1676,6 +1715,18 @@ msgid "facets_created_by-facet_description" msgstr "" +msgid "facets_cwfinal-facet" +msgstr "\"final entity or relation type\" facet" + +msgid "facets_cwfinal-facet_description" +msgstr "" + +msgid "facets_cwmeta-facet" +msgstr "" + +msgid "facets_cwmeta-facet_description" +msgstr "" + msgid "facets_etype-facet" msgstr "\"entity type\" facet" @@ -1820,9 +1871,6 @@ msgid "hide filter form" msgstr "" -msgid "hide meta-data" -msgstr "hide meta entities and relations" - msgid "home" msgstr "" @@ -2484,6 +2532,12 @@ msgid "schema's permissions definitions" msgstr "" +msgid "schema-image" +msgstr "schema" + +msgid "schema-text" +msgstr "description" + msgid "search" msgstr "" @@ -2569,9 +2623,6 @@ msgid "show filter form" msgstr "" -msgid "show meta-data" -msgstr "show the complete schema" - msgid "sioc" msgstr "" @@ -2685,6 +2736,9 @@ msgid "thursday" msgstr "" +msgid "timeline" +msgstr "" + msgid "timestamp" msgstr "" @@ -2957,14 +3011,26 @@ #~ msgid "add a Card" #~ msgstr "add a card" +#~ msgid "components_applmessages" +#~ msgstr "application messages" + +#~ msgid "components_applmessages_description" +#~ msgstr "display the application messages" + #~ msgid "content_format" #~ msgstr "content format" +#~ msgid "hide meta-data" +#~ msgstr "hide meta entities and relations" + #~ msgid "planned_delivery" #~ msgstr "planned delivery" #~ msgid "remove this Card" #~ msgstr "remove this card" +#~ msgid "show meta-data" +#~ msgstr "show the complete schema" + #~ msgid "wikiid" #~ msgstr "wiki identifier" diff -r d5987f75c97c -r 7ded2a1416e4 i18n/es.po --- a/i18n/es.po Thu Jul 02 10:36:25 2009 +0200 +++ b/i18n/es.po Mon Jul 06 19:55:18 2009 +0200 @@ -105,7 +105,7 @@ #, python-format msgid "%d years" -msgstr "" +msgstr "%d años" #, python-format msgid "%s error report" @@ -159,6 +159,14 @@ msgid "1?" msgstr "1 0..1" +#, python-format +msgid "" +"
This schema of the data model excludes the meta-data, but you " +"can also display a complete schema with meta-data.
" +msgstr "" +"
Este esquema del modelo de datos no incluye los meta-datos, pero " +"se puede ver a un modelo completo con meta-datos.
" + msgid "?*" msgstr "0..1 0..n" @@ -174,12 +182,18 @@ msgid "AND" msgstr "Y" +msgid "Add permissions" +msgstr "Añadir autorizaciónes" + msgid "Any" msgstr "Cualquiera" msgid "Application" msgstr "Aplicación" +msgid "Attributes" +msgstr "Atributos" + msgid "Bookmark" msgstr "Favorito" @@ -289,6 +303,9 @@ msgid "Decimal_plural" msgstr "Decimales" +msgid "Delete permissions" +msgstr "Autorización de suprimir" + msgid "Do you want to delete the following element(s) ?" msgstr "Desea suprimir el(los) elemento(s) siguiente(s)" @@ -311,7 +328,7 @@ msgstr "Números flotantes" msgid "From:" -msgstr "" +msgstr "De: " msgid "Int" msgstr "Número entero" @@ -400,8 +417,11 @@ msgid "RQLExpression_plural" msgstr "Expresiones RQL" +msgid "Read permissions" +msgstr "Autorización de leer" + msgid "Recipients:" -msgstr "" +msgstr "Destinatarios" msgid "Relations" msgstr "Relaciones" @@ -413,6 +433,9 @@ msgid "Schema %s" msgstr "Esquema %s" +msgid "Schema of the data model" +msgstr "Esquema del modelo de datos" + msgid "Search for" msgstr "Buscar" @@ -435,7 +458,7 @@ msgstr "Cadenas de caracteres" msgid "Subject:" -msgstr "" +msgstr "Sujeto:" msgid "Submit bug report" msgstr "Enviar un reporte de error (bug)" @@ -454,6 +477,9 @@ msgid "The view %s could not be found" msgstr "La vista %s no ha podido ser encontrada" +msgid "There is no workflow defined for this entity." +msgstr "No hay workflow para este entidad" + #, python-format msgid "This %s" msgstr "Este %s" @@ -531,6 +557,9 @@ msgid "Unable to find anything named \"%s\" in the schema !" msgstr "No encontramos el nombre \"%s\" en el esquema" +msgid "Update permissions" +msgstr "Autorización de modificar" + msgid "Used by:" msgstr "Utilizado por :" @@ -683,7 +712,7 @@ msgstr "" msgid "actions_managepermission" -msgstr "" +msgstr "Administración de autorizaciónes" msgid "actions_managepermission_description" msgstr "" @@ -973,7 +1002,7 @@ msgstr "Atributo" msgid "attributes with modified permissions:" -msgstr "" +msgstr "atributos con autorizaciónes modificadas:" msgid "august" msgstr "Agosto" @@ -1008,6 +1037,9 @@ msgid "bookmarks" msgstr "Favoritos" +msgid "bookmarks are used to have user's specific internal links" +msgstr "favoritos son usados para que un usuario recorde ligas" + msgid "boxes" msgstr "Cajas" @@ -1132,7 +1164,7 @@ msgstr "cardinalidad" msgid "category" -msgstr "" +msgstr "categoria" #, python-format msgid "changed state of %(etype)s #%(eid)s (%(title)s)" @@ -1141,6 +1173,9 @@ msgid "changes applied" msgstr "Cambios realizados" +msgid "click here to see created entity" +msgstr "ver la entidad creada" + msgid "click on the box to cancel the deletion" msgstr "Seleccione la zona de edición para cancelar la eliminación" @@ -1162,12 +1197,6 @@ msgid "components_appliname_description" msgstr "Muestra el título de la aplicación en el encabezado de la página" -msgid "components_applmessages" -msgstr "Mensajes de la aplicación" - -msgid "components_applmessages_description" -msgstr "Muestra los mensajes de la aplicación" - msgid "components_breadcrumbs" msgstr "Ruta de Navegación" @@ -1474,7 +1503,19 @@ #, python-format msgid "currently attached file: %s" -msgstr "" +msgstr "archivo adjunto: %s" + +msgid "cwetype-schema-image" +msgstr "Esquema" + +msgid "cwetype-schema-permissions" +msgstr "Autorizaciónes" + +msgid "cwetype-schema-text" +msgstr "Modelo de datos" + +msgid "cwetype-workflow" +msgstr "Workflow" msgid "data directory url" msgstr "Url del repertorio de datos" @@ -1608,9 +1649,6 @@ msgid "detach attached file %s" msgstr "Quitar archivo adjunto %s" -msgid "detailed schema view" -msgstr "Vista detallada del esquema" - msgid "display order of the action" msgstr "Orden de aparición de la acción" @@ -1685,16 +1723,19 @@ msgstr "Entidades eliminadas" msgid "entity copied" -msgstr "" +msgstr "entidad copiada" msgid "entity created" -msgstr "" +msgstr "entidad creada" msgid "entity deleted" msgstr "Entidad eliminada" msgid "entity edited" -msgstr "" +msgstr "entidad modificada" + +msgid "entity linked" +msgstr "entidad asociada" msgid "entity type" msgstr "Tipo de entidad" @@ -1753,6 +1794,18 @@ msgid "facets_created_by-facet_description" msgstr "faceta creado por" +msgid "facets_cwfinal-facet" +msgstr "faceta \"final\"" + +msgid "facets_cwfinal-facet_description" +msgstr "faceta para las entidades \"finales\"" + +msgid "facets_cwmeta-facet" +msgstr "faceta \"meta\"" + +msgid "facets_cwmeta-facet_description" +msgstr "faceta para las entidades \"meta\"" + msgid "facets_etype-facet" msgstr "faceta \"es de tipo\"" @@ -1809,7 +1862,7 @@ #, python-format msgid "from %(date)s" -msgstr "" +msgstr "de %(date)s" msgid "from_entity" msgstr "De la entidad" @@ -1836,7 +1889,7 @@ msgstr "Trazado de curbas estándares" msgid "generic relation to link one entity to another" -msgstr "" +msgstr "relación generica para ligar entidades" msgid "go back to the index page" msgstr "Regresar a la página de inicio" @@ -1897,9 +1950,6 @@ msgid "hide filter form" msgstr "Esconder el filtro" -msgid "hide meta-data" -msgstr "Esconder los meta-datos" - msgid "home" msgstr "Inicio" @@ -2106,7 +2156,8 @@ msgid "" "link a permission to the entity. This permission should be used in the " "security definition of the entity's type to be useful." -msgstr "" +msgstr "relaciónar una autorización con la entidad. Este autorización debe " +"ser usada en la definición de la entidad para ser utíl." msgid "" "link a property to the user which want this property customization. Unless " @@ -2150,7 +2201,7 @@ msgstr "Clave de acesso" msgid "login or email" -msgstr "" +msgstr "Clave de acesso o dirección de correo" msgid "login_action" msgstr "Ingresa tus datos" @@ -2259,15 +2310,19 @@ msgid "navigation.combobox-limit" msgstr "" +# msgstr "Navegación: numero maximo de elementos en una caja de elección (combobox)" msgid "navigation.page-size" msgstr "" +# msgstr "Navegación: numero maximo de elementos por pagina" msgid "navigation.related-limit" msgstr "" +# msgstr "Navegación: numero maximo de elementos relacionados" msgid "navigation.short-line-size" msgstr "" +#msgstr "Navegación: numero maximo de caracteres en una linéa corta" msgid "navtop" msgstr "Encabezado del contenido principal" @@ -2282,7 +2337,7 @@ msgstr "no" msgid "no associated permissions" -msgstr "" +msgstr "no autorización relacionada" msgid "no possible transition" msgstr "transición no posible" @@ -2322,7 +2377,7 @@ msgstr "objeto" msgid "object_plural:" -msgstr "" +msgstr "objetos:" msgid "october" msgstr "octubre" @@ -2340,7 +2395,7 @@ msgstr "solo estan permitidas consultas de lectura" msgid "open all" -msgstr "" +msgstr "abrir todos" msgid "order" msgstr "orden" @@ -2349,10 +2404,10 @@ msgstr "orden" msgid "owl" -msgstr "" +msgstr "owl" msgid "owlabox" -msgstr "" +msgstr "owlabox" msgid "owned_by" msgstr "pertenece a" @@ -2385,10 +2440,10 @@ msgstr "Permiso" msgid "permissions for entities" -msgstr "" +msgstr "autorizaciónes para entidades" msgid "permissions for relations" -msgstr "" +msgstr "autorizaciónes para relaciones" msgid "permissions for this entity" msgstr "Permisos para esta entidad" @@ -2400,7 +2455,7 @@ msgstr "Seleccione los favoritos existentes" msgid "pkey" -msgstr "" +msgstr "pkey" msgid "please correct errors below" msgstr "Favor de corregir errores" @@ -2458,7 +2513,7 @@ msgstr "Definición" msgid "relations" -msgstr "" +msgstr "relaciones" msgid "relations deleted" msgstr "Relaciones eliminadas" @@ -2518,16 +2573,16 @@ msgstr "Eliminar esta transición" msgid "require_group" -msgstr "Requiere_grupo" +msgstr "Requiere grupo" msgid "require_group_object" -msgstr "Objeto_grupo_requerido" +msgstr "Requerido por grupo" msgid "require_permission" -msgstr "" +msgstr "Requiere autorización" msgid "require_permission_object" -msgstr "" +msgstr "Requerido por autorización" msgid "required attribute" msgstr "Atributo requerido" @@ -2582,6 +2637,12 @@ msgid "schema's permissions definitions" msgstr "definiciones de permisos del esquema" +msgid "schema-image" +msgstr "esquema imagen" + +msgid "schema-text" +msgstr "esquema text" + msgid "search" msgstr "buscar" @@ -2601,7 +2662,7 @@ msgstr "Ver todos" msgid "see_also" -msgstr "" +msgstr "Ver tambíen" msgid "select" msgstr "Seleccionar" @@ -2610,7 +2671,7 @@ msgstr "seleccione un" msgid "select a key first" -msgstr "" +msgstr "seleccione una clave" msgid "select a relation" msgstr "seleccione una relación" @@ -2670,9 +2731,6 @@ msgid "show filter form" msgstr "afficher le filtre" -msgid "show meta-data" -msgstr "mostrar meta-data" - msgid "sioc" msgstr "" @@ -2730,7 +2788,7 @@ msgstr "cardinalidad sujeto/objeto" msgid "subject_plural:" -msgstr "" +msgstr "sujetos:" msgid "sunday" msgstr "domingo" @@ -2787,6 +2845,9 @@ msgid "thursday" msgstr "jueves" +msgid "timeline" +msgstr "" + msgid "timestamp" msgstr "fecha" @@ -2804,7 +2865,7 @@ #, python-format msgid "to %(date)s" -msgstr "" +msgstr "a %(date)s" msgid "to associate with" msgstr "a asociar con" @@ -2825,7 +2886,7 @@ msgstr "a hacer por" msgid "toggle check boxes" -msgstr "" +msgstr "cambiar valor" msgid "transition is not allowed" msgstr "transition no permitida" @@ -2894,7 +2955,7 @@ msgstr "propiedad desconocida" msgid "up" -msgstr "" +msgstr "arriba" msgid "upassword" msgstr "clave de acceso" @@ -3055,7 +3116,7 @@ msgstr "ha terminado la sesion" msgid "you should probably delete that property" -msgstr "" +msgstr "deberia probablamente suprimir esta propriedad" #~ msgid "%s constraint failed" #~ msgstr "La contrainte %s n'est pas satisfaite" @@ -3121,6 +3182,12 @@ #~ msgid "cancel edition" #~ msgstr "annuler l'Èdition" +#~ msgid "components_applmessages" +#~ msgstr "Mensajes de la aplicación" + +#~ msgid "components_applmessages_description" +#~ msgstr "Muestra los mensajes de la aplicación" + #~ msgid "components_rss_feed_url" #~ msgstr "RSS FEED URL" @@ -3140,6 +3207,9 @@ #~ "langue par dÈfaut (regarder le rÈpertoire i18n de l'application pour voir " #~ "les langues disponibles)" +#~ msgid "detailed schema view" +#~ msgstr "Vista detallada del esquema" + #~ msgid "filter" #~ msgstr "filtrer" @@ -3149,6 +3219,9 @@ #~ msgid "header" #~ msgstr "en-tÍte de page" +#~ msgid "hide meta-data" +#~ msgstr "Esconder los meta-datos" + #~ msgid "iCal" #~ msgstr "iCal" @@ -3190,6 +3263,9 @@ #~ msgid "see also" #~ msgstr "voir aussi" +#~ msgid "show meta-data" +#~ msgstr "mostrar meta-data" + #~ msgid "status will change from %s to %s" #~ msgstr "l'Ètat va passer de %s ‡ %s" diff -r d5987f75c97c -r 7ded2a1416e4 i18n/fr.po --- a/i18n/fr.po Thu Jul 02 10:36:25 2009 +0200 +++ b/i18n/fr.po Mon Jul 06 19:55:18 2009 +0200 @@ -159,6 +159,14 @@ msgid "1?" msgstr "1 0..1" +#, python-format +msgid "" +"
This schema of the data model excludes the meta-data, but you " +"can also display a complete schema with meta-data.
" +msgstr "" +"
Ce schéma du modèle de données exclue les méta-données, mais vous " +"pouvez afficher un schéma complet.
" + msgid "?*" msgstr "0..1 0..n" @@ -174,12 +182,18 @@ msgid "AND" msgstr "ET" +msgid "Add permissions" +msgstr "Permissions d'ajouter" + msgid "Any" msgstr "N'importe" msgid "Application" msgstr "Application" +msgid "Attributes" +msgstr "Attributs" + msgid "Bookmark" msgstr "Signet" @@ -289,6 +303,9 @@ msgid "Decimal_plural" msgstr "Nombres décimaux" +msgid "Delete permissions" +msgstr "Permissions de supprimer" + msgid "Do you want to delete the following element(s) ?" msgstr "Voulez vous supprimer le(s) élément(s) suivant(s)" @@ -400,6 +417,9 @@ msgid "RQLExpression_plural" msgstr "Expressions RQL" +msgid "Read permissions" +msgstr "Permissions de lire" + msgid "Recipients:" msgstr "Destinataires :" @@ -413,6 +433,9 @@ msgid "Schema %s" msgstr "Schéma %s" +msgid "Schema of the data model" +msgstr "Schéma du modèle de données" + msgid "Search for" msgstr "Rechercher" @@ -454,6 +477,9 @@ msgid "The view %s could not be found" msgstr "La vue %s est introuvable" +msgid "There is no workflow defined for this entity." +msgstr "Il n'y a pas de workflow défini pour ce type d'entité" + #, python-format msgid "This %s" msgstr "Ce %s" @@ -531,6 +557,9 @@ msgid "Unable to find anything named \"%s\" in the schema !" msgstr "Rien de nommé \"%s\" dans le schéma" +msgid "Update permissions" +msgstr "Permissions de modifier" + msgid "Used by:" msgstr "Utilisé par :" @@ -604,6 +633,9 @@ "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject." "get_cache() method." msgstr "" +"une simple entité de cache, caractérisées par un nom et une date de validité. L'application " +"est responsable de la mise à jour de la date quand il est nécessaire d'invalider le cache (typiquement dans les crochets). " +"Voir aussi la méthode get_cache() sur la classe AppRsetObject." msgid "about this site" msgstr "à propos de ce site" @@ -645,7 +677,7 @@ msgstr "" msgid "actions_download_as_owl" -msgstr "" +msgstr "télécharger en owl" msgid "actions_download_as_owl_description" msgstr "" @@ -681,7 +713,7 @@ msgstr "" msgid "actions_managepermission" -msgstr "" +msgstr "gestion des permissions" msgid "actions_managepermission_description" msgstr "" @@ -964,7 +996,7 @@ #, python-format msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)" msgstr "" -"L'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n" +"l'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n" "autre via la relation %(rtype)s" msgid "attribute" @@ -1006,6 +1038,9 @@ msgid "bookmarks" msgstr "signets" +msgid "bookmarks are used to have user's specific internal links" +msgstr "les signets sont utilisés pour gérer des liens internes par utilisateur" + msgid "boxes" msgstr "boîtes" @@ -1139,6 +1174,9 @@ msgid "changes applied" msgstr "changements appliqués" +msgid "click here to see created entity" +msgstr "cliquez ici pour voir l'entité créée" + msgid "click on the box to cancel the deletion" msgstr "cliquer dans la zone d'édition pour annuler la suppression" @@ -1160,12 +1198,6 @@ msgid "components_appliname_description" msgstr "affiche le titre de l'application dans l'en-tête de page" -msgid "components_applmessages" -msgstr "messages applicatifs" - -msgid "components_applmessages_description" -msgstr "affiche les messages applicatifs" - msgid "components_breadcrumbs" msgstr "fil d'ariane" @@ -1475,6 +1507,18 @@ msgid "currently attached file: %s" msgstr "fichie actuellement attaché %s" +msgid "cwetype-schema-image" +msgstr "schéma" + +msgid "cwetype-schema-permissions" +msgstr "permissions" + +msgid "cwetype-schema-text" +msgstr "description" + +msgid "cwetype-workflow" +msgstr "workflow" + msgid "data directory url" msgstr "url du répertoire de données" @@ -1606,9 +1650,6 @@ msgid "detach attached file %s" msgstr "détacher le fichier existant %s" -msgid "detailed schema view" -msgstr "vue détaillée du schéma" - msgid "display order of the action" msgstr "ordre d'affichage de l'action" @@ -1694,6 +1735,9 @@ msgid "entity edited" msgstr "entité éditée" +msgid "entity linked" +msgstr "entité liée" + msgid "entity type" msgstr "type d'entité" @@ -1750,6 +1794,18 @@ msgid "facets_created_by-facet_description" msgstr "" +msgid "facets_cwfinal-facet" +msgstr "facette \"type d'entité ou de relation final\"" + +msgid "facets_cwfinal-facet_description" +msgstr "" + +msgid "facets_cwmeta-facet" +msgstr "" + +msgid "facets_cwmeta-facet_description" +msgstr "" + msgid "facets_etype-facet" msgstr "facette \"est de type\"" @@ -1895,9 +1951,6 @@ msgid "hide filter form" msgstr "cacher le filtre" -msgid "hide meta-data" -msgstr "cacher les entités et relations \"méta\"" - msgid "home" msgstr "maison" @@ -2106,8 +2159,8 @@ "link a permission to the entity. This permission should be used in the " "security definition of the entity's type to be useful." msgstr "" -"lie une permission à une entité. Cette permission doit généralement être utilisée " -"dans la définition de sécurité du type d'entité pour être utile." +"lie une permission à une entité. Cette permission doit généralement être " +"utilisée dans la définition de sécurité du type d'entité pour être utile." msgid "" "link a property to the user which want this property customization. Unless " @@ -2590,6 +2643,12 @@ msgid "schema's permissions definitions" msgstr "permissions définies dans le schéma" +msgid "schema-image" +msgstr "schéma" + +msgid "schema-text" +msgstr "description" + msgid "search" msgstr "rechercher" @@ -2678,9 +2737,6 @@ msgid "show filter form" msgstr "afficher le filtre" -msgid "show meta-data" -msgstr "afficher le schéma complet" - msgid "sioc" msgstr "sioc" @@ -2795,6 +2851,9 @@ msgid "thursday" msgstr "jeudi" +msgid "timeline" +msgstr "échelle de temps" + msgid "timestamp" msgstr "date" @@ -3133,6 +3192,12 @@ #~ msgid "close all" #~ msgstr "tout fermer" +#~ msgid "components_applmessages" +#~ msgstr "messages applicatifs" + +#~ msgid "components_applmessages_description" +#~ msgstr "affiche les messages applicatifs" + #~ msgid "components_rss_feed_url" #~ msgstr "syndication rss" @@ -3149,6 +3214,9 @@ #~ "langue par défaut (regarder le répertoire i18n de l'application pour voir " #~ "les langues disponibles)" +#~ msgid "detailed schema view" +#~ msgstr "vue détaillée du schéma" + #~ msgid "filter" #~ msgstr "filtrer" @@ -3158,6 +3226,9 @@ #~ msgid "header" #~ msgstr "en-tête de page" +#~ msgid "hide meta-data" +#~ msgstr "cacher les entités et relations \"méta\"" + #~ msgid "iCal" #~ msgstr "iCal" @@ -3193,6 +3264,9 @@ #~ msgid "see also" #~ msgstr "voir aussi" +#~ msgid "show meta-data" +#~ msgstr "afficher le schéma complet" + #~ msgid "status will change from %s to %s" #~ msgstr "l'état va passer de %s à %s" diff -r d5987f75c97c -r 7ded2a1416e4 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Thu Jul 02 10:36:25 2009 +0200 +++ b/misc/migration/bootstrapmigration_repository.py Mon Jul 06 19:55:18 2009 +0200 @@ -8,6 +8,8 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ +applcubicwebversion, cubicwebversion = versions_map['cubicweb'] + if applcubicwebversion < (3, 2, 2) and cubicwebversion >= (3, 2, 1): from base64 import b64encode for table in ('entities', 'deleted_entities'): diff -r d5987f75c97c -r 7ded2a1416e4 schema.py --- a/schema.py Thu Jul 02 10:36:25 2009 +0200 +++ b/schema.py Mon Jul 06 19:55:18 2009 +0200 @@ -354,6 +354,7 @@ """rql expression factory""" return ERQLExpression(expression, mainvars, eid) + class CubicWebRelationSchema(RelationSchema): RelationSchema._RPROPERTIES['eid'] = None _perms_checked = False diff -r d5987f75c97c -r 7ded2a1416e4 selectors.py --- a/selectors.py Thu Jul 02 10:36:25 2009 +0200 +++ b/selectors.py Mon Jul 06 19:55:18 2009 +0200 @@ -482,8 +482,7 @@ def __call__(self, cls, req, *args, **kwargs): score = 0 for param in self.expected: - val = req.form.get(param) - if not val: + if not param in req.form: return 0 score += 1 return len(self.expected) @@ -623,6 +622,14 @@ etype = kwargs['etype'] except KeyError: return 0 + else: + # only check this is a known type if etype comes from req.form, + # else we want the error to propagate + try: + etype = cls.vreg.case_insensitive_etypes[etype.lower()] + req.form['etype'] = etype + except KeyError: + return 0 return self.score_class(cls.vreg.etype_class(etype), req) diff -r d5987f75c97c -r 7ded2a1416e4 server/checkintegrity.py --- a/server/checkintegrity.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/checkintegrity.py Mon Jul 06 19:55:18 2009 +0200 @@ -71,6 +71,14 @@ uniquecstrcheck_before_modification) from cubicweb.server.repository import FTIndexEntityOp repo = session.repo + cursor = session.pool['system'] + if not repo.system_source.indexer.has_fti_table(cursor): + from indexer import get_indexer + print 'no text index table' + indexer = get_indexer(repo.system_source.dbdriver) + # XXX indexer.init_fti(cursor) once index 0.7 is out + indexer.init_extensions(cursor) + cursor.execute(indexer.sql_init_fti()) repo.hm.unregister_hook(setmtime_before_update_entity, 'before_update_entity', '') repo.hm.unregister_hook(uniquecstrcheck_before_modification, diff -r d5987f75c97c -r 7ded2a1416e4 server/hooks.py --- a/server/hooks.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/hooks.py Mon Jul 06 19:55:18 2009 +0200 @@ -222,9 +222,10 @@ return if self.session.unsafe_execute(*self._rql()).rowcount < 1: etype = self.session.describe(self.eid)[0] - msg = self.session._('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)') - raise ValidationError(self.eid, {self.rtype: msg % {'rtype': self.rtype, - 'etype': etype, + _ = self.session._ + msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)') + raise ValidationError(self.eid, {self.rtype: msg % {'rtype': _(self.rtype), + 'etype': _(etype), 'eid': self.eid}}) def commit_event(self): diff -r d5987f75c97c -r 7ded2a1416e4 server/migractions.py --- a/server/migractions.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/migractions.py Mon Jul 06 19:55:18 2009 +0200 @@ -64,25 +64,48 @@ self.fs_schema = schema self._synchronized = set() + # overriden from base MigrationHelper ###################################### + @cached def repo_connect(self): self.repo = get_repository(method='inmemory', config=self.config) return self.repo + def cube_upgraded(self, cube, version): + self.cmd_set_property('system.version.%s' % cube.lower(), + unicode(version)) + self.commit() + def shutdown(self): if self.repo is not None: self.repo.shutdown() - def rewrite_vcconfiguration(self): - """write current installed versions (of cubicweb software - and of each used cube) into the database + def migrate(self, vcconf, toupgrade, options): + if not options.fs_only: + if options.backup_db is None: + self.backup_database() + elif options.backup_db: + self.backup_database(askconfirm=False) + super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options) + + def process_script(self, migrscript, funcname=None, *args, **kwargs): + """execute a migration script + in interactive mode, display the migration script path, ask for + confirmation and execute it if confirmed """ - self.cmd_set_property('system.version.cubicweb', - self.config.cubicweb_version()) - for pkg in self.config.cubes(): - pkgversion = self.config.cube_version(pkg) - self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion) - self.commit() + try: + if migrscript.endswith('.sql'): + if self.execscript_confirm(migrscript): + sqlexec(open(migrscript).read(), self.session.system_sql) + else: + return super(ServerMigrationHelper, self).process_script( + migrscript, funcname, *args, **kwargs) + self.commit() + except: + self.rollback() + raise + + # server specific migration methods ######################################## def backup_database(self, backupfile=None, askconfirm=True): config = self.config @@ -142,26 +165,6 @@ break print 'database restored' - def migrate(self, vcconf, toupgrade, options): - if not options.fs_only: - if options.backup_db is None: - self.backup_database() - elif options.backup_db: - self.backup_database(askconfirm=False) - super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options) - - def process_script(self, migrscript, funcname=None, *args, **kwargs): - """execute a migration script - in interactive mode, display the migration script path, ask for - confirmation and execute it if confirmed - """ - if migrscript.endswith('.sql'): - if self.execscript_confirm(migrscript): - sqlexec(open(migrscript).read(), self.session.system_sql) - else: - return super(ServerMigrationHelper, self).process_script( - migrscript, funcname, *args, **kwargs) - @property def cnx(self): """lazy connection""" diff -r d5987f75c97c -r 7ded2a1416e4 server/repository.py --- a/server/repository.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/repository.py Mon Jul 06 19:55:18 2009 +0200 @@ -392,10 +392,23 @@ except ZeroDivisionError: pass + def _login_from_email(self, login): + session = self.internal_session() + try: + rset = session.execute('Any L WHERE U login L, U primary_email M, ' + 'M address %(login)s', {'login': login}) + if rset.rowcount == 1: + login = rset[0][0] + finally: + session.close() + return login + def authenticate_user(self, session, login, password): """validate login / password, raise AuthenticationError on failure return associated CWUser instance on success """ + if self.vreg.config['allow-email-login'] and '@' in login: + login = self._login_from_email(login) for source in self.sources: if source.support_entity('CWUser'): try: @@ -405,11 +418,11 @@ continue else: raise AuthenticationError('authentication failed with all sources') - euser = self._build_user(session, eid) + cwuser = self._build_user(session, eid) if self.config.consider_user_state and \ - not euser.state in euser.AUTHENTICABLE_STATES: + not cwuser.state in cwuser.AUTHENTICABLE_STATES: raise AuthenticationError('user is not in authenticable state') - return euser + return cwuser def _build_user(self, session, eid): """return a CWUser entity for user with the given eid""" @@ -417,13 +430,13 @@ rql = cls.fetch_rql(session.user, ['X eid %(x)s']) rset = session.execute(rql, {'x': eid}, 'x') assert len(rset) == 1, rset - euser = rset.get_entity(0, 0) + cwuser = rset.get_entity(0, 0) # pylint: disable-msg=W0104 - # prefetch / cache euser's groups and properties. This is especially + # prefetch / cache cwuser's groups and properties. This is especially # useful for internal sessions to avoid security insertions - euser.groups - euser.properties - return euser + cwuser.groups + cwuser.properties + return cwuser # public (dbapi) interface ################################################ @@ -664,13 +677,22 @@ custom properties) """ session = self._get_session(sessionid, setpool=False) - if props: - # update session properties - for prop, value in props.items(): - session.change_property(prop, value) + if props is not None: + self.set_session_props(sessionid, props) user = session.user return user.eid, user.login, user.groups, user.properties + def set_session_props(self, sessionid, props): + """this method should be used by client to: + * check session id validity + * update user information on each user's request (i.e. groups and + custom properties) + """ + session = self._get_session(sessionid, setpool=False) + # update session properties + for prop, value in props.items(): + session.change_property(prop, value) + # public (inter-repository) interface ##################################### def entities_modified_since(self, etypes, mtime): diff -r d5987f75c97c -r 7ded2a1416e4 server/schemahooks.py --- a/server/schemahooks.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/schemahooks.py Mon Jul 06 19:55:18 2009 +0200 @@ -247,8 +247,7 @@ """actually add the entity type to the application's schema""" eid = None # make pylint happy def commit_event(self): - eschema = self.schema.add_entity_type(self.kobj) - eschema.eid = self.eid + self.schema.add_entity_type(self.kobj) def before_add_eetype(session, entity): """before adding a CWEType entity: @@ -299,7 +298,8 @@ # register operation to modify the schema on commit # this have to be done before adding other relations definitions # or permission settings - AddCWETypeOp(session, etype, eid=entity.eid) + etype.eid = entity.eid + AddCWETypeOp(session, etype) # add meta creation_date, modification_date and owned_by relations for rql, kwargs in relrqls: session.execute(rql, kwargs) @@ -311,7 +311,6 @@ def commit_event(self): rschema = self.schema.add_relation_type(self.kobj) rschema.set_default_groups() - rschema.eid = self.eid def before_add_ertype(session, entity): """before adding a CWRType entity: @@ -331,12 +330,13 @@ schema on commit We don't know yeat this point if a table is necessary """ - AddCWRTypeOp(session, RelationType(name=entity['name'], - description=entity.get('description'), - meta=entity.get('meta', False), - inlined=entity.get('inlined', False), - symetric=entity.get('symetric', False)), - eid=entity.eid) + rtype = RelationType(name=entity['name'], + description=entity.get('description'), + meta=entity.get('meta', False), + inlined=entity.get('inlined', False), + symetric=entity.get('symetric', False)) + rtype.eid = entity.eid + AddCWRTypeOp(session, rtype) class AddErdefOp(EarlySchemaOperation): diff -r d5987f75c97c -r 7ded2a1416e4 server/sources/pyrorql.py --- a/server/sources/pyrorql.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/sources/pyrorql.py Mon Jul 06 19:55:18 2009 +0200 @@ -164,7 +164,12 @@ """ self.info('synchronizing pyro source %s', self.uri) cnx = self.get_connection() - extrepo = cnx._repo + try: + extrepo = cnx._repo + except AttributeError: + # fake connection wrapper returned when we can't connect to the + # external source (hence we've no chance to synchronize...) + return etypes = self.support_entities.keys() if mtime is None: mtime = self.last_update_time() @@ -212,7 +217,7 @@ nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group'] #cnxprops = ConnectionProperties(cnxtype=self.config['cnx-type']) return dbapi.connect(database=self.config['pyro-ns-id'], - user=self.config['cubicweb-user'], + login=self.config['cubicweb-user'], password=self.config['cubicweb-password'], host=nshost, port=nsport, group=nsgroup, setvreg=False) #cnxprops=cnxprops) diff -r d5987f75c97c -r 7ded2a1416e4 server/test/unittest_hooks.py --- a/server/test/unittest_hooks.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/test/unittest_hooks.py Mon Jul 06 19:55:18 2009 +0200 @@ -242,7 +242,7 @@ class SchemaModificationHooksTC(RepositoryBasedTC): - copy_schema = True + #copy_schema = True def setUp(self): if not hasattr(self, '_repo'): @@ -471,6 +471,10 @@ self.commit() # should not be able anymore to add personne without prenom self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"') + self.execute('SET DEF cardinality "?1" ' + 'WHERE DEF relation_type RT, DEF from_entity E,' + 'RT name "prenom", E name "Personne"') + self.commit() class WorkflowHooksTC(RepositoryBasedTC): diff -r d5987f75c97c -r 7ded2a1416e4 server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Thu Jul 02 10:36:25 2009 +0200 +++ b/server/test/unittest_migractions.py Mon Jul 06 19:55:18 2009 +0200 @@ -23,7 +23,7 @@ class MigrationCommandsTC(RepositoryBasedTC): - copy_schema = True + copy_schema = False def setUp(self): if not hasattr(self, '_repo'): @@ -146,7 +146,7 @@ for cstr in eschema.constraints('name'): self.failUnless(hasattr(cstr, 'eid')) - def test_drop_entity_type(self): + def test_add_drop_entity_type(self): self.mh.cmd_add_entity_type('Folder2') todoeid = self.mh.cmd_add_state(u'todo', 'Folder2', initial=True) doneeid = self.mh.cmd_add_state(u'done', 'Folder2') @@ -161,7 +161,7 @@ self.failIf(self.execute('State X WHERE NOT X state_of ET')) self.failIf(self.execute('Transition X WHERE NOT X transition_of ET')) - def test_add_relation_type(self): + def test_add_drop_relation_type(self): self.mh.cmd_add_entity_type('Folder2', auto=False) self.mh.cmd_add_relation_type('filed_under2') self.failUnless('filed_under2' in self.schema) @@ -169,52 +169,40 @@ ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File', 'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision']) self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',)) - - - def test_drop_relation_type(self): - self.mh.cmd_add_entity_type('Folder2', auto=False) - self.mh.cmd_add_relation_type('filed_under2') - self.failUnless('filed_under2' in self.schema) self.mh.cmd_drop_relation_type('filed_under2') self.failIf('filed_under2' in self.schema) - def test_add_relation_definition(self): - self.mh.cmd_add_relation_definition('Societe', 'in_state', 'State') - self.assertEquals(sorted(str(x) for x in self.schema['in_state'].subjects()), - ['Affaire', 'CWUser', 'Division', 'Note', 'Societe', 'SubDivision']) - self.assertEquals(self.schema['in_state'].objects(), ('State',)) - def test_add_relation_definition_nortype(self): self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Affaire') self.assertEquals(self.schema['concerne2'].subjects(), ('Personne',)) self.assertEquals(self.schema['concerne2'].objects(), ('Affaire',)) + self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire') + self.failIf('concerne2' in self.schema) - def test_drop_relation_definition1(self): - self.failUnless('concerne' in self.schema) + def test_drop_relation_definition_existant_rtype(self): self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) self.mh.cmd_drop_relation_definition('Personne', 'concerne', 'Affaire') self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Division', 'Note', 'Societe', 'SubDivision']) + self.mh.cmd_add_relation_definition('Personne', 'concerne', 'Affaire') + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) + # trick: overwrite self.maxeid to avoid deletion of just reintroduced types + self.maxeid = self.execute('Any MAX(X)')[0][0] def test_drop_relation_definition_with_specialization(self): - self.failUnless('concerne' in self.schema) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) self.mh.cmd_drop_relation_definition('Affaire', 'concerne', 'Societe') - self.mh.cmd_drop_relation_definition('None', 'concerne', 'Societe') self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note']) - - def test_drop_relation_definition2(self): - self.failUnless('evaluee' in self.schema) - self.mh.cmd_drop_relation_definition('Personne', 'evaluee', 'Note') - self.failUnless('evaluee' in self.schema) - self.assertEquals(sorted(self.schema['evaluee'].subjects()), - ['CWUser', 'Division', 'Societe', 'SubDivision']) - self.assertEquals(sorted(self.schema['evaluee'].objects()), - ['Note']) + self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe') + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) + # trick: overwrite self.maxeid to avoid deletion of just reintroduced types + self.maxeid = self.execute('Any MAX(X)')[0][0] def test_rename_relation(self): self.skip('implement me') @@ -455,7 +443,6 @@ ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file') self.assertEquals(str(ex), "can't remove cube file, used as a dependency") - def test_set_state(self): user = self.session.user self.mh.set_state(user.eid, 'deactivated') diff -r d5987f75c97c -r 7ded2a1416e4 test/unittest_rset.py --- a/test/unittest_rset.py Thu Jul 02 10:36:25 2009 +0200 +++ b/test/unittest_rset.py Mon Jul 06 19:55:18 2009 +0200 @@ -6,7 +6,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -from __future__ import with_statement +#from __future__ import with_statement from logilab.common.testlib import TestCase, unittest_main diff -r d5987f75c97c -r 7ded2a1416e4 utils.py --- a/utils.py Thu Jul 02 10:36:25 2009 +0200 +++ b/utils.py Mon Jul 06 19:55:18 2009 +0200 @@ -206,8 +206,13 @@ def add_post_inline_script(self, content): self.post_inlined_scripts.append(content) - def add_onload(self, jscode): - self.add_post_inline_script(u"""jQuery(document).ready(function () { + def add_onload(self, jscode, jsoncall=False): + if jsoncall: + self.add_post_inline_script(u"""jQuery(CubicWeb).bind('ajax-loaded', function(event) { +%s +});""" % jscode) + else: + self.add_post_inline_script(u"""jQuery(document).ready(function () { %s });""" % jscode) diff -r d5987f75c97c -r 7ded2a1416e4 web/application.py --- a/web/application.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/application.py Mon Jul 06 19:55:18 2009 +0200 @@ -21,8 +21,7 @@ from cubicweb.web import LOGGER, component from cubicweb.web import ( StatusResponse, DirectResponse, Redirect, NotFound, - RemoteCallFailed, ExplicitLogin, InvalidSession) -from cubicweb.web.component import Component + RemoteCallFailed, ExplicitLogin, InvalidSession, RequestError) # make session manager available through a global variable so the debug view can # print information about web session @@ -340,7 +339,7 @@ raise except ValidationError, ex: self.validation_error_handler(req, ex) - except (Unauthorized, BadRQLQuery), ex: + except (Unauthorized, BadRQLQuery, RequestError), ex: self.error_handler(req, ex, tb=False) except Exception, ex: self.error_handler(req, ex, tb=True) @@ -396,8 +395,8 @@ return self.vreg.main_template(req, template, view=view) def main_template_id(self, req): - template = req.property_value('ui.main-template') - if template not in self.vreg.registry('views') : + template = req.form.get('__template', req.property_value('ui.main-template')) + if template not in self.vreg.registry('views'): template = 'main-template' return template diff -r d5987f75c97c -r 7ded2a1416e4 web/component.py --- a/web/component.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/component.py Mon Jul 06 19:55:18 2009 +0200 @@ -37,7 +37,7 @@ property_defs = { _('visible'): dict(type='Boolean', default=True, - help=_('display the box or not')), + help=_('display the component or not')), _('order'): dict(type='Int', default=99, help=_('display order of the component')), _('context'): dict(type='String', default='header', diff -r d5987f75c97c -r 7ded2a1416e4 web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Thu Jul 02 10:36:25 2009 +0200 +++ b/web/data/cubicweb.ajax.js Mon Jul 06 19:55:18 2009 +0200 @@ -11,15 +11,20 @@ function _loadAjaxHtmlHead(node, head, tag, srcattr) { var loaded = []; - jQuery('head ' + tag).each(function(i) { + var jqtagfilter = tag + '[' + srcattr + ']'; + jQuery('head ' + jqtagfilter).each(function(i) { loaded.push(this.getAttribute(srcattr)); }); node.find(tag).each(function(i) { - if (!loaded.contains(this.getAttribute(srcattr))) { + if (this.getAttribute(srcattr)) { + if (!loaded.contains(this.getAttribute(srcattr))) { + jQuery(this).appendTo(head); + } + } else { jQuery(this).appendTo(head); } }); - node.find(tag).remove(); + node.find(jqtagfilter).remove(); } /* diff -r d5987f75c97c -r 7ded2a1416e4 web/data/cubicweb.css --- a/web/data/cubicweb.css Thu Jul 02 10:36:25 2009 +0200 +++ b/web/data/cubicweb.css Mon Jul 06 19:55:18 2009 +0200 @@ -448,14 +448,11 @@ background: #cfceb7; display: block; font: bold 100% Georgia; - padding : 2px 0; } div.sideBox { padding: 0 0 0.2em; margin-bottom: 0.5em; - min-width: 21em; - max-width: 50em; } ul.sideBox li{ @@ -575,8 +572,6 @@ } div.primaryRight{ - float:right; - } div.metadata { @@ -687,7 +682,7 @@ /***************************************/ table.listing { - margin: 10px 0em; + padding: 10px 0em; color: #000; width: 100%; border-right: 1px solid #dfdfdf; diff -r d5987f75c97c -r 7ded2a1416e4 web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Thu Jul 02 10:36:25 2009 +0200 +++ b/web/data/cubicweb.edition.js Mon Jul 06 19:55:18 2009 +0200 @@ -305,7 +305,9 @@ } function _clearPreviousErrors(formid) { + jQuery('#' + formid + 'ErrorMessage').remove(); jQuery('#' + formid + ' span.error').remove(); + jQuery('#' + formid + ' .error').removeClass('error'); } function _displayValidationerrors(formid, eid, errors) { @@ -324,7 +326,7 @@ field.before(span); } else { firsterrfield = formid; - globalerrors.push(fieldname + ': ' + errmsg); + globalerrors.push(_(fieldname) + ' : ' + errmsg); } } if (globalerrors.length) { @@ -334,7 +336,7 @@ var innernode = UL(null, map(LI, globalerrors)); } // insert DIV and innernode before the form - var div = DIV({'class' : "errorMessage"}); + var div = DIV({'class' : "errorMessage", 'id': formid + 'ErrorMessage'}); div.appendChild(innernode); jQuery('#' + formid).before(div); } diff -r d5987f75c97c -r 7ded2a1416e4 web/formfields.py --- a/web/formfields.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/formfields.py Mon Jul 06 19:55:18 2009 +0200 @@ -7,6 +7,7 @@ """ __docformat__ = "restructuredtext en" +from warnings import warn from datetime import datetime from logilab.mtconverter import html_escape diff -r d5987f75c97c -r 7ded2a1416e4 web/request.py --- a/web/request.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/request.py Mon Jul 06 19:55:18 2009 +0200 @@ -61,6 +61,7 @@ class CubicWebRequestBase(DBAPIRequest): """abstract HTTP request, should be extended according to the HTTP backend""" + json_request = False # to be set to True by json controllers def __init__(self, vreg, https, form=None): super(CubicWebRequestBase, self).__init__(vreg) @@ -91,7 +92,7 @@ or an anonymous connection is open """ super(CubicWebRequestBase, self).set_connection(cnx, user) - # get request language: + # set request language vreg = self.vreg if self.user: try: @@ -114,6 +115,7 @@ def set_language(self, lang): self._ = self.__ = self.translations[lang] self.lang = lang + self.cnx.set_session_props(lang=lang) self.debug('request language: %s', lang) # input form parameters management ######################################## @@ -336,7 +338,6 @@ params[name] = value params['eid'] = eid if len(params) < minparams: - print eid, params raise RequestError(self._('missing parameters for entity %s') % eid) return params @@ -452,6 +453,9 @@ # high level methods for HTML headers management ########################## + def add_onload(self, jscode): + self.html_headers.add_onload(jscode, self.json_request) + def add_js(self, jsfiles, localfile=True): """specify a list of JS files to include in the HTML headers :param jsfiles: a JS filename or a list of JS filenames diff -r d5987f75c97c -r 7ded2a1416e4 web/test/unittest_application.py --- a/web/test/unittest_application.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/test/unittest_application.py Mon Jul 06 19:55:18 2009 +0200 @@ -307,7 +307,7 @@ self.assertEquals(cnx.password, origcnx.password) self.assertEquals(cnx.anonymous_connection, False) self.assertEquals(path, 'view') - self.assertEquals(params, {'__message': 'welcome %s !' % origcnx.login}) + self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login}) def _test_auth_fail(self, req): self.assertRaises(AuthenticationError, self.app.connect, req) @@ -351,8 +351,8 @@ req.form['__password'] = origcnx.password self._test_auth_fail(req) # option allow-email-login set + origcnx.login = address self.set_option('allow-email-login', True) - req, origcnx = self._init_auth('cookie') req.form['__login'] = address req.form['__password'] = origcnx.password self._test_auth_succeed(req, origcnx) diff -r d5987f75c97c -r 7ded2a1416e4 web/test/unittest_form.py --- a/web/test/unittest_form.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/test/unittest_form.py Mon Jul 06 19:55:18 2009 +0200 @@ -5,7 +5,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -from __future__ import with_statement +#from __future__ import with_statement from xml.etree.ElementTree import fromstring diff -r d5987f75c97c -r 7ded2a1416e4 web/views/authentication.py --- a/web/views/authentication.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/authentication.py Mon Jul 06 19:55:18 2009 +0200 @@ -36,7 +36,10 @@ # calling cnx.user() check connection validity, raise # BadConnectionId on failure user = cnx.user(req) - if login and user.login != login: + # check cnx.login and not user.login, since in case of login by + # email, login and cnx.login are the email while user.login is the + # actual user login + if login and cnx.login != login: cnx.close() raise InvalidSession('login mismatch') except BadConnectionId: @@ -53,18 +56,6 @@ req.set_connection(cnx, user) return cnx - def login_from_email(self, login): - # XXX should not be called from web interface - session = self.repo.internal_session() - try: - rset = session.execute('Any L WHERE U login L, U primary_email M, ' - 'M address %(login)s', {'login': login}) - if rset.rowcount == 1: - login = rset[0][0] - finally: - session.close() - return login - def authenticate(self, req, _login=None, _password=None): """authenticate user and return corresponding user object @@ -79,8 +70,6 @@ login, password = _login, _password else: login, password = req.get_authorization() - if self.vreg.config['allow-email-login'] and '@' in (login or u''): - login = self.login_from_email(login) if not login: # No session and no login -> try anonymous login, password = self.vreg.config.anonymous_user() diff -r d5987f75c97c -r 7ded2a1416e4 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/basecontrollers.py Mon Jul 06 19:55:18 2009 +0200 @@ -178,37 +178,50 @@ req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y')) +def _validation_error(req, ex): + req.cnx.rollback() + forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True) + foreid = ex.entity + eidmap = req.data.get('eidmap', {}) + for var, eid in eidmap.items(): + if foreid == eid: + foreid = var + break + return (foreid, ex.errors) + +def _validate_form(req, vreg): + # XXX should use the `RemoteCallFailed` mechanism + try: + ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'), + req=req) + except NoSelectableObject: + return (False, {None: req._('not authorized')}) + try: + ctrl.publish(None) + except ValidationError, ex: + return (False, _validation_error(req, ex)) + except Redirect, ex: + try: + req.cnx.commit() # ValidationError may be raise on commit + except ValidationError, ex: + return (False, _validation_error(req, ex)) + else: + return (True, ex.location) + except Exception, ex: + req.cnx.rollback() + req.exception('unexpected error while validating form') + return (False, req._(str(ex).decode('utf-8'))) + return (False, '???') + + class FormValidatorController(Controller): id = 'validateform' def publish(self, rset=None): - vreg = self.vreg - try: - ctrl = vreg.select('controllers', 'edit', self.req, - appli=self.appli) - except NoSelectableObject: - status, args = (False, {None: self.req._('not authorized')}) - else: - try: - ctrl.publish(None, fromjson=True) - except ValidationError, err: - status, args = self.validation_error(err) - except Redirect, err: - try: - self.req.cnx.commit() # ValidationError may be raise on commit - except ValidationError, err: - status, args = self.validation_error(err) - else: - status, args = (True, err.location) - except Exception, err: - self.req.cnx.rollback() - self.exception('unexpected error in validateform') - try: - status, args = (False, self.req._(unicode(err))) - except UnicodeError: - status, args = (False, repr(err)) - else: - status, args = (False, '???') + self.req.json_request = True + # XXX unclear why we have a separated controller here vs + # js_validate_form on the json controller + status, args = _validate_form(self.req, self.vreg) self.req.set_content_type('text/html') jsarg = simplejson.dumps( (status, args) ) domid = self.req.form.get('__domid', 'entityForm').encode( @@ -217,14 +230,6 @@ window.parent.handleFormValidationResponse('%s', null, null, %s); """ % (domid, simplejson.dumps( (status, args) )) - def validation_error(self, err): - self.req.cnx.rollback() - try: - eid = err.entity.eid - except AttributeError: - eid = err.entity - return (False, (eid, err.errors)) - class JSonController(Controller): id = 'json' @@ -238,6 +243,7 @@ note: it's the responsability of js_* methods to set the correct response content type """ + self.req.json_request = True self.req.pageid = self.req.form.get('pageid') try: fname = self.req.form['fname'] @@ -377,26 +383,8 @@ return self.validate_form(action, names, values) def validate_form(self, action, names, values): - # XXX this method (and correspoding js calls) should use the new - # `RemoteCallFailed` mechansim self.req.form = self._rebuild_posted_form(names, values, action) - vreg = self.vreg - try: - ctrl = vreg.select('controllers', 'edit', self.req) - except NoSelectableObject: - return (False, {None: self.req._('not authorized')}) - try: - ctrl.publish(None, fromjson=True) - except ValidationError, err: - self.req.cnx.rollback() - return (False, (err.entity, err.errors)) - except Redirect, redir: - return (True, redir.location) - except Exception, err: - self.req.cnx.rollback() - self.exception('unexpected error in js_validateform') - return (False, self.req._(str(err).decode('utf-8'))) - return (False, '???') + return _validate_form(self.req, self.vreg) @jsonize def js_edit_field(self, action, names, values, rtype, eid, default): diff -r d5987f75c97c -r 7ded2a1416e4 web/views/boxes.py --- a/web/views/boxes.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/boxes.py Mon Jul 06 19:55:18 2009 +0200 @@ -18,10 +18,10 @@ from logilab.mtconverter import html_escape -from cubicweb.rtags import RelationTags from cubicweb.selectors import match_user_groups, non_final_entity +from cubicweb.view import EntityView +from cubicweb.schema import display_name from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem -from cubicweb.view import EntityView from cubicweb.web import uicfg from cubicweb.web.box import BoxTemplate diff -r d5987f75c97c -r 7ded2a1416e4 web/views/editcontroller.py --- a/web/views/editcontroller.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/editcontroller.py Mon Jul 06 19:55:18 2009 +0200 @@ -25,9 +25,8 @@ class EditController(ViewController): id = 'edit' - def publish(self, rset=None, fromjson=False): + def publish(self, rset=None): """edit / create / copy / delete entity / relations""" - self.fromjson = fromjson for key in self.req.form: # There should be 0 or 1 action if key.startswith('__action_'): @@ -113,9 +112,8 @@ entity = execute(rql, formparams).get_entity(0, 0) eid = entity.eid except ValidationError, ex: - # ex.entity may be an int or an entity instance self._to_create[formparams['eid']] = ex.entity - if self.fromjson: + if self.req.json_request: # XXX (syt) why? ex.entity = formparams['eid'] raise self._to_create[formparams['eid']] = eid diff -r d5987f75c97c -r 7ded2a1416e4 web/views/editforms.py --- a/web/views/editforms.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/editforms.py Mon Jul 06 19:55:18 2009 +0200 @@ -368,8 +368,13 @@ divid = '%s-%s-%s' % (peid, rtype, entity.eid) title = self.schema.rschema(rtype).display_name(self.req, role) removejs = self.removejs % (peid, rtype,entity.eid) + countkey = '%s_count' % rtype + try: + self.req.data[countkey] += 1 + except: + self.req.data[countkey] = 1 self.w(form.form_render(divid=divid, title=title, removejs=removejs, - **kwargs)) + counter=self.req.data[countkey], **kwargs)) def add_hiddens(self, form, entity, peid, rtype, role): # to ease overriding (see cubes.vcsfile.views.forms for instance) diff -r d5987f75c97c -r 7ded2a1416e4 web/views/facets.py --- a/web/views/facets.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/facets.py Mon Jul 06 19:55:18 2009 +0200 @@ -41,6 +41,8 @@ needs_css = 'cubicweb.facets.css' needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js') + bkLinkBox_template = u'
%s
' + def facetargs(self): """this method returns the list of extra arguments that should be used by the facet @@ -80,18 +82,8 @@ widgets.append(wdg) if not widgets: return + self.displayBookmarkLink(rset) w = self.w - eschema = self.schema.eschema('Bookmark') - if eschema.has_perm(req, 'add'): - bk_path = 'view?rql=%s' % rset.printable_rql() - bk_title = req._('my custom search') - linkto = 'bookmarked_by:%s:subject' % self.req.user.eid - bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto) - bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto) - w(u'' % ( - html_escape(bk_base_url), - html_escape(bk_add_url), - req._('bookmark this search'))) w(u'
' % ( divid, html_escape(dumps([divid, vid, paginate, self.facetargs()])))) w(u'
') @@ -109,6 +101,20 @@ import cubicweb cubicweb.info('after facets with rql: %s' % repr(rqlst)) + def displayBookmarkLink(self, rset): + eschema = self.schema.eschema('Bookmark') + if eschema.has_perm(self.req, 'add'): + bk_path = 'view?rql=%s' % rset.printable_rql() + bk_title = self.req._('my custom search') + linkto = 'bookmarked_by:%s:subject' % self.req.user.eid + bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto) + bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto) + bk_link = u'%s' % ( + html_escape(bk_base_url), + html_escape(bk_add_url), + self.req._('bookmark this search')) + self.w(self.bkLinkBox_template % bk_link) + def get_facets(self, rset, mainvar): return self.vreg.possible_vobjects('facets', self.req, rset=rset, context='facetbox', diff -r d5987f75c97c -r 7ded2a1416e4 web/views/formrenderers.py --- a/web/views/formrenderers.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/formrenderers.py Mon Jul 06 19:55:18 2009 +0200 @@ -503,7 +503,7 @@ w(u'
') values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema) w(u'
%(title)s ' - '#1 ' + '#%(counter)s ' '[%(removemsg)s]
' % values) # cleanup values diff -r d5987f75c97c -r 7ded2a1416e4 web/views/forms.py --- a/web/views/forms.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/forms.py Mon Jul 06 19:55:18 2009 +0200 @@ -119,8 +119,8 @@ def form_add_hidden(self, name, value=None, **kwargs): """add an hidden field to the form""" - field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value, - **kwargs) + kwargs.setdefault('widget', fwdgs.HiddenInput) + field = StringField(name=name, initial=value, **kwargs) if 'id' in kwargs: # by default, hidden input don't set id attribute. If one is # explicitly specified, ensure it will be set diff -r d5987f75c97c -r 7ded2a1416e4 web/views/management.py --- a/web/views/management.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/management.py Mon Jul 06 19:55:18 2009 +0200 @@ -14,7 +14,7 @@ from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user from cubicweb.view import AnyRsetView, StartupView, EntityView from cubicweb.common.uilib import html_traceback, rest_traceback -from cubicweb.web import formwidgets +from cubicweb.web import formwidgets as wdgs from cubicweb.web.formfields import guess_field SUBMIT_MSGID = _('Submit bug report') @@ -160,11 +160,9 @@ self.w(self.req._('no associated permissions')) def require_permission_edit_form(self, entity): - w = self.w - _ = self.req._ newperm = self.vreg.etype_class('CWPermission')(self.req, None) newperm.eid = self.req.varmaker.next() - w(u'

%s

' % _('add a new permission')) + self.w(u'

%s

' % self.req._('add a new permission')) form = self.vreg.select('forms', 'base', self.req, entity=newperm, form_buttons=[formwidgets.SubmitButton()], domid='reqperm%s' % entity.eid, @@ -176,7 +174,7 @@ cwpermschema = newperm.e_schema if permnames is not None: field = guess_field(cwpermschema, self.schema.rschema('name'), - widget=formwidgets.Select({'size': 1}), + widget=wdgs.Select({'size': 1}), choices=permnames) else: field = guess_field(cwpermschema, self.schema.rschema('name')) @@ -246,15 +244,17 @@ form = self.vreg.select('forms', 'base', self.req, rset=None, mainform=False) binfo = text_error_description(ex, excinfo, req, eversion, cversions) - form.form_add_hidden('description', binfo) + form.form_add_hidden('description', binfo, + # we must use a text area to keep line breaks + widget=wdgs.TextArea({'class': 'hidden'})) form.form_add_hidden('__bugreporting', '1') if submitmail: - form.form_buttons = [formwidgets.SubmitButton(MAIL_SUBMIT_MSGID)] + form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)] form.action = req.build_url('reportbug') w(form.form_render()) if submiturl: form.form_add_hidden('description_format', 'text/rest') - form.form_buttons = [formwidgets.SubmitButton(SUBMIT_MSGID)] + form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)] form.action = submiturl w(form.form_render()) diff -r d5987f75c97c -r 7ded2a1416e4 web/views/navigation.py diff -r d5987f75c97c -r 7ded2a1416e4 web/views/primary.py --- a/web/views/primary.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/primary.py Mon Jul 06 19:55:18 2009 +0200 @@ -52,7 +52,6 @@ boxes = self._prepare_side_boxes(entity) if boxes or hasattr(self, 'render_side_related'): self.w(u'
') - self.w(u'
') self.w(u'
') self.content_navigation_components('navcontenttop') try: @@ -63,7 +62,6 @@ warn('siderelations argument of render_entity_attributes is ' 'deprecated (%s)' % self.__class__) self.render_entity_attributes(entity, []) - self.w(u'
') if self.main_related_section: try: self.render_entity_relations(entity) @@ -74,9 +72,9 @@ 'deprecated') self.render_entity_relations(entity, []) self.w(u'
') + # side boxes if boxes or hasattr(self, 'render_side_related'): self.w(u'
') - # side boxes self.w(u'
') if hasattr(self, 'render_side_related'): warn('render_side_related is deprecated') diff -r d5987f75c97c -r 7ded2a1416e4 web/views/schema.py --- a/web/views/schema.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/schema.py Mon Jul 06 19:55:18 2009 +0200 @@ -248,12 +248,7 @@ ### facets -class CWMetaFacet(AttributeFacet): - id = 'cwmeta-facet' - __select__ = AttributeFacet.__select__ & implements('CWEType') - rtype = 'meta' - class CWFinalFacet(AttributeFacet): id = 'cwfinal-facet' - __select__ = AttributeFacet.__select__ & implements('CWEType') + __select__ = AttributeFacet.__select__ & implements('CWEType', 'CWRType') rtype = 'final' diff -r d5987f75c97c -r 7ded2a1416e4 web/views/startup.py --- a/web/views/startup.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/startup.py Mon Jul 06 19:55:18 2009 +0200 @@ -14,7 +14,7 @@ from cubicweb.view import StartupView from cubicweb.selectors import match_user_groups, implements -from cubicweb.schema import META_RELATIONS_TYPES +from cubicweb.schema import META_RELATIONS_TYPES, display_name from cubicweb.common.uilib import ureport_as_html from cubicweb.web import ajax_replace_url, uicfg, httpcache from cubicweb.web.views import tabs, management, schema as schemamod diff -r d5987f75c97c -r 7ded2a1416e4 web/views/tabs.py --- a/web/views/tabs.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/tabs.py Mon Jul 06 19:55:18 2009 +0200 @@ -24,7 +24,7 @@ """ def _prepare_bindings(self, vid, reloadable): - self.req.html_headers.add_onload(u""" + self.req.add_onload(u""" jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) { load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s); });""" % {'event': 'load_%s' % vid, 'vid': vid, @@ -59,7 +59,7 @@ on dom readyness """ self.req.add_js('cubicweb.lazy.js') - self.req.html_headers.add_onload("trigger_load('%s');" % vid) + self.req.add_onload("trigger_load('%s');" % vid) class TabsMixin(LazyViewMixin): @@ -93,22 +93,11 @@ return selected_tabs def render_tabs(self, tabs, default, entity=None): - # tabbed views do no support concatenation - # hence we delegate to the default tab if there is more than on entity - # in the result set + # delegate to the default tab if there is more than one entity + # in the result set (tabs are pretty useless there) if entity and len(self.rset) > 1: entity.view(default, w=self.w) return - # XXX (syt) fix below add been introduced at some point to fix something - # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean - # way. We must not consider form['rql'] here since it introduces some - # other failures on non rql queries (plain text, shortcuts,... handled by - # magicsearch) which has a single result whose primary view is using tabs - # (https://www.logilab.net/cwo/ticket/342789) - #rql = self.req.form.get('rql') - #if rql: - # self.req.execute(rql).get_entity(0,0).view(default, w=self.w) - # return self.req.add_css('tabs-no-images.css') self.req.add_js(('jquery.tools.min.js', 'cubicweb.htmlhelpers.js', 'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js')) @@ -145,11 +134,11 @@ w(u'
') # call the set_tab() JS function *after* each tab is generated # because the callback binding needs to be done before - self.req.html_headers.add_onload(u""" + self.req.add_onload(u''' jQuery(function() { jQuery("#tabs-%(eeid)s").tabs("#panes-%(eeid)s > div", {initialIndex: %(tabindex)s}); set_tab('%(vid)s', '%(cookiename)s'); - });""" % {'eeid' : entity.eid, + });''' % {'eeid' : entity.eid, 'vid' : active_tab, 'cookiename' : self.cookie_name, 'tabindex' : tabs.index(active_tab)}) diff -r d5987f75c97c -r 7ded2a1416e4 web/views/urlpublishing.py --- a/web/views/urlpublishing.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/views/urlpublishing.py Mon Jul 06 19:55:18 2009 +0200 @@ -144,18 +144,12 @@ [[/]/]* """ priority = 2 - def __init__(self, urlpublisher): - super(RestPathEvaluator, self).__init__(urlpublisher) - self.etype_map = {} - for etype in self.schema.entities(): - etype = str(etype) - self.etype_map[etype.lower()] = etype def evaluate_path(self, req, parts): if not (0 < len(parts) < 4): raise PathDontMatch() try: - etype = self.etype_map[parts.pop(0).lower()] + etype = self.vreg.case_insensitive_etypes[parts.pop(0).lower()] except KeyError: raise PathDontMatch() cls = self.vreg.etype_class(etype) diff -r d5987f75c97c -r 7ded2a1416e4 web/webconfig.py --- a/web/webconfig.py Thu Jul 02 10:36:25 2009 +0200 +++ b/web/webconfig.py Mon Jul 06 19:55:18 2009 +0200 @@ -80,12 +80,6 @@ 'if anonymous-user is set', 'group': 'main', 'inputlevel': 1, }), - ('allow-email-login', - {'type' : 'yn', - 'default': False, - 'help': 'allow users to login with their primary email if set', - 'group': 'main', 'inputlevel': 2, - }), ('query-log-file', {'type' : 'string', 'default': None,