# HG changeset patch # User Sylvain Thénault # Date 1244190395 -7200 # Node ID e4d24e4d74e6b190b04b59e15b675fb1da5ab344 # Parent ce184fdb1e56d7bc09d9ffaf3892c5b2a22bff9a# Parent 6ab574aa5e5f6009a323f97c12a8bd5112e2b793 merge diff -r ce184fdb1e56 -r e4d24e4d74e6 .hgtags --- 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 __pkginfo__.py --- 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' diff -r ce184fdb1e56 -r e4d24e4d74e6 cwctl.py --- 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: diff -r ce184fdb1e56 -r e4d24e4d74e6 cwvreg.py --- 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 debian/changelog --- 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 Wed, 03 Jun 2009 19:52:37 +0200 + cubicweb (3.2.3-1) unstable; urgency=low * new upstream release diff -r ce184fdb1e56 -r e4d24e4d74e6 debian/control --- 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. Uploaders: Sylvain Thenault , Julien Jehannet , - Aurélien Campéas + Aurélien Campéas , + Nicolas Chauvat 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 doc/book/en/annexes/rql/language.rst --- 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 :: diff -r ce184fdb1e56 -r e4d24e4d74e6 doc/book/en/development/devcore/vreg.rst --- 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 doc/book/en/development/webstdlib/boxes.rst --- 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 server/schemahooks.py --- 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) diff -r ce184fdb1e56 -r e4d24e4d74e6 server/schemaserial.py --- 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 test/unittest_cwconfig.py --- 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__ = () diff -r ce184fdb1e56 -r e4d24e4d74e6 utils.py --- 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() diff -r ce184fdb1e56 -r e4d24e4d74e6 vregistry.py --- 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 : 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): diff -r ce184fdb1e56 -r e4d24e4d74e6 web/data/black-uncheck.png Binary file web/data/black-uncheck.png has changed diff -r ce184fdb1e56 -r e4d24e4d74e6 web/data/cubicweb.formfilter.js --- 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) { diff -r ce184fdb1e56 -r e4d24e4d74e6 web/data/cubicweb.python.js --- 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]+'), diff -r ce184fdb1e56 -r e4d24e4d74e6 web/facet.py --- 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'
\n' % facetid) self.w(u'
%s
\n' % (facetid, title)) - self.w(u'%s - %s' - % (sliderid, self.minvalue, sliderid, self.maxvalue)) + self.w(u' - ' + % (sliderid, sliderid)) self.w(u'' % (facetid, self.minvalue)) self.w(u'' @@ -620,6 +682,16 @@ self.w(u'
\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'%s' % html_escape(self.label)) self.w(u'') +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'
\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'
\n' + % (cssclass, html_escape(unicode(self.value)))) + self.w(u'
') + self.w(u'%s ' % (imgsrc, imgalt)) + self.w(u'' % (facetid,title)) + self.w(u'
\n') + self.w(u'
\n') + self.w(u'
\n') class FacetSeparator(HTMLWidget): def __init__(self, label=None): @@ -653,4 +755,3 @@ def _render(self): pass - diff -r ce184fdb1e56 -r e4d24e4d74e6 web/views/cwproperties.py --- 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 diff -r ce184fdb1e56 -r e4d24e4d74e6 web/views/formrenderers.py --- 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'
%s
' % form.req._('validating...')) + w(u'
%s
' % self.req._('validating...')) w(u'
') 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('
%s
' % form.req._(descr)) - example = field.example_format(form.req) + help.append('
%s
' % self.req._(descr)) + example = field.example_format(self.req) if example: help.append('
(%s: %s)
' - % (form.req._('sample format'), example)) + % (self.req._('sample format'), example)) return u' '.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 = ('
') w(u'%s' - % 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'%s' % form.req._(field.label)) + w(u'%s' % self.req._(field.label)) w(u'') @@ -319,7 +319,7 @@ def open_form(self, form, values): attrs_fs_label = ('
%s
' - % form.req._('main informations')) + % self.req._('main informations')) attrs_fs_label += '
' 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'' % form.req._('view all')) + '' % self.req._('view all')) w(u'' % link) w(u'') w(u'') @@ -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'
' % values) else: w(u'
%s
' % ( - 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'
') - 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'
%(title)s ' '#1 ' '[%(removemsg)s]
' diff -r ce184fdb1e56 -r e4d24e4d74e6 web/views/management.py --- 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'%s
' % ( self.build_url('cwgroup/%s' % group), group, trad)) else: diff -r ce184fdb1e56 -r e4d24e4d74e6 web/views/plots.py --- 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): diff -r ce184fdb1e56 -r e4d24e4d74e6 web/views/startup.py --- 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""" diff -r ce184fdb1e56 -r e4d24e4d74e6 web/widgets.py --- 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'(%s: %s)' % (req._('sample format'), example)) - help.append(u'
') + help.append(u'
') return u' '.join(help) def render_example(self, req):