merge
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 05 Jun 2009 10:26:35 +0200
changeset 2051 e4d24e4d74e6
parent 2050 ce184fdb1e56 (current diff)
parent 2047 6ab574aa5e5f (diff)
child 2052 0b9b0bdc93f5
child 2057 0a0cbccafcb5
merge
web/views/cwproperties.py
web/views/management.py
--- a/.hgtags	Fri Jun 05 10:23:56 2009 +0200
+++ b/.hgtags	Fri Jun 05 10:26:35 2009 +0200
@@ -38,3 +38,5 @@
 0e07514264aa1b0b671226f41725ea4c066c210a cubicweb-debian-version-3_2_2-1
 f60bb84b86cf371f1f25197e00c778b469297721 cubicweb-version-3_2_3
 4003d24974f15f17bd03b7efd6a5047cad4e4c41 cubicweb-debian-version-3_2_3-1
+2d7d3062ca03d4b4144100013dc4ab7f9d9cb25e cubicweb-version-3_3_0
+07214e923e75c8f0490e609e9bee0f4964b87114 cubicweb-debian-version-3_3_0-1
--- a/__pkginfo__.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/__pkginfo__.py	Fri Jun 05 10:26:35 2009 +0200
@@ -7,7 +7,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 2, 3)
+numversion = (3, 3, 0)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL v2'
--- a/cwctl.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/cwctl.py	Fri Jun 05 10:26:35 2009 +0200
@@ -200,7 +200,7 @@
                                            or tinfo.__doc__)
                     if shortdesc:
                         print '    '+ '    \n'.join(shortdesc.splitlines())
-                    modes = detect_available_modes(CubicWebConfiguration.cube_dir(cube))
+                    modes = detect_available_modes(cwcfg.cube_dir(cube))
                     print '    available modes: %s' % ', '.join(modes)
         print
         try:
--- a/cwvreg.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/cwvreg.py	Fri Jun 05 10:26:35 2009 +0200
@@ -114,7 +114,12 @@
 
     def register_objects(self, path, force_reload=None):
         """overriden to remove objects requiring a missing interface"""
-        if super(CubicWebRegistry, self).register_objects(path, force_reload):
+        extrapath = {}
+        for cubesdir in self.config.cubes_search_path():
+            if cubesdir != self.config.CUBES_DIR:
+                extrapath[cubesdir] = 'cubes'
+        if super(CubicWebRegistry, self).register_objects(path, force_reload,
+                                                          extrapath):
             self.initialization_completed()
             # call vreg_initialization_completed on appobjects and print
             # registry content
--- a/debian/changelog	Fri Jun 05 10:23:56 2009 +0200
+++ b/debian/changelog	Fri Jun 05 10:26:35 2009 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.3.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr>  Wed, 03 Jun 2009 19:52:37 +0200
+
 cubicweb (3.2.3-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Fri Jun 05 10:23:56 2009 +0200
+++ b/debian/control	Fri Jun 05 10:26:35 2009 +0200
@@ -4,7 +4,8 @@
 Maintainer: Logilab S.A. <contact@logilab.fr>
 Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>,
            Julien Jehannet <julien.jehannet@logilab.fr>,
-           Aurélien Campéas <aurelien.campeas@logilab.fr>
+           Aurélien Campéas <aurelien.campeas@logilab.fr>,
+           Nicolas Chauvat <nicolas.chauvat@logilab.fr>
 Build-Depends: debhelper (>= 7), python-dev (>=2.4), python-central (>= 0.5)
 Standards-Version: 3.8.0
 Homepage: http://www.cubicweb.org
@@ -75,7 +76,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.40.0), python-yams (>= 0.22.0), python-rql (>= 0.22.0)
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.0)
 Recommends: python-simpletal (>= 4.0), python-lxml
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/doc/book/en/annexes/rql/language.rst	Fri Jun 05 10:23:56 2009 +0200
+++ b/doc/book/en/annexes/rql/language.rst	Fri Jun 05 10:26:35 2009 +0200
@@ -299,7 +299,7 @@
 
 
   Note: You can not specify several types with * ... where X is FirstType or X is SecondType*.
-  To specify several types explicitely, you have to do
+  To specify several types explicitly, you have to do
 
   ::
 
--- a/doc/book/en/development/devcore/vreg.rst	Fri Jun 05 10:23:56 2009 +0200
+++ b/doc/book/en/development/devcore/vreg.rst	Fri Jun 05 10:26:35 2009 +0200
@@ -13,7 +13,10 @@
   conditionnellement,
   - enregistrement explicite en définissant la fonction `registration_callback(vreg)`
   - appel des méthodes d'enregistrement des objets sur le vreg
-
+.. note::
+    Once the function `registration_callback(vreg)` is implemented, all the objects
+    need to be explicitly registered as it disables the automatic object registering.
+    
 * suppression de l'ancien système quand il ne restera plus de réference au
   module registerers dans le code des cubes existants.
 
@@ -148,4 +151,4 @@
 
 Debugging
 `````````
-XXX explain traced_selection context manager
\ No newline at end of file
+XXX explain traced_selection context manager
--- a/doc/book/en/development/webstdlib/boxes.rst	Fri Jun 05 10:23:56 2009 +0200
+++ b/doc/book/en/development/webstdlib/boxes.rst	Fri Jun 05 10:26:35 2009 +0200
@@ -12,7 +12,7 @@
 an entity automatically related to the initial entity (context in
 which the box is displayed). By default, the links generated in this
 box are computed from the schema properties of the displayed entity,
-but it is possible to explicitely specify them thanks to the
+but it is possible to explicitly specify them thanks to the
 `cubicweb.web.uicfg.rmode` *relation tag*:
 
 * `link`, indicates that a relation is in general created pointing
--- a/server/schemahooks.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/server/schemahooks.py	Fri Jun 05 10:26:35 2009 +0200
@@ -14,7 +14,7 @@
 
 from yams.schema import BASE_TYPES
 from yams.buildobjs import EntityType, RelationType, RelationDefinition
-from yams.schema2sql import eschema2sql, rschema2sql, _type_from_constraints
+from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
 
 from cubicweb import ValidationError, RepositoryError
 from cubicweb.server import schemaserial as ss
@@ -393,8 +393,8 @@
                                   constraints=constraints,
                                   eid=entity.eid)
         sysource = session.pool.source('system')
-        attrtype = _type_from_constraints(sysource.dbhelper, rdef.object,
-                                          constraints)
+        attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
+                                         constraints)
         # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
         # add a new column with UNIQUE, it should be added after the ALTER TABLE
         # using ADD INDEX
@@ -423,20 +423,6 @@
             except Exception, ex:
                 self.error('error while creating index for %s.%s: %s',
                            table, column, ex)
-        # postgres doesn't implement, so do it in two times
-        # ALTER TABLE %s ADD COLUMN %s %s SET DEFAULT %s
-        if default is not None:
-            if isinstance(default, unicode):
-                default = default.encode(sysource.encoding)
-            try:
-                session.system_sql('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT '
-                                   '%%(default)s' % (table, column),
-                                   {'default': default})
-            except Exception, ex:
-                # not supported by sqlite for instance
-                self.error('error while altering table %s: %s', table, ex)
-            session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
-                               {'default': default})
         AddErdefOp(session, rdef)
 
 def after_add_efrdef(session, entity):
@@ -567,28 +553,28 @@
     rschema = values = None # make pylint happy
 
     def precommit_event(self):
+        etype = self.kobj[0]
+        table = SQL_PREFIX + etype
+        column = SQL_PREFIX + self.rschema.type
         if 'indexed' in self.values:
             sysource = self.session.pool.source('system')
-            etype, rtype = self.kobj[0], self.rschema.type
-            table = SQL_PREFIX + etype
-            column = SQL_PREFIX + rtype
             if self.values['indexed']:
                 sysource.create_index(self.session, table, column)
             else:
                 sysource.drop_index(self.session, table, column)
         if 'cardinality' in self.values and self.rschema.is_final():
-            if self.session.pool.source('system').dbdriver == 'sqlite':
+            adbh = self.session.pool.source('system').dbhelper
+            if not adbh.alter_column_support:
                 # not supported (and NOT NULL not set by yams in that case, so
                 # no worry)
                 return
-            sqlexec = self.session.system_sql
-            etype, rtype = self.kobj[0], self.rschema.type
-            if self.values['cardinality'][0] == '1':
-                cmd = 'SET'
-            else:
-                cmd = 'DROP'
-            sqlexec('ALTER TABLE %s ALTER COLUMN %s %s NOT NULL' % (
-                table, SQL_PREFIX + etype, SQL_PREFIX + rtype))
+            atype = self.rschema.objects(etype)[0]
+            constraints = self.rschema.rproperty(etype, atype, 'constraints')
+            coltype = type_from_constraints(adbh, atype, constraints,
+                                            creating=False)
+            sql = adbh.sql_set_null_allowed(table, column, coltype,
+                                            self.values['cardinality'][0] != '1')
+            self.session.system_sql(sql)
 
     def commit_event(self):
         # structure should be clean, not need to remove entity's relations
@@ -726,9 +712,13 @@
         # alter the physical schema on size constraint changes
         if self._cstr.type() == 'SizeConstraint' and (
             self.cstr is None or self.cstr.max != self._cstr.max):
+            adbh = self.session.pool.source('system').dbhelper
+            card = rtype.rproperty(subjtype, objtype, 'cardinality')
+            coltype = type_from_constraints(adbh, objtype, [self._cstr],
+                                            creating=False)
+            sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
             try:
-                session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE VARCHAR(%s)'
-                                   % (table, column, self._cstr.max))
+                session.system_sql(sql)
                 self.info('altered column %s of table %s: now VARCHAR(%s)',
                           column, table, self._cstr.max)
             except Exception, ex:
@@ -741,7 +731,7 @@
     def commit_event(self):
         if self.cancelled:
             return
-        # in-place removing
+        # in-place modification
         if not self.cstr is None:
             self.constraints.remove(self.cstr)
         self.constraints.append(self._cstr)
--- a/server/schemaserial.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/server/schemaserial.py	Fri Jun 05 10:26:35 2009 +0200
@@ -83,6 +83,9 @@
                 sqlcu.execute(sql)
 
 # schema / perms deserialization ##############################################
+OLD_SCHEMA_TYPES = frozenset(('EFRDef', 'ENFRDef', 'ERType', 'EEType',
+                              'EConstraintType', 'EConstraint', 'EGroup',
+                              'EUser', 'ECache', 'EPermission', 'EProperty'))
 
 def deserialize_schema(schema, session):
     """return a schema according to information stored in an rql database
@@ -92,16 +95,15 @@
     repo = session.repo
     sqlcu = session.pool['system']
     _3_2_migration = False
-    tables = set(t.lower() for t in repo.system_source.dbhelper.list_tables(sqlcu))
+    dbhelper = repo.system_source.dbhelper
+    tables = set(t.lower() for t in dbhelper.list_tables(sqlcu))
     if 'eetype' in tables:
         _3_2_migration = True
         # 3.2 migration
         _set_sql_prefix('')
         # first rename entity types whose name changed in 3.2 without adding the
         # cw_ prefix
-        for etype in ('EFRDef', 'ENFRDef', 'ERType', 'EEType',
-                      'EConstraintType', 'EConstraint', 'EGroup', 'EUser',
-                      'ECache', 'EPermission', 'EProperty'):
+        for etype in OLD_SCHEMA_TYPES:
             if etype.lower() in tables:
                 sql = 'ALTER TABLE %s RENAME TO %s' % (etype,
                                                        ETYPE_NAME_MAP[etype])
@@ -123,18 +125,25 @@
             eschema.eid = eid
             index[eid] = eschema
             continue
-        if etype in ETYPE_NAME_MAP: # XXX <2.45 bw compat
-            print 'fixing etype name from %s to %s' % (etype, ETYPE_NAME_MAP[etype])
+        if etype in ETYPE_NAME_MAP:
+            netype = ETYPE_NAME_MAP[etype]
+            print 'fixing etype name from %s to %s' % (etype, netype)
             # can't use write rql queries at this point, use raw sql
             session.system_sql('UPDATE %(p)sCWEType SET %(p)sname=%%(n)s WHERE %(p)seid=%%(x)s'
                                % {'p': sqlutils.SQL_PREFIX},
-                               {'x': eid, 'n': ETYPE_NAME_MAP[etype]})
+                               {'x': eid, 'n': netype})
             session.system_sql('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
-                               {'x': etype, 'n': ETYPE_NAME_MAP[etype]})
+                               {'x': etype, 'n': netype})
+            # XXX should be donne as well on sqlite based sources
+            if not etype in OLD_SCHEMA_TYPES and \
+               (getattr(dbhelper, 'case_sensitive', False) 
+                or etype.lower() != netype.lower()):
+                session.system_sql('ALTER TABLE %s%s RENAME TO %s%s' % (
+                    sqlutils.SQL_PREFIX, etype, sqlutils.SQL_PREFIX, netype))
             session.commit(False)
             try:
                 session.system_sql('UPDATE deleted_entities SET type=%(n)s WHERE type=%(x)s',
-                                   {'x': etype, 'n': ETYPE_NAME_MAP[etype]})
+                                   {'x': etype, 'n': netype})
             except:
                 pass
             tocleanup = [eid]
@@ -142,7 +151,7 @@
                           if etype == eidetype)
             repo.clear_caches(tocleanup)
             session.commit(False)
-            etype = ETYPE_NAME_MAP[etype]
+            etype = netype
         etype = ybo.EntityType(name=etype, description=desc, meta=meta, eid=eid)
         eschema = schema.add_entity_type(etype)
         index[eid] = eschema
--- a/test/unittest_cwconfig.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/test/unittest_cwconfig.py	Fri Jun 05 10:26:35 2009 +0200
@@ -33,18 +33,18 @@
     def test_reorder_cubes(self):
         # jpl depends on email and file and comment
         # email depends on file
-        self.assertEquals(self.config.reorder_cubes(['file', 'email', 'jpl']),
-                          ('jpl', 'email', 'file'))
-        self.assertEquals(self.config.reorder_cubes(['email', 'file', 'jpl']),
-                          ('jpl', 'email', 'file'))
-        self.assertEquals(self.config.reorder_cubes(['email', 'jpl', 'file']),
-                          ('jpl', 'email', 'file'))
-        self.assertEquals(self.config.reorder_cubes(['file', 'jpl', 'email']),
-                          ('jpl', 'email', 'file'))
-        self.assertEquals(self.config.reorder_cubes(['jpl', 'file', 'email']),
-                          ('jpl', 'email', 'file'))
-        self.assertEquals(self.config.reorder_cubes(('jpl', 'email', 'file')),
-                          ('jpl', 'email', 'file'))
+        self.assertEquals(self.config.reorder_cubes(['file', 'email', 'forge']),
+                          ('forge', 'email', 'file'))
+        self.assertEquals(self.config.reorder_cubes(['email', 'file', 'forge']),
+                          ('forge', 'email', 'file'))
+        self.assertEquals(self.config.reorder_cubes(['email', 'forge', 'file']),
+                          ('forge', 'email', 'file'))
+        self.assertEquals(self.config.reorder_cubes(['file', 'forge', 'email']),
+                          ('forge', 'email', 'file'))
+        self.assertEquals(self.config.reorder_cubes(['forge', 'file', 'email']),
+                          ('forge', 'email', 'file'))
+        self.assertEquals(self.config.reorder_cubes(('forge', 'email', 'file')),
+                          ('forge', 'email', 'file'))
 
     def test_reorder_cubes_recommends(self):
         from cubes.comment import __pkginfo__ as comment_pkginfo
@@ -52,14 +52,14 @@
         try:
             # email recommends comment
             # comment recommends file
-            self.assertEquals(self.config.reorder_cubes(('jpl', 'email', 'file', 'comment')),
-                              ('jpl', 'email', 'comment', 'file'))
-            self.assertEquals(self.config.reorder_cubes(('jpl', 'email', 'comment', 'file')),
-                              ('jpl', 'email', 'comment', 'file'))
-            self.assertEquals(self.config.reorder_cubes(('jpl', 'comment', 'email', 'file')),
-                              ('jpl', 'email', 'comment', 'file'))
-            self.assertEquals(self.config.reorder_cubes(('comment', 'jpl', 'email', 'file')),
-                              ('jpl', 'email', 'comment', 'file'))
+            self.assertEquals(self.config.reorder_cubes(('forge', 'email', 'file', 'comment')),
+                              ('forge', 'email', 'comment', 'file'))
+            self.assertEquals(self.config.reorder_cubes(('forge', 'email', 'comment', 'file')),
+                              ('forge', 'email', 'comment', 'file'))
+            self.assertEquals(self.config.reorder_cubes(('forge', 'comment', 'email', 'file')),
+                              ('forge', 'email', 'comment', 'file'))
+            self.assertEquals(self.config.reorder_cubes(('comment', 'forge', 'email', 'file')),
+                              ('forge', 'email', 'comment', 'file'))
         finally:
             comment_pkginfo.__use__ = ()
 
--- a/utils.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/utils.py	Fri Jun 05 10:26:35 2009 +0200
@@ -10,7 +10,7 @@
 import locale
 from md5 import md5
 from datetime import datetime, timedelta, date
-from time import time
+from time import time, mktime
 from random import randint, seed
 from calendar import monthrange
 
@@ -38,6 +38,9 @@
     assert isinstance(somedate, date), repr(somedate)
     return datetime(somedate.year, somedate.month, somedate.day)
 
+def datetime2ticks(date):
+    return mktime(date.timetuple()) * 1000
+
 ONEDAY = timedelta(days=1)
 ONEWEEK = timedelta(days=7)
 
@@ -100,7 +103,7 @@
 
 def make_uid(key):
     """forge a unique identifier"""
-    msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
+    msg = str(key) + "%.10f" % time() + str(randint(0, 1000000))
     return md5(msg).hexdigest()
 
 
--- a/vregistry.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/vregistry.py	Fri Jun 05 10:26:35 2009 +0200
@@ -31,19 +31,20 @@
 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
 
 
-def _toload_info(path, _toload=None):
+def _toload_info(path, extrapath, _toload=None):
     """return a dictionary of <modname>: <modpath> and an ordered list of
     (file, module name) to load
     """
     from logilab.common.modutils import modpath_from_file
     if _toload is None:
+        assert isinstance(path, list)
         _toload = {}, []
     for fileordir in path:
         if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
             subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
-            _toload_info(subfiles, _toload)
+            _toload_info(subfiles, extrapath, _toload)
         elif fileordir[-3:] == '.py':
-            modname = '.'.join(modpath_from_file(fileordir))
+            modname = '.'.join(modpath_from_file(fileordir, extrapath))
             _toload[0][modname] = fileordir
             _toload[1].append((fileordir, modname))
     return _toload
@@ -313,13 +314,13 @@
 
     # intialization methods ###################################################
 
-    def init_registration(self, path):
+    def init_registration(self, path, extrapath=None):
         # compute list of all modules that have to be loaded
-        self._toloadmods, filemods = _toload_info(path)
+        self._toloadmods, filemods = _toload_info(path, extrapath)
         self._loadedmods = {}
         return filemods
 
-    def register_objects(self, path, force_reload=None):
+    def register_objects(self, path, force_reload=None, extrapath=None):
         if force_reload is None:
             force_reload = self.config.mode == 'dev'
         elif not force_reload:
@@ -339,7 +340,7 @@
         if CW_SOFTWARE_ROOT in sys.path:
             sys.path.remove(CW_SOFTWARE_ROOT)
         # load views from each directory in the application's path
-        filemods = self.init_registration(path)
+        filemods = self.init_registration(path, extrapath)
         change = False
         for filepath, modname in filemods:
             if self.load_file(filepath, modname, force_reload):
Binary file web/data/black-uncheck.png has changed
--- a/web/data/cubicweb.formfilter.js	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/data/cubicweb.formfilter.js	Fri Jun 05 10:26:35 2009 +0200
@@ -103,7 +103,7 @@
 
 var SELECTED_IMG = baseuri()+"data/black-check.png";
 var UNSELECTED_IMG = baseuri()+"data/no-check-no-border.png";
-var UNSELECTED_BORDER_IMG = baseuri()+"data/black-unchecked.png";
+var UNSELECTED_BORDER_IMG = baseuri()+"data/black-uncheck.png";
 
 function initFacetBoxEvents(root) {
     // facetargs : (divid, vid, paginate, extraargs)
@@ -133,7 +133,7 @@
 			       this.setAttribute('src', UNSELECTED_BORDER_IMG);
 			    }
 			    else{
-                             this.setAttribute('src', UNSELECTED_IMG);
+			       this.setAttribute('src', UNSELECTED_IMG);
 			    }
 			});
 			var index = parseInt($this.attr('cubicweb:idx'));
@@ -143,7 +143,11 @@
 			}).length;
 			index += shift;
 			var parent = this.parentNode;
-			jQuery(parent).find('.facetCheckBox:nth('+index+')').after(this);
+			var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
+			if ( ! ($insertAfter.length == 1 && index == 0) ) {
+			    // only rearrange element if necessary
+			    $insertAfter.after(this);
+			}
 		    } else {
 			var lastSelected = facet.find('.facetValueSelected:last');
 			if (lastSelected.length) {
--- a/web/data/cubicweb.python.js	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/data/cubicweb.python.js	Fri Jun 05 10:26:35 2009 +0200
@@ -21,23 +21,23 @@
     var res = new Date()
     res.setTime(this.getTime() + (days * ONE_DAY))
     return res
-}
+};
 
 Date.prototype.sub = function(days) {
     return this.add(-days);
-}
+};
 
 Date.prototype.iadd = function(days) {
     // in-place add
     this.setTime(this.getTime() + (days * ONE_DAY))
     // avoid strange rounding problems !!
     this.setHours(12);
-}
+};
 
 Date.prototype.isub = function(days) {
     // in-place sub
     this.setTime(this.getTime() - (days * ONE_DAY))
-}
+};
 
 /*
  * returns the first day of the next month
@@ -50,7 +50,7 @@
 	var d2 = new Date(this.getFullYear(), this.getMonth()+1, 1);
 	return d2;
     }
-}
+};
 
 /*
  * returns the day of week, 0 being monday, 6 being sunday
@@ -58,8 +58,15 @@
 Date.prototype.getRealDay = function() {
     // getDay() returns 0 for Sunday ==> 6 for Saturday
     return (this.getDay()+6) % 7;
-}
+};
 
+Date.prototype.strftime = function(fmt) {
+    if (this.toLocaleFormat !== undefined) { // browser dependent
+	return this.toLocaleFormat(fmt);
+    }
+    // XXX implement at least a decent fallback implementation
+    return this.getFullYear() + '/' + (this.getMonth()+1) + '/' + this.getDate();
+};
 
 var _DATE_FORMAT_REGXES = {
     'Y': new RegExp('^-?[0-9]+'),
--- a/web/facet.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/facet.py	Fri Jun 05 10:26:35 2009 +0200
@@ -10,6 +10,7 @@
 
 from itertools import chain
 from copy import deepcopy
+from datetime import date
 
 from logilab.mtconverter import html_escape
 
@@ -20,7 +21,7 @@
 from rql import parse, nodes
 
 from cubicweb import Unauthorized, typed_eid
-from cubicweb.utils import make_uid
+from cubicweb.utils import datetime2ticks, make_uid, ustrftime
 from cubicweb.selectors import match_context_prop, partial_relation_possible
 from cubicweb.appobject import AppRsetObject
 from cubicweb.web.htmlwidgets import HTMLWidget
@@ -476,29 +477,6 @@
                                             self.attrtype, self.comparator)
 
 
-class RangeFacet(AttributeFacet):
-
-    def get_widget(self):
-        """return the widget instance to use to display this facet
-        """
-        values = set(value for _, value in self.vocabulary() if value is not None)
-        return FacetRangeWidget(self, min(values), max(values))
-
-    def add_rql_restrictions(self):
-        infvalue = self.req.form.get('%s_inf' % self.id)
-        if not infvalue:
-            return
-        supvalue = self.req.form.get('%s_sup' % self.id)
-        self.rqlst.add_constant_restriction(self.filtered_variable,
-                                            self.rtype,
-                                            u'%s' % infvalue,
-                                            'Float', '>=')
-        self.rqlst.add_constant_restriction(self.filtered_variable,
-                                            self.rtype,
-                                            u'%s' % supvalue,
-                                            'Float', '<=')
-
-
 class FilterRQLBuilder(object):
     """called by javascript to get a rql string from filter form"""
 
@@ -519,6 +497,83 @@
         return select.as_string(), toupdate
 
 
+class RangeFacet(AttributeFacet):
+    attrtype = 'Float' # only numerical types are supported
+
+    @property
+    def wdgclass(self):
+        return FacetRangeWidget
+
+    def get_widget(self):
+        """return the widget instance to use to display this facet
+        """
+        values = set(value for _, value in self.vocabulary() if value is not None)
+        return self.wdgclass(self, min(values), max(values))
+
+    def infvalue(self):
+        return self.req.form.get('%s_inf' % self.id)
+
+    def supvalue(self):
+        return self.req.form.get('%s_sup' % self.id)
+
+    def formatvalue(self, value):
+        """format `value` before in order to insert it in the RQL query"""
+        return unicode(value)
+
+    def add_rql_restrictions(self):
+        infvalue = self.infvalue()
+        if infvalue is None: # nothing sent
+            return
+        supvalue = self.supvalue()
+        self.rqlst.add_constant_restriction(self.filtered_variable,
+                                            self.rtype,
+                                            self.formatvalue(infvalue),
+                                            self.attrtype, '>=')
+        self.rqlst.add_constant_restriction(self.filtered_variable,
+                                            self.rtype,
+                                            self.formatvalue(supvalue),
+                                            self.attrtype, '<=')
+
+class DateRangeFacet(RangeFacet):
+    attrtype = 'Date' # only date types are supported
+
+    @property
+    def wdgclass(self):
+        return DateFacetRangeWidget
+
+    def formatvalue(self, value):
+        """format `value` before in order to insert it in the RQL query"""
+        return '"%s"' % date.fromtimestamp(float(value) / 1000).strftime('%Y/%m/%d')
+
+
+class HasRelationFacet(AbstractFacet):
+    rtype = None # override me in subclass
+    role = 'subject' # role of filtered entity in the relation
+
+    @property
+    def title(self):
+        return display_name(self.req, self.rtype, self.role)
+
+    def support_and(self):
+        return False
+
+    def get_widget(self):
+        return CheckBoxFacetWidget(self.req, self,
+                                   '%s:%s' % (self.rtype, self),
+                                   self.req.form.get(self.id))
+
+    def add_rql_restrictions(self):
+        """add restriction for this facet into the rql syntax tree"""
+        self.rqlst.set_distinct(True) # XXX
+        value = self.req.form.get(self.id)
+        if not value: # no value sent for this facet
+            return
+        var = self.rqlst.make_variable()
+        if self.role == 'subject':
+            self.rqlst.add_relation(self.filtered_variable, self.rtype, var)
+        else:
+            self.rqlst.add_relation(var, self.rtype, self.filtered_variable)
+
 ## html widets ################################################################
 
 class FacetVocabularyWidget(HTMLWidget):
@@ -571,24 +626,30 @@
 
 
 class FacetRangeWidget(HTMLWidget):
+    formatter = 'function (value) {return value;}'
     onload = u'''
+    var _formatter = %(formatter)s;
     jQuery("#%(sliderid)s").slider({
-    	range: true,
-	min: %(minvalue)s,
-	max: %(maxvalue)s,
+        range: true,
+        min: %(minvalue)s,
+        max: %(maxvalue)s,
         values: [%(minvalue)s, %(maxvalue)s],
         stop: function(event, ui) { // submit when the user stops sliding
            var form = $('#%(sliderid)s').closest('form');
            buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
         },
-    	slide: function(event, ui) {
-            $('#%(sliderid)s_inf').html(ui.values[0]);
-            $('#%(sliderid)s_sup').html(ui.values[1]);
-            $('input[name=%(facetid)s_inf]').val(ui.values[0]);
-            $('input[name=%(facetid)s_sup]').val(ui.values[1]);
-    	}
+        slide: function(event, ui) {
+            jQuery('#%(sliderid)s_inf').html(_formatter(ui.values[0]));
+            jQuery('#%(sliderid)s_sup').html(_formatter(ui.values[1]));
+            jQuery('input[name=%(facetid)s_inf]').val(ui.values[0]);
+            jQuery('input[name=%(facetid)s_sup]').val(ui.values[1]);
+        }
    });
+   // use JS formatter to format value on page load
+   jQuery('#%(sliderid)s_inf').html(_formatter(jQuery('input[name=%(facetid)s_inf]').val()));
+   jQuery('#%(sliderid)s_sup').html(_formatter(jQuery('input[name=%(facetid)s_sup]').val()));
 '''
+    #'# make emacs happier
     def __init__(self, facet, minvalue, maxvalue):
         self.facet = facet
         self.minvalue = minvalue
@@ -601,17 +662,18 @@
         sliderid = make_uid('the slider')
         facetid = html_escape(self.facet.id)
         facet.req.html_headers.add_onload(self.onload % {
-                'sliderid': sliderid,
-                'facetid': facetid,
-                'minvalue': self.minvalue,
-                'maxvalue': self.maxvalue,
-                })
+            'sliderid': sliderid,
+            'facetid': facetid,
+            'minvalue': self.minvalue,
+            'maxvalue': self.maxvalue,
+            'formatter': self.formatter,
+            })
         title = html_escape(self.facet.title)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
                (facetid, title))
-        self.w(u'<span id="%s_inf">%s</span> - <span id="%s_sup">%s</span>'
-               % (sliderid, self.minvalue, sliderid, self.maxvalue))
+        self.w(u'<span id="%s_inf"></span> - <span id="%s_sup"></span>'
+               % (sliderid, sliderid))
         self.w(u'<input type="hidden" name="%s_inf" value="%s" />'
                % (facetid, self.minvalue))
         self.w(u'<input type="hidden" name="%s_sup" value="%s" />'
@@ -620,6 +682,16 @@
         self.w(u'</div>\n')
 
 
+class DateFacetRangeWidget(FacetRangeWidget):
+    formatter = 'function (value) {return (new Date(parseFloat(value))).strftime(DATE_FMT);}'
+    def __init__(self, facet, minvalue, maxvalue):
+        super(DateFacetRangeWidget, self).__init__(facet,
+                                                   datetime2ticks(minvalue),
+                                                   datetime2ticks(maxvalue))
+        fmt = facet.req.property_value('ui.date-format')
+        facet.req.html_headers.define_var('DATE_FMT', fmt)
+
+
 class FacetItem(HTMLWidget):
 
     selected_img = "black-check.png"
@@ -646,6 +718,36 @@
         self.w(u'<a href="javascript: {}">%s</a>' % html_escape(self.label))
         self.w(u'</div>')
 
+class CheckBoxFacetWidget(HTMLWidget):
+    selected_img = "black-check.png"
+    unselected_img = "black-uncheck.png"
+
+    def __init__(self, req, facet, value, selected):
+        self.req = req
+        self.facet = facet
+        self.value = value
+        self.selected = selected
+
+    def _render(self):
+        title = html_escape(self.facet.title)
+        facetid = html_escape(self.facet.id)
+        self.w(u'<div id="%s" class="facet">\n' % facetid)
+        if self.selected:
+            cssclass = ' facetValueSelected'
+            imgsrc = self.req.datadir_url + self.selected_img
+            imgalt = self.req._('selected')
+        else:
+            cssclass = ''
+            imgsrc = self.req.datadir_url + self.unselected_img
+            imgalt = self.req._('not selected')
+        self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
+               % (cssclass, html_escape(unicode(self.value))))
+        self.w(u'<div class="facetCheckBoxWidget">')
+        self.w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" />&nbsp;' % (imgsrc, imgalt))
+        self.w(u'<label class="facetTitle" cubicweb:facetName="%s"><a href="javascript: {}">%s</a></label>' % (facetid,title))
+        self.w(u'</div>\n')
+        self.w(u'</div>\n')
+        self.w(u'</div>\n')
 
 class FacetSeparator(HTMLWidget):
     def __init__(self, label=None):
@@ -653,4 +755,3 @@
 
     def _render(self):
         pass
-
--- a/web/views/cwproperties.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/views/cwproperties.py	Fri Jun 05 10:26:35 2009 +0200
@@ -217,7 +217,7 @@
                                                 eidparam=True))
         subform.vreg = self.vreg
         subform.form_add_hidden('pkey', key, eidparam=True)
- 	subform.form_add_hidden("current-value:%s" % entity.eid,)
+        subform.form_add_hidden("current-value:%s" % entity.eid,)
         form.form_add_subform(subform)
         return subform
 
--- a/web/views/formrenderers.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/views/formrenderers.py	Fri Jun 05 10:26:35 2009 +0200
@@ -69,7 +69,7 @@
         w = data.append
         w(self.open_form(form, values))
         if self.display_progress_div:
-            w(u'<div id="progress">%s</div>' % form.req._('validating...'))
+            w(u'<div id="progress">%s</div>' % self.req._('validating...'))
         w(u'<fieldset>')
         w(tags.input(type=u'hidden', name=u'__form_id',
                      value=values.get('formvid', form.id)))
@@ -85,7 +85,7 @@
         return '\n'.join(data)
 
     def render_label(self, form, field):
-        label = form.req._(field.label)
+        label = self.req._(field.label)
         attrs = {'for': form.context[field]['id']}
         if field.required:
             attrs['class'] = 'required'
@@ -95,11 +95,11 @@
         help = []
         descr = field.help
         if descr:
-            help.append('<div class="helper">%s</div>' % form.req._(descr))
-        example = field.example_format(form.req)
+            help.append('<div class="helper">%s</div>' % self.req._(descr))
+        example = field.example_format(self.req)
         if example:
             help.append('<div class="helper">(%s: %s)</div>'
-                        % (form.req._('sample format'), example))
+                        % (self.req._('sample format'), example))
         return u'&nbsp;'.join(help)
 
     # specific methods (mostly to ease overriding) #############################
@@ -109,7 +109,7 @@
 
         This method should be called once inlined field errors has been consumed
         """
-        req = form.req
+        req = self.req
         errex = form.form_valerror
         # get extra errors
         if errex is not None:
@@ -138,7 +138,7 @@
         else:
             enctype = 'application/x-www-form-urlencoded'
         if form.action is None:
-            action = form.req.build_url('edit')
+            action = self.req.build_url('edit')
         else:
             action = form.action
         tag = ('<form action="%s" method="post" enctype="%s"' % (
@@ -298,11 +298,11 @@
             # main form, display table headers
             w(u'<tr class="header">')
             w(u'<th align="left">%s</th>'
-              % tags.input(type='checkbox', title=form.req._('toggle check boxes'),
+              % tags.input(type='checkbox', title=self.req._('toggle check boxes'),
                            onclick="setCheckboxesState('eid', this.checked)"))
             for field in self.forms[0].fields:
                 if self.display_field(form, field) and field.is_visible():
-                    w(u'<th>%s</th>' % form.req._(field.label))
+                    w(u'<th>%s</th>' % self.req._(field.label))
         w(u'</tr>')
 
 
@@ -319,7 +319,7 @@
 
     def open_form(self, form, values):
         attrs_fs_label = ('<div class="iformTitle"><span>%s</span></div>'
-                          % form.req._('main informations'))
+                          % self.req._('main informations'))
         attrs_fs_label += '<div class="formBody">'
         return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
 
@@ -352,7 +352,7 @@
         srels_by_cat = form.srelations_by_category('generic', 'add')
         if not srels_by_cat:
             return u''
-        req = form.req
+        req = self.req
         _ = req._
         label = u'%s :' % _('This %s' % form.edited_entity.e_schema).capitalize()
         eid = form.edited_entity.eid
@@ -371,7 +371,7 @@
                 if not form.force_display and form.maxrelitems < len(related):
                     link = (u'<span class="invisible">'
                             '[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
-                            '</span>' % form.req._('view all'))
+                            '</span>' % self.req._('view all'))
                     w(u'<li class="invisible">%s</li>' % link)
                 w(u'</ul>')
                 w(u'</td>')
@@ -413,7 +413,7 @@
         if not hasattr(form, 'inlined_relations'):
             return
         entity = form.edited_entity
-        __ = form.req.__
+        __ = self.req.__
         for rschema, targettypes, role in form.inlined_relations():
             # show inline forms only if there's one possible target type
             # for rschema
@@ -473,9 +473,9 @@
             w(u'<div id="div-%(divid)s">' % values)
         else:
             w(u'<div id="notice-%s" class="notice">%s</div>' % (
-                values['divid'], form.req._('click on the box to cancel the deletion')))
+                values['divid'], self.req._('click on the box to cancel the deletion')))
         w(u'<div class="iformBody">')
-        values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema)
+        values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema)
         w(u'<div class="iformTitle"><span>%(title)s</span> '
           '#<span class="icounter">1</span> '
           '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
--- a/web/views/management.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/views/management.py	Fri Jun 05 10:26:35 2009 +0200
@@ -40,6 +40,8 @@
             groups = [(_(group), group) for group in groups]
             for trad, group in sorted(groups):
                 if link:
+                    # XXX we should get a group entity and call its absolute_url
+                    # method
                     l.append(u'<a href="%s" class="%s">%s</a><br/>' % (
                     self.build_url('cwgroup/%s' % group), group, trad))
                 else:
--- a/web/views/plots.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/views/plots.py	Fri Jun 05 10:26:35 2009 +0200
@@ -15,7 +15,7 @@
 from logilab.common import flatten
 from logilab.mtconverter import html_escape
 
-from cubicweb.utils import make_uid, UStringIO
+from cubicweb.utils import make_uid, UStringIO, datetime2ticks
 from cubicweb.vregistry import objectify_selector
 from cubicweb.web.views import baseviews
 
@@ -60,9 +60,6 @@
         filtered.append( (x, y) )
     return sorted(filtered)
 
-def datetime2ticks(date):
-    return time.mktime(date.timetuple()) * 1000
-
 class PlotWidget(object):
     # XXX refactor with cubicweb.web.views.htmlwidgets.HtmlWidget
     def _initialize_stream(self, w=None):
--- a/web/views/startup.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/views/startup.py	Fri Jun 05 10:26:35 2009 +0200
@@ -168,7 +168,7 @@
     id = 'schema'
     title = _('application schema')
     tabs = [_('schema-text'), _('schema-image')]
-    default_tab = 'schema-image'
+    default_tab = 'schema-text'
 
     def call(self):
         """display schema information"""
--- a/web/widgets.py	Fri Jun 05 10:23:56 2009 +0200
+++ b/web/widgets.py	Fri Jun 05 10:26:35 2009 +0200
@@ -153,7 +153,7 @@
         if example:
             help.append(u'<span>(%s: %s)</span>'
                         % (req._('sample format'), example))
-	help.append(u'</div>')
+        help.append(u'</div>')
         return u'&nbsp;'.join(help)
 
     def render_example(self, req):