--- 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" /> ' % (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' '.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+=\'&__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' '.join(help)
def render_example(self, req):