--- a/dbapi.py Thu May 21 00:44:57 2009 +0200
+++ b/dbapi.py Thu May 21 00:50:24 2009 +0200
@@ -50,7 +50,7 @@
# resolve the Pyro object
try:
nshost, nsport = config['pyro-ns-host'], config['pyro-ns-port']
- uri = locator.getNS(nshost, nsport) .resolve(nsid)
+ uri = locator.getNS(nshost, nsport).resolve(nsid)
except ProtocolError:
raise ConnectionError('Could not connect to the Pyro name server '
'(host: %s:%i)' % (nshost, nsport))
@@ -325,6 +325,11 @@
self.vreg = None
# session's data
self.data = {}
+ # XXX < 3.2 bw compat
+ if 'EUser' in self._repo.get_schema():
+ self._user_etype = 'EUser'
+ else:
+ self._user_etype = 'CWUser'
def __repr__(self):
if self.anonymous_connection:
@@ -430,9 +435,9 @@
eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
if req is None:
req = self.request()
- rset = req.eid_rset(eid, 'CWUser')
- user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups,
- properties=properties)
+ rset = req.eid_rset(eid, self._user_etype)
+ user = self.vreg.etype_class(self._user_etype)(req, rset, row=0, groups=groups,
+ properties=properties)
user['login'] = login # cache login
return user
--- a/debian/cubicweb-common.install.in Thu May 21 00:44:57 2009 +0200
+++ b/debian/cubicweb-common.install.in Thu May 21 00:50:24 2009 +0200
@@ -1,4 +1,5 @@
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/common/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/entities/ usr/lib/PY_VERSION/site-packages/cubicweb
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/share/cubicweb/cubes/shared/i18n usr/share/cubicweb/cubes/shared/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/*.py usr/share/pyshared/cubicweb
--- a/devtools/devctl.py Thu May 21 00:44:57 2009 +0200
+++ b/devtools/devctl.py Thu May 21 00:50:24 2009 +0200
@@ -77,11 +77,11 @@
if mod.__file__.startswith(path):
del sys.modules[name]
break
- # fresh rtags
- from cubicweb import rtags
- from cubicweb.web import uicfg
- rtags.RTAGS[:] = []
- reload(uicfg)
+ # fresh rtags
+ from cubicweb import rtags
+ from cubicweb.web import uicfg
+ rtags.RTAGS[:] = []
+ reload(uicfg)
def generate_schema_pot(w, cubedir=None):
"""generate a pot file with schema specific i18n messages
--- a/entities/__init__.py Thu May 21 00:44:57 2009 +0200
+++ b/entities/__init__.py Thu May 21 00:50:24 2009 +0200
@@ -57,16 +57,6 @@
return '%s DESC' % var
return None
- @classmethod
- def __initialize__(cls):
- super(ANYENTITY, cls).__initialize__() # XXX
- # set a default_ATTR method for rich text format fields
- # XXX move this away once the old widgets have been dropped!
- eschema = cls.e_schema
- for metaattr, (metadata, attr) in eschema.meta_attributes().iteritems():
- if metadata == 'format' and not hasattr(cls, 'default_%s' % metaattr):
- setattr(cls, 'default_%s' % metaattr, cls._default_format)
-
# meta data api ###########################################################
def dc_title(self):
@@ -269,9 +259,6 @@
from cubicweb.web.views.autoform import AutomaticEntityForm
return AutomaticEntityForm.esrelations_by_category(self, categories, permission)
- def _default_format(self):
- return self.req.property_value('ui.default-text-format')
-
def attribute_values(self, attrname):
if self.has_eid() or attrname in self:
try:
--- a/entity.py Thu May 21 00:44:57 2009 +0200
+++ b/entity.py Thu May 21 00:50:24 2009 +0200
@@ -284,6 +284,23 @@
parents.append(cls.vreg.etype_class('Any'))
return parents
+ @classmethod
+ @cached
+ def _rest_attr_info(cls):
+ mainattr, needcheck = 'eid', True
+ if cls.rest_attr:
+ mainattr = cls.rest_attr
+ needcheck = not cls.e_schema.has_unique_values(mainattr)
+ else:
+ for rschema in cls.e_schema.subject_relations():
+ if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
+ mainattr = str(rschema)
+ needcheck = False
+ break
+ if mainattr == 'eid':
+ needcheck = False
+ return mainattr, needcheck
+
def __init__(self, req, rset=None, row=None, col=0):
AppRsetObject.__init__(self, req, rset, row, col)
dict.__init__(self)
@@ -363,46 +380,41 @@
if getattr(self.req, 'search_state', ('normal',))[0] == 'normal':
kwargs['base_url'] = self.metainformation()['source'].get('base-url')
if method is None or method == 'view':
- kwargs['_restpath'] = self.rest_path()
+ try:
+ kwargs['_restpath'] = self.rest_path(kwargs.get('base_url'))
+ except TypeError:
+ warn('%s: rest_path() now take use_ext_eid argument, '
+ 'please update' % self.id, DeprecationWarning)
+ kwargs['_restpath'] = self.rest_path()
else:
kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
return self.build_url(method, **kwargs)
- def rest_path(self):
+ def rest_path(self, use_ext_eid=False):
"""returns a REST-like (relative) path for this entity"""
mainattr, needcheck = self._rest_attr_info()
etype = str(self.e_schema)
- if mainattr == 'eid':
- value = self.eid
- else:
+ path = etype.lower()
+ if mainattr != 'eid':
value = getattr(self, mainattr)
if value is None:
- return '%s/eid/%s' % (etype.lower(), self.eid)
- if needcheck:
- # make sure url is not ambiguous
- rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
- if value is not None:
- nbresults = self.req.execute(rql, {'value' : value})[0][0]
- # may an assertion that nbresults is not 0 would be a good idea
- if nbresults != 1: # no ambiguity
- return '%s/eid/%s' % (etype.lower(), self.eid)
- return '%s/%s' % (etype.lower(), self.req.url_quote(value))
-
- @classmethod
- def _rest_attr_info(cls):
- mainattr, needcheck = 'eid', True
- if cls.rest_attr:
- mainattr = cls.rest_attr
- needcheck = not cls.e_schema.has_unique_values(mainattr)
- else:
- for rschema in cls.e_schema.subject_relations():
- if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
- mainattr = str(rschema)
- needcheck = False
- break
+ mainattr = 'eid'
+ path += '/eid'
+ elif needcheck:
+ # make sure url is not ambiguous
+ rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (
+ etype, mainattr)
+ if value is not None:
+ nbresults = self.req.execute(rql, {'value' : value})[0][0]
+ if nbresults != 1: # ambiguity?
+ mainattr = 'eid'
+ path += '/eid'
if mainattr == 'eid':
- needcheck = False
- return mainattr, needcheck
+ if use_ext_eid:
+ value = self.metainformation()['extid']
+ else:
+ value = self.eid
+ return '%s/%s' % (path, self.req.url_quote(value))
def attr_metadata(self, attr, metadata):
"""return a metadata for an attribute (None if unspecified)"""
--- a/rtags.py Thu May 21 00:44:57 2009 +0200
+++ b/rtags.py Thu May 21 00:50:24 2009 +0200
@@ -24,7 +24,8 @@
This class associates a single tag to each key.
"""
_allowed_values = None
- def __init__(self, initfunc=None, allowed_values=None):
+ def __init__(self, name=None, initfunc=None, allowed_values=None):
+ self._name = name or '<unknown>'
self._tagdefs = {}
if allowed_values is not None:
self._allowed_values = allowed_values
@@ -32,7 +33,7 @@
register_rtag(self)
def __repr__(self):
- return repr(self._tagdefs)
+ return '%s: %s' % (self._name, repr(self._tagdefs))
# dict compat
def __getitem__(self, key):
@@ -95,7 +96,9 @@
#else:
stype, rtype, otype, tagged = [str(k) for k in key]
if self._allowed_values is not None:
- assert tag in self._allowed_values, '%r is not an allowed tag' % tag
+ assert tag in self._allowed_values, \
+ '%r is not an allowed tag (should be in %s)' % (
+ tag, self._allowed_values)
self._tagdefs[(rtype, tagged, stype, otype)] = tag
# rtag runtime api ########################################################
--- a/server/repository.py Thu May 21 00:44:57 2009 +0200
+++ b/server/repository.py Thu May 21 00:50:24 2009 +0200
@@ -217,6 +217,7 @@
self._get_pool().close(True)
for i in xrange(config['connections-pool-size']):
self._available_pools.put_nowait(ConnectionsPool(self.sources))
+ self._shutting_down = False
# internals ###############################################################
@@ -349,6 +350,7 @@
"""called on server stop event to properly close opened sessions and
connections
"""
+ self._shutting_down = True
if isinstance(self._looping_tasks, tuple): # if tasks have been started
for looptask in self._looping_tasks:
self.info('canceling task %s...', looptask.name)
@@ -618,7 +620,7 @@
"""commit transaction for the session with the given id"""
self.debug('begin commit for session %s', sessionid)
try:
- self._get_session(sessionid, setpool=True).commit()
+ self._get_session(sessionid).commit()
except (ValidationError, Unauthorized):
raise
except:
@@ -629,7 +631,7 @@
"""commit transaction for the session with the given id"""
self.debug('begin rollback for session %s', sessionid)
try:
- self._get_session(sessionid, setpool=True).rollback()
+ self._get_session(sessionid).rollback()
except:
self.exception('unexpected error')
raise
@@ -720,6 +722,8 @@
def _get_session(self, sessionid, setpool=False):
"""return the user associated to the given session identifier"""
+ if self._shutting_down:
+ raise Exception('Repository is shutting down')
try:
session = self._sessions[sessionid]
except KeyError:
--- a/server/schemaserial.py Thu May 21 00:44:57 2009 +0200
+++ b/server/schemaserial.py Thu May 21 00:50:24 2009 +0200
@@ -14,6 +14,7 @@
from yams import schema as schemamod, buildobjs as ybo
from cubicweb.schema import CONSTRAINTS, ETYPE_NAME_MAP
+from cubicweb.server import sqlutils
def group_mapping(cursor, interactive=True):
"""create a group mapping from an rql cursor
@@ -124,7 +125,8 @@
if etype in ETYPE_NAME_MAP: # XXX <2.45 bw compat
print 'fixing etype name from %s to %s' % (etype, ETYPE_NAME_MAP[etype])
# can't use write rql queries at this point, use raw sql
- session.system_sql('UPDATE CWEType SET name=%(n)s WHERE eid=%(x)s',
+ 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]})
session.system_sql('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
{'x': etype, 'n': ETYPE_NAME_MAP[etype]})
--- a/server/session.py Thu May 21 00:44:57 2009 +0200
+++ b/server/session.py Thu May 21 00:50:24 2009 +0200
@@ -65,6 +65,8 @@
# i18n initialization
self.set_language(cnxprops.lang)
self._threaddata = threading.local()
+ self._threads_in_transaction = set()
+ self._closed = False
def get_mode(self):
return getattr(self._threaddata, 'mode', 'read')
@@ -150,6 +152,8 @@
def set_pool(self):
"""the session need a pool to execute some queries"""
+ if self._closed:
+ raise Exception('try to set pool on a closed session')
if self.pool is None:
self._threaddata.pool = self.repo._get_pool()
try:
@@ -158,6 +162,7 @@
self.repo._free_pool(self.pool)
self._threaddata.pool = None
raise
+ self._threads_in_transaction.add(threading.currentThread())
return self._threaddata.pool
def reset_pool(self):
@@ -167,6 +172,7 @@
# or rollback
if self.pool is not None and self.mode == 'read':
# even in read mode, we must release the current transaction
+ self._threads_in_transaction.remove(threading.currentThread())
self.repo._free_pool(self.pool)
self.pool.pool_reset(self)
self._threaddata.pool = None
@@ -343,6 +349,23 @@
def close(self):
"""do not close pool on session close, since they are shared now"""
+ self._closed = True
+ # copy since _threads_in_transaction maybe modified while waiting
+ for thread in self._threads_in_transaction.copy():
+ if thread is threading.currentThread():
+ continue
+ self.info('waiting for thread %s', thread)
+ # do this loop/break instead of a simple join(10) in case thread is
+ # the main thread (in which case it will be removed from
+ # self._threads_in_transaction but still be alive...)
+ for i in xrange(10):
+ thread.join(1)
+ if not (thread.isAlive() and
+ thread in self._threads_in_transaction):
+ break
+ else:
+ self.error('thread %s still alive after 10 seconds, will close '
+ 'session anyway', thread)
self.rollback()
# transaction data/operations management ##################################
--- a/server/sources/rql2sql.py Thu May 21 00:44:57 2009 +0200
+++ b/server/sources/rql2sql.py Thu May 21 00:50:24 2009 +0200
@@ -901,6 +901,7 @@
def visit_comparison(self, cmp, contextrels=None):
"""generate SQL for a comparaison"""
if len(cmp.children) == 2:
+ # XXX occurs ?
lhs, rhs = cmp.children
else:
lhs = None
@@ -911,6 +912,11 @@
operator = ' LIKE '
else:
operator = ' %s ' % operator
+ elif (operator == '=' and isinstance(rhs, Constant)
+ and rhs.eval(self._args) is None):
+ if lhs is None:
+ return ' IS NULL'
+ return '%s IS NULL' % lhs.accept(self, contextrels)
elif isinstance(rhs, Function) and rhs.name == 'IN':
assert operator == '='
operator = ' '
--- a/server/test/data/sources_multi Thu May 21 00:44:57 2009 +0200
+++ b/server/test/data/sources_multi Thu May 21 00:50:24 2009 +0200
@@ -14,6 +14,7 @@
cubicweb-user = admin
cubicweb-password = gingkow
mapping-file = extern_mapping.py
+base-url=http://extern.org/
[extern-multi]
adapter = pyrorql
--- a/server/test/unittest_multisources.py Thu May 21 00:44:57 2009 +0200
+++ b/server/test/unittest_multisources.py Thu May 21 00:50:24 2009 +0200
@@ -235,9 +235,21 @@
states.remove((aff1stateeid, aff1statename))
notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
{'x': aff1}, 'x'))
- self.set_debug(False)
self.assertSetEquals(notstates, states)
+ def test_absolute_url_base_url(self):
+ ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
+ cnx2.commit()
+ lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+ self.assertEquals(lc.absolute_url(), 'http://extern.org/card/eid/%s' % ceid)
+
+ def test_absolute_url_no_base_url(self):
+ cu = cnx3.cursor()
+ ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
+ cnx3.commit()
+ lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+ self.assertEquals(lc.absolute_url(), 'http://testing.fr/cubicweb/card/eid/%s' % lc.eid)
+
def test_nonregr1(self):
ueid = self.session.user.eid
affaire = self.execute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
@@ -251,7 +263,6 @@
self.assertEquals(len(rset), 1)
self.assertEquals(rset.rows[0], [self.session.user.eid])
-
def test_nonregr3(self):
self.execute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
--- a/server/test/unittest_repository.py Thu May 21 00:44:57 2009 +0200
+++ b/server/test/unittest_repository.py Thu May 21 00:50:24 2009 +0200
@@ -199,6 +199,26 @@
def test_transaction_interleaved(self):
self.skip('implement me')
+ def test_close_wait_processing_request(self):
+ repo = self.repo
+ cnxid = repo.connect(*self.default_user_password())
+ repo.execute(cnxid, 'INSERT CWUser X: X login "toto", X upassword "tutu", X in_group G WHERE G name "users"')
+ repo.commit(cnxid)
+ # close has to be in the thread due to sqlite limitations
+ def close_in_a_few_moment():
+ time.sleep(0.1)
+ repo.close(cnxid)
+ t = threading.Thread(target=close_in_a_few_moment)
+ t.start()
+ try:
+ print 'execute'
+ repo.execute(cnxid, 'DELETE CWUser X WHERE X login "toto"')
+ print 'commit'
+ repo.commit(cnxid)
+ print 'commited'
+ finally:
+ t.join()
+
def test_initial_schema(self):
schema = self.repo.schema
# check order of attributes is respected
@@ -238,31 +258,29 @@
def test_pyro(self):
import Pyro
Pyro.config.PYRO_MULTITHREADED = 0
- lock = threading.Lock()
+ done = []
# the client part has to be in the thread due to sqlite limitations
- t = threading.Thread(target=self._pyro_client, args=(lock,))
+ t = threading.Thread(target=self._pyro_client, args=(done,))
try:
daemon = self.repo.pyro_register()
t.start()
- # connection
- daemon.handleRequests(1.0)
- daemon.handleRequests(1.0)
- daemon.handleRequests(1.0)
- # get schema
- daemon.handleRequests(1.0)
- # execute
- daemon.handleRequests(1.0)
- t.join()
+ while not done:
+ daemon.handleRequests(1.0)
+ t.join(1)
+ if t.isAlive():
+ self.fail('something went wrong, thread still alive')
finally:
repository.pyro_unregister(self.repo.config)
- def _pyro_client(self, lock):
+ def _pyro_client(self, done):
cnx = connect(self.repo.config.appid, u'admin', 'gingkow')
# check we can get the schema
schema = cnx.get_schema()
self.assertEquals(schema.__hashmode__, None)
- rset = cnx.cursor().execute('Any U,G WHERE U in_group G')
-
+ cu = cnx.cursor()
+ rset = cu.execute('Any U,G WHERE U in_group G')
+ cnx.close()
+ done.append(True)
def test_internal_api(self):
repo = self.repo
--- a/server/test/unittest_rql2sql.py Thu May 21 00:44:57 2009 +0200
+++ b/server/test/unittest_rql2sql.py Thu May 21 00:50:24 2009 +0200
@@ -1,3 +1,4 @@
+
"""unit tests for module cubicweb.server.sources.rql2sql"""
import sys
@@ -1211,6 +1212,14 @@
WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.cw_eid''',
varmap={'X': 'T00.x', 'X.login': 'T00.l'})
+ def test_is_null_transform(self):
+ union = self._prepare('Any X WHERE X login %(login)s')
+ r, args = self.o.generate(union, {'login': None})
+ self.assertLinesEquals((r % args).strip(),
+ '''SELECT X.cw_eid
+FROM cw_CWUser AS X
+WHERE X.cw_login IS NULL''')
+
def test_parser_parse(self):
for t in self._parse(PARSER):
yield t
--- a/web/box.py Thu May 21 00:44:57 2009 +0200
+++ b/web/box.py Thu May 21 00:50:24 2009 +0200
@@ -152,7 +152,7 @@
entity = self.entity(row, col)
limit = self.req.property_value('navigation.related-limit') + 1
role = get_role(self)
- self.w(u'<div class="sideRelated">')
+ self.w(u'<div class="sideBox">')
self.wview('sidebox', entity.related(self.rtype, role, limit=limit),
title=display_name(self.req, self.rtype, role))
self.w(u'</div>')
--- a/web/data/cubicweb.css Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.css Thu May 21 00:50:24 2009 +0200
@@ -455,6 +455,8 @@
padding: 0.2em 0px;
margin-bottom: 0.5em;
background: #eeedd9;
+ min-width: 21em;
+ max-width: 50em;
}
ul.sideBox li{
@@ -572,24 +574,11 @@
padding: 0.5em 0.2em 0.2em;
}
-div.sideRelated h4,
-div.sideRelated h5 {
- margin-top: 0px;
- margin-bottom: 0px;
-}
-
div.primaryRight{
float:right;
}
-div.sideRelated {
- margin-right: 1em;
- padding: 12px 0px 12px 12px;
- min-width: 21em;
- max-width: 50em;
-}
-
div.metadata {
font-size: 90%;
margin: 5px 0px 3px;
--- a/web/data/cubicweb.edition.js Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.edition.js Thu May 21 00:50:24 2009 +0200
@@ -342,11 +342,11 @@
}
-function handleFormValidationResponse(formid, onsuccess, result) {
+function handleFormValidationResponse(formid, onsuccess, onfailure, result) {
// Success
if (result[0]) {
if (onsuccess) {
- return onsuccess(result[1]);
+ return onsuccess(result[1], formid);
} else {
document.location.href = result[1];
return ;
@@ -365,6 +365,9 @@
_displayValidationerrors(formid, descr[0], descr[1]);
updateMessage(_("please correct errors below"));
document.location.hash = '#header';
+ if (onfailure){
+ onfailure(formid);
+ }
return false;
}
@@ -426,7 +429,7 @@
* to the appropriate URL. Otherwise, the validation errors are displayed
* around the corresponding input fields.
*/
-function validateForm(formid, action, onsuccess) {
+function validateForm(formid, action, onsuccess, onfailure) {
try {
var zipped = formContents(formid);
var d = asyncRemoteExec('validate_form', action, zipped[0], zipped[1]);
@@ -435,7 +438,7 @@
return false;
}
function _callback(result, req) {
- handleFormValidationResponse(formid, onsuccess, result);
+ handleFormValidationResponse(formid, onsuccess, onfailure, result);
}
d.addCallback(_callback);
return false;
@@ -468,7 +471,7 @@
return false;
}
d.addCallback(function (result, req) {
- handleFormValidationResponse(formid, noop, result);
+ handleFormValidationResponse(formid, noop, noop, result);
if (reload) {
document.location.href = result[1];
} else {
@@ -502,7 +505,7 @@
return false;
}
d.addCallback(function (result, req) {
- handleFormValidationResponse(formid, noop, result);
+ handleFormValidationResponse(formid, noop, noop, result);
var fieldview = getNode(divid);
fieldview.innerHTML = result[2];
// switch inline form off only if no error
--- a/web/data/cubicweb.preferences.css Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.preferences.css Thu May 21 00:50:24 2009 +0200
@@ -6,98 +6,105 @@
*/
-table.preferences td{
- padding: 0 0.5em 1em;
+.preferences .validateButton{
+ margin-top:0px;
}
-fieldset.preferences{
+fieldset.preferences{
border : 1px solid #CFCEB7;
- margin:1em 0;
- padding:0 1em 1em;
+ margin:7px 1em 0;
+ padding:2px 6px 6px;
}
-div.preffield {
- margin-bottom: 0.8em ;
+div.component {
+ margin-left: 1em;
}
-/*
-div.preffield label{
- font-size:110%
- }
-*/
-
-div.prefinput{
- margin:.3em 0;
-}
-
-div.componentLink{
+div.componentLink{
margin-top:0.3em;
}
a.componentTitle{
font-weight:bold;
- color: #000;
+ color: #000/*#0083AB;*/
}
a.componentTitle:visited{
color: #000;
}
-
-
h2.propertiesform a{
display:block;
margin: 10px 0px 6px 0px;
font-weight: bold;
- color: #222211;
+ color: #000;
padding: 0.2em 0.2em 0.2em 16px;
background:#eeedd9 url("puce_down.png") 3px center no-repeat;
- font-size:76%;
+ font-size:89%;
}
h2.propertiesform a:hover{
- color:#000;
background-color:#cfceb7;
+}
+
+h2.propertiesform a:hover,
+h2.propertiesform a:visited{
text-decoration:none;
+ color: #000;
}
+div.preffield {
+ margin-bottom: 5px;
+ padding:2px 5px;
+ background:#eeedd9;
+}
+
+div.prefinput{
+ margin:.3em;
+}
+
+
div.prefinput select.changed,
-div.prefinput input.changed{
- background:#eeedd9;
- border: 1px solid #eeedd9;
+div.prefinput input.changed{
+ border: 1px solid #000;
+ font-weight:bold;
+
}
div.prefinput select,
-div.prefinput input{
+div.prefinput input{
background:#fff;
border: 1px solid #CFCEB7;
}
.prefinput input.error {
- background:transparent url(error.png) no-repeat scroll 100% 50% !important;
+ /* background:#fff url(error.png) no-repeat scroll 100% 50% !important; */
+ border:1px solid red !important;
+ color:red;
+ padding-right:1em;
}
-div.formsg{
+div.formsg{
font-weight:bold;
margin:0.5em 0px;
- }
+}
-div.formsg .critical{
+div.critical{
color:red;
padding-left:20px;
background:#fff url(critical.png) no-repeat;
}
-div.formsg .message{
+div.formsg .msg{
color : green;
}
.helper{
font-size: 96%;
color: #555544;
- padding:0;
+ padding:0;
}
div.prefinput .helper:hover {
@@ -105,11 +112,6 @@
cursor: default;
}
-.error{
- color:red;
- padding-right:1em;
+div.openlink{
+ display:inline;
}
-
-div.openlink{
- display:inline;
- }
\ No newline at end of file
--- a/web/data/cubicweb.preferences.js Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.preferences.js Thu May 21 00:50:24 2009 +0200
@@ -4,23 +4,21 @@
* move me in a more appropriate place
*/
-function toggleVisibility(elemId, cookiename) {
- _clearPreviousMessages();
- jqNode(elemId).toggleClass('hidden');
- asyncRemoteExec('set_cookie', cookiename,
- jQuery('#' + elemId).attr('class'));
+function togglePrefVisibility(elemId) {
+ clearPreviousMessages();
+ jQuery('#' + elemId).toggleClass('hidden');
}
function closeFieldset(fieldsetid){
var linklabel = _('open all');
- var linkhref = 'javascript:openFieldset("' +fieldsetid + '")'
- _toggleFieldset(fieldsetid, 1, linklabel, linkhref)
+ var linkhref = 'javascript:openFieldset("' +fieldsetid + '")';
+ _toggleFieldset(fieldsetid, 1, linklabel, linkhref);
}
function openFieldset(fieldsetid){
var linklabel = _('close all');
- var linkhref = 'javascript:closeFieldset("'+ fieldsetid + '")'
- _toggleFieldset(fieldsetid, 0, linklabel, linkhref)
+ var linkhref = 'javascript:closeFieldset("'+ fieldsetid + '")';
+ _toggleFieldset(fieldsetid, 0, linklabel, linkhref);
}
@@ -28,13 +26,13 @@
jQuery('#'+fieldsetid).find('div.openlink').each(function(){
var link = A({'href' : "javascript:noop();",
'onclick' : linkhref},
- linklabel)
+ linklabel);
jQuery(this).empty().append(link);
});
jQuery('#'+fieldsetid).find('fieldset[id]').each(function(){
var fieldset = jQuery(this);
if(closeaction){
- fieldset.addClass('hidden')
+ fieldset.addClass('hidden');
}else{
fieldset.removeClass('hidden');
linkLabel = (_('open all'));
@@ -43,44 +41,24 @@
}
function validatePrefsForm(formid){
- var form = getNode(formid);
- freezeFormButtons(formid);
- try {
- var d = _sendForm(formid, null);
- } catch (ex) {
- log('got exception', ex);
- return false;
- }
- function _callback(result, req) {
- _clearPreviousMessages();
- _clearPreviousErrors(formid);
- // success
- if(result[0]){
- return submitSucces(formid)
- }
- // Failures
- unfreezeFormButtons(formid);
- var descr = result[1];
- if (!isArrayLike(descr) || descr.length != 2) {
- log('got strange error :', descr);
- updateMessage(descr);
- return ;
- }
- _displayValidationerrors(formid, descr[0], descr[1]);
- var dom = DIV({'class':'critical'},
- _("please correct errors below"));
- jQuery(form).find('div.formsg').empty().append(dom);
- updateMessage(_(""));
- return false;
- }
- d.addCallback(_callback);
- return false;
+ clearPreviousMessages();
+ clearPreviousErrors(formid);
+ return validateForm(formid, null, submitSucces, submitFailure);
}
-function submitSucces(formid){
+function submitFailure(formid){
+ var form = jQuery('#'+formid);
+ var dom = DIV({'class':'critical'},
+ _("please correct errors below"));
+ jQuery(form).find('div.formsg').empty().append(dom);
+ // clearPreviousMessages()
+ jQuery(form).find('span.error').next().focus();
+}
+
+function submitSucces(url, formid){
var form = jQuery('#'+formid);
setCurrentValues(form);
- var dom = DIV({'class':'message'},
+ var dom = DIV({'class':'msg'},
_("changes applied"));
jQuery(form).find('div.formsg').empty().append(dom);
jQuery(form).find('input').removeClass('changed');
@@ -88,64 +66,63 @@
return;
}
-function _clearPreviousMessages() {
+function clearPreviousMessages() {
jQuery('div#appMsg').addClass('hidden');
jQuery('div.formsg').empty();
}
-function _clearPreviousErrors(formid) {
- jQuery('#' + formid + ' span.error').remove();
+function clearPreviousErrors(formid) {
+ jQuery('#err-value:' + formid).remove();
}
function checkValues(form, success){
var unfreezeButtons = false;
- jQuery(form).find('select').each(function () {
+ jQuery(form).find('select').each(function () {
unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
});
jQuery(form).find('[type=text]').each(function () {
unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
});
- jQuery(form).find('input[type=radio]').each(function () {
+ jQuery(form).find('input[type=radio]').each(function () {
if (jQuery(this).attr('checked')){
unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
}
- });
-
+ });
+
if (unfreezeButtons){
unfreezeFormButtons(form.attr('id'));
}else{
if (!success){
- _clearPreviousMessages();
+ clearPreviousMessages();
}
- _clearPreviousErrors(form.attr('id'));
+ clearPreviousErrors(form.attr('id'));
freezeFormButtons(form.attr('id'));
}
}
function _checkValue(input, unfreezeButtons){
- var currentValueInput = jQuery("input[id=current-" + input.attr('name') + "]");
+ var currentValueInput = jQuery("input[name=current-" + input.attr('name') + "]");
if (currentValueInput.attr('value') != input.attr('value')){
input.addClass('changed');
unfreezeButtons = true;
}else{
input.removeClass('changed');
jQuery("span[id=err-" + input.attr('id') + "]").remove();
- }
+ }
input.removeClass('error');
- return unfreezeButtons
+ return unfreezeButtons;
}
function setCurrentValues(form){
- jQuery(form).find('input[id^=current-value]').each(function () {
+ jQuery(form).find('input[name^=current-value]').each(function () {
var currentValueInput = jQuery(this);
- var name = currentValueInput.attr('id').split('-')[1];
+ var name = currentValueInput.attr('name').split('-')[1];
jQuery(form).find("[name=" + name + "]").each(function (){
var input = jQuery(this);
if(input.attr('type')=='radio'){
if(input.attr('checked')){
- log(input.attr('value'));
currentValueInput.attr('value', input.attr('value'));
}
}else{
@@ -155,20 +132,20 @@
});
}
-
function initEvents(){
- jQuery('form').each(function() {
+ jQuery('form').each(function() {
var form = jQuery(this);
freezeFormButtons(form.attr('id'));
- form.find('input[type=text]').keyup(function(){
- checkValues(form);
+ form.find('input[type=text]').keyup(function(){
+ checkValues(form);
});
- form.find('input[type=radio]').change(function(){
- checkValues(form);
+ form.find('input[type=radio]').change(function(){
+ checkValues(form);
});
- form.find('select').change(function(){
- checkValues(form);
+ form.find('select').change(function(){
+ checkValues(form);
});
+ setCurrentValues(form);
});
}
--- a/web/form.py Thu May 21 00:44:57 2009 +0200
+++ b/web/form.py Thu May 21 00:50:24 2009 +0200
@@ -372,10 +372,15 @@
return self.form_previous_values[qname]
if qname in self.req.form:
return self.req.form[qname]
+ if field.name in self.req.form:
+ return self.req.form[field.name]
return None
def form_field_value(self, field, load_bytes=False):
"""return field's *typed* value"""
+ myattr = '%s_%s_default' % (field.role, field.name)
+ if hasattr(self, myattr):
+ return getattr(self, myattr)()
value = field.initial
if callable(value):
value = value(self)
--- a/web/formfields.py Thu May 21 00:44:57 2009 +0200
+++ b/web/formfields.py Thu May 21 00:50:24 2009 +0200
@@ -213,20 +213,21 @@
try:
return req.data[self]
except KeyError:
+ fkwargs = {}
if self.use_fckeditor(form):
# if fckeditor is used and format field isn't explicitly
# deactivated, we want an hidden field for the format
- widget = HiddenInput()
- choices = None
+ fkwargs['widget'] = HiddenInput()
+ fkwargs['initial'] = 'text/html'
else:
# else we want a format selector
- # XXX compute vocabulary
- widget = Select()
+ fkwargs['widget'] = Select()
+ fkwargs['widget'].attrs['size'] = 1
fcstr = FormatConstraint()
- choices = [(req._(fmt), fmt) for fmt in fcstr.vocabulary(req=req)]
- widget.attrs['size'] = 1
- field = StringField(name=self.name + '_format', widget=widget,
- choices=choices)
+ fkwargs['choices'] = fcstr.vocabulary(req=req)
+ fkwargs['internationalizable'] = True
+ fkwargs['initial'] = lambda f: f.form_field_format(self)
+ field = StringField(name=self.name + '_format', **fkwargs)
req.data[self] = field
return field
@@ -471,7 +472,10 @@
kwargs.setdefault('widget', Select())
kwargs.setdefault('choices', cstr.vocabulary)
if card in '?1':
+ if isinstance(kwargs['widget'], type):
+ kwargs['widget'] = kwargs['widget']()
kwargs['widget'].attrs.setdefault('size', 1)
+ for cstr in constraints:
if isinstance(cstr, SizeConstraint) and cstr.max is not None:
if cstr.max < 257:
kwargs.setdefault('widget', TextInput())
--- a/web/formrenderers.py Thu May 21 00:44:57 2009 +0200
+++ b/web/formrenderers.py Thu May 21 00:50:24 2009 +0200
@@ -83,13 +83,13 @@
return tags.label(label, **attrs)
def render_help(self, form, field):
- help = [ u'<br/>' ]
+ help = []
descr = field.help
if descr:
- help.append('<span class="helper">%s</span>' % form.req._(descr))
+ help.append('<div class="helper">%s</div>' % form.req._(descr))
example = field.example_format(form.req)
if example:
- help.append('<span class="helper">(%s: %s)</span>'
+ help.append('<div class="helper">(%s: %s)</div>'
% (form.req._('sample format'), example))
return u' '.join(help)
--- a/web/formwidgets.py Thu May 21 00:44:57 2009 +0200
+++ b/web/formwidgets.py Thu May 21 00:50:24 2009 +0200
@@ -199,27 +199,8 @@
def render(self, form, field):
name, curvalues, attrs = self._render_attrs(form, field)
- options = []
- for label, value in field.vocabulary(form):
- if value in curvalues:
- tag = tags.input(name=name, value=value, type=self.type,
- checked='checked', **attrs)
- else:
- tag = tags.input(name=name, value=value, type=self.type,
- **attrs)
- options.append(tag + label)
- return '<br/>\n'.join(options)
-
-
-class Radio(Input):
- """<input type='radio'>, for field having a specific vocabulary. One
- input will be generated for each possible value.
- """
- type = 'radio'
-
- def render(self, form, field):
- name, curvalues, attrs = self._render_attrs(form, field)
domid = attrs.pop('id', None)
+ sep = attrs.pop('separator', u'<br/>')
options = []
for i, (label, value) in enumerate(field.vocabulary(form)):
iattrs = attrs.copy()
@@ -228,10 +209,16 @@
if value in curvalues:
iattrs['checked'] = u'checked'
tag = tags.input(name=name, type=self.type, value=value, **iattrs)
- options.append(tag + label + '<br/>')
+ options.append(tag + label + sep)
return '\n'.join(options)
+class Radio(CheckBox):
+ """<input type='radio'>, for field having a specific vocabulary. One
+ input will be generated for each possible value.
+ """
+ type = 'radio'
+
# javascript widgets ###########################################################
class DateTimePicker(TextInput):
@@ -318,20 +305,21 @@
values = (INTERNAL_FIELD_VALUE,)
init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
# XXX entity form specific
- attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity)
+ attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
return name, values, attrs
- def _get_url(self, entity):
- return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
- pageid=entity.req.pageid, mode='remote')
+ def _get_url(self, entity, field):
+ fname = entity.autocomplete_initfuncs[field.name]
+ return entity.req.build_url('json', fname=fname, mode='remote',
+ pageid=entity.req.pageid)
class StaticFileAutoCompletionWidget(AutoCompletionWidget):
"""XXX describe me"""
wdgtype = 'StaticFileSuggestField'
- def _get_url(self, entity):
- return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
+ def _get_url(self, entity, field):
+ return entity.req.datadir_url + entity.autocomplete_initfuncs[field.name]
class RestrictedAutoCompletionWidget(AutoCompletionWidget):
--- a/web/htmlwidgets.py Thu May 21 00:44:57 2009 +0200
+++ b/web/htmlwidgets.py Thu May 21 00:50:24 2009 +0200
@@ -57,6 +57,7 @@
def append(self, item):
self.items.append(item)
+ title_class = 'boxTitle'
main_div_class = 'boxContent'
listing_class = 'boxListing'
@@ -82,7 +83,7 @@
title = '<span>%s</span>' % html_escape(self.title)
else:
title = '<span>%s</span>' % self.title
- self.w(u'<div class="boxTitle">%s</div>' % title)
+ self.w(u'<div class="%s">%s</div>' % (self.title_class, title))
if self.items:
self.box_begin_content()
for item in self.items:
@@ -93,6 +94,7 @@
class SideBoxWidget(BoxWidget):
"""default CubicWeb's sidebox widget"""
+ title_class = u'sideBoxTitle'
main_div_class = u'sideBoxBody'
listing_class = ''
--- a/web/test/data/schema/testschema.py Thu May 21 00:44:57 2009 +0200
+++ b/web/test/data/schema/testschema.py Thu May 21 00:50:24 2009 +0200
@@ -1,6 +1,7 @@
class Salesterm(EntityType):
described_by_test = SubjectRelation('File', cardinality='1*', composite='subject')
amount = Int(constraints=[IntervalBoundConstraint(0, 100)])
+ reason = String(maxsize=20, vocabulary=[u'canceled', u'sold'])
class tags(RelationDefinition):
subject = 'Tag'
--- a/web/test/unittest_form.py Thu May 21 00:44:57 2009 +0200
+++ b/web/test/unittest_form.py Thu May 21 00:50:24 2009 +0200
@@ -64,7 +64,14 @@
self.assertEquals(len(states), 1)
self.assertEquals(states[0][0], u'deactivated') # list of (combobox view, state eid)
-
+ def test_consider_req_form_params(self):
+ e = self.etype_instance('CWUser')
+ e.eid = 'A'
+ form = EntityFieldsForm(self.request(login=u'toto'), None, entity=e)
+ field = StringField(name='login', eidparam=True)
+ form.append_field(field)
+ form.form_build_context({})
+ self.assertEquals(form.form_field_display_value(field, {}), 'toto')
# form view tests #########################################################
--- a/web/test/unittest_formfields.py Thu May 21 00:44:57 2009 +0200
+++ b/web/test/unittest_formfields.py Thu May 21 00:50:24 2009 +0200
@@ -1,11 +1,15 @@
"""unittests for cw.web.formfields"""
from logilab.common.testlib import TestCase, unittest_main
+
+from yams.constraints import StaticVocabularyConstraint, SizeConstraint
+
from cubicweb.devtools import TestServerConfiguration
-from cubicweb.web.formwidgets import PasswordInput, TextArea
+from cubicweb.devtools.testlib import EnvBasedTC
+from cubicweb.web.form import EntityFieldsForm
+from cubicweb.web.formwidgets import PasswordInput, TextArea, Select
from cubicweb.web.formfields import *
-from cubicweb.entities.wfobjs import State
-from cubicweb.entities.authobjs import CWUser
+
from cubes.file.entities import File
config = TestServerConfiguration('data')
@@ -14,6 +18,7 @@
state_schema = schema['State']
cwuser_schema = schema['CWUser']
file_schema = schema['File']
+salesterm_schema = schema['Salesterm']
class GuessFieldTC(TestCase):
@@ -41,12 +46,13 @@
self.assertEquals(description_format_field.sort, True)
self.assertEquals(description_format_field.initial(None), 'text/rest')
+
# wikiid_field = guess_field(state_schema, schema['wikiid'])
# self.assertIsInstance(wikiid_field, StringField)
# self.assertEquals(wikiid_field.required, False)
- def test_euser_fields(self):
+ def test_cwuser_fields(self):
upassword_field = guess_field(cwuser_schema, schema['upassword'])
self.assertIsInstance(upassword_field, StringField)
self.assertIsInstance(upassword_field.widget, PasswordInput)
@@ -80,5 +86,28 @@
self.assertIsInstance(data_field.format_field, StringField)
self.assertIsInstance(data_field.encoding_field, StringField)
+ def test_constraints_priority(self):
+ salesterm_field = guess_field(salesterm_schema, schema['reason'])
+ constraints = schema['reason'].rproperty('Salesterm', 'String', 'constraints')
+ self.assertEquals([c.__class__ for c in constraints],
+ [SizeConstraint, StaticVocabularyConstraint])
+ self.assertIsInstance(salesterm_field.widget, Select)
+
+class MoreFieldsTC(EnvBasedTC):
+ def test_rtf_format_field(self):
+ req = self.request()
+ req.use_fckeditor = lambda: False
+ e = self.etype_instance('State')
+ form = EntityFieldsForm(req, entity=e)
+ description_field = guess_field(state_schema, schema['description'])
+ description_format_field = description_field.get_format_field(form)
+ self.assertEquals(description_format_field.internationalizable, True)
+ self.assertEquals(description_format_field.sort, True)
+ # unlike below, initial is bound to form.form_field_format
+ self.assertEquals(description_format_field.initial(form), 'text/html')
+ self.execute('INSERT CWProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"')
+ self.commit()
+ self.assertEquals(description_format_field.initial(form), 'text/rest')
+
if __name__ == '__main__':
unittest_main()
--- a/web/uicfg.py Thu May 21 00:44:57 2009 +0200
+++ b/web/uicfg.py Thu May 21 00:50:24 2009 +0200
@@ -97,8 +97,9 @@
section = 'sideboxes'
rtag.tag_relation((sschema, rschema, oschema, role), section)
-primaryview_section = RelationTags(init_primaryview_section,
- frozenset(('attributes', 'relations',
+primaryview_section = RelationTags('primaryview_section',
+ init_primaryview_section,
+ frozenset(('attributes', 'relations',
'sideboxes', 'hidden')))
for rtype in ('eid', 'creation_date', 'modification_date',
'is', 'is_instance_of', 'identity',
@@ -108,6 +109,8 @@
'see_also'):
primaryview_section.tag_subject_of(('*', rtype, '*'), 'hidden')
primaryview_section.tag_object_of(('*', rtype, '*'), 'hidden')
+primaryview_section.tag_subject_of(('*', 'use_email', '*'), 'attributes')
+primaryview_section.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
for attr in ('name', 'meta', 'final'):
primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
@@ -140,7 +143,8 @@
rtag.tag_relation((sschema, rschema, oschema, role), displayinfo)
displayinfo.setdefault('label', label)
-primaryview_display_ctrl = DisplayCtrlRelationTags(init_primaryview_display_ctrl)
+primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
+ init_primaryview_display_ctrl)
# index view configuration ####################################################
@@ -178,7 +182,7 @@
section = 'generic'
rtag.tag_relation((sschema, rschema, oschema, role), section)
-autoform_section = RelationTags(init_autoform_section,
+autoform_section = RelationTags('autoform_section', init_autoform_section,
set(('primary', 'secondary', 'generic',
'metadata', 'generated')))
# use primary and not generated for eid since it has to be an hidden
@@ -217,7 +221,7 @@
# relations'field class
-autoform_field = RelationTags()
+autoform_field = RelationTags('autoform_field')
# relations'field explicit kwargs (given to field's __init__)
autoform_field_kwargs = RelationTags()
@@ -231,7 +235,7 @@
# inlined view flag for non final relations: when True for an entry, the
# entity(ies) at the other end of the relation will be editable from the
# form of the edited entity
-autoform_is_inlined = RelationTagsBool()
+autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
autoform_is_inlined.tag_subject_of(('*', 'use_email', '*'), True)
autoform_is_inlined.tag_subject_of(('CWRelation', 'relation_type', '*'), True)
autoform_is_inlined.tag_subject_of(('CWRelation', 'from_entity', '*'), True)
@@ -241,7 +245,7 @@
# set of tags of the form <action>_on_new on relations. <action> is a
# schema action (add/update/delete/read), and when such a tag is found
# permissions checking is by-passed and supposed to be ok
-autoform_permissions_overrides = RelationTagsSet()
+autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
# boxes.EditBox configuration #################################################
@@ -254,7 +258,8 @@
rschema.rproperty(sschema, oschema, 'composite') == role:
rtag.tag_relation((sschema, rschema, oschema, role), True)
-actionbox_appearsin_addmenu = RelationTagsBool(init_actionbox_appearsin_addmenu)
+actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
+ init_actionbox_appearsin_addmenu)
actionbox_appearsin_addmenu.tag_subject_of(('*', 'is', '*'), False)
actionbox_appearsin_addmenu.tag_object_of(('*', 'is', '*'), False)
actionbox_appearsin_addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
@@ -279,3 +284,4 @@
actionbox_appearsin_addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
actionbox_appearsin_addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
actionbox_appearsin_addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
+
--- a/web/views/actions.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/actions.py Thu May 21 00:50:24 2009 +0200
@@ -9,7 +9,7 @@
from cubicweb.vregistry import objectify_selector
from cubicweb.selectors import (EntitySelector,
one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
- non_final_entity,
+ nonempty_rset, non_final_entity,
authenticated_user, match_user_groups, match_search_state,
has_permission, has_add_permission,
)
@@ -74,7 +74,7 @@
if accept match.
"""
id = 'select'
- __select__ = match_search_state('linksearch') & match_searched_etype()
+ __select__ = match_search_state('linksearch') & nonempty_rset() & match_searched_etype()
title = _('select')
category = 'mainactions'
--- a/web/views/basecontrollers.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/basecontrollers.py Thu May 21 00:50:24 2009 +0200
@@ -18,7 +18,7 @@
from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
from cubicweb.utils import strptime
from cubicweb.selectors import yes, match_user_groups
-from cubicweb.view import STRICT_DOCTYPE
+from cubicweb.view import STRICT_DOCTYPE, STRICT_DOCTYPE_NOEXT
from cubicweb.common.mail import format_mail
from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
from cubicweb.web.formrenderers import FormRenderer
@@ -32,8 +32,13 @@
HAS_SEARCH_RESTRICTION = False
-def xhtml_wrap(source):
- head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE
+def xhtml_wrap(self, source):
+ # XXX factor out, watch view.py ~ Maintemplate.doctype
+ if self.req.xhtml_browser():
+ dt = STRICT_DOCTYPE
+ else:
+ dt = STRICT_DOCTYPE_NOEXT
+ head = u'<?xml version="1.0"?>\n' + dt
return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
def jsonize(func):
@@ -51,7 +56,7 @@
def wrapper(self, *args, **kwargs):
self.req.set_content_type(self.req.html_content_type())
result = func(self, *args, **kwargs)
- return xhtml_wrap(result)
+ return xhtml_wrap(self, result)
wrapper.__name__ = func.__name__
return wrapper
@@ -205,7 +210,7 @@
self.req.set_content_type('text/html')
jsarg = simplejson.dumps( (status, args) )
return """<script type="text/javascript">
- window.parent.handleFormValidationResponse('entityForm', null, %s);
+ window.parent.handleFormValidationResponse('entityForm', null, null, %s);
</script>""" % simplejson.dumps( (status, args) )
def validation_error(self, err):
@@ -379,11 +384,7 @@
ctrl.publish(None, fromjson=True)
except ValidationError, err:
self.req.cnx.rollback()
- if not err.entity or isinstance(err.entity, (long, int)):
- eid = err.entity
- else:
- eid = err.entity.eid
- return (False, (eid, err.errors))
+ return (False, (err.entity, err.errors))
except Redirect, redir:
return (True, redir.location)
except Exception, err:
@@ -484,6 +485,24 @@
rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
+ def js_node_clicked(self, treeid, nodeeid):
+ """add/remove eid in treestate cookie"""
+ from cubicweb.web.views.treeview import treecookiename
+ cookies = self.req.get_cookie()
+ statename = treecookiename(treeid)
+ treestate = cookies.get(statename)
+ if treestate is None:
+ cookies[statename] = nodeeid
+ self.req.set_cookie(cookies, statename)
+ else:
+ marked = set(filter(None, treestate.value.split(';')))
+ if nodeeid in marked:
+ marked.remove(nodeeid)
+ else:
+ marked.add(nodeeid)
+ cookies[statename] = ';'.join(marked)
+ self.req.set_cookie(cookies, statename)
+
def js_set_cookie(self, cookiename, cookievalue):
# XXX we should consider jQuery.Cookie
cookiename, cookievalue = str(cookiename), str(cookievalue)
--- a/web/views/basetemplates.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/basetemplates.py Thu May 21 00:50:24 2009 +0200
@@ -7,7 +7,6 @@
"""
__docformat__ = "restructuredtext en"
-
from logilab.mtconverter import html_escape
from cubicweb.vregistry import objectify_selector
--- a/web/views/baseviews.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/baseviews.py Thu May 21 00:50:24 2009 +0200
@@ -11,8 +11,8 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
#from __future__ import with_statement
-
__docformat__ = "restructuredtext en"
+_ = unicode
from rql import nodes
@@ -23,7 +23,6 @@
from cubicweb.view import EntityView, AnyRsetView, View
from cubicweb.common.uilib import cut, printable_value
-_ = unicode
class NullView(AnyRsetView):
"""default view when no result has been found"""
@@ -204,9 +203,10 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
desc = cut(entity.dc_description(), 50)
- self.w(u'<a href="%s" title="%s">' % (html_escape(entity.absolute_url()),
- html_escape(desc)))
- self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col)))
+ self.w(u'<a href="%s" title="%s">' % (
+ html_escape(entity.absolute_url()), html_escape(desc)))
+ self.w(html_escape(self.view('textincontext', self.rset,
+ row=row, col=col)))
self.w(u'</a>')
@@ -214,8 +214,12 @@
id = 'outofcontext'
def cell_call(self, row, col):
- self.w(u'<a href="%s">' % self.entity(row, col).absolute_url())
- self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
+ entity = self.entity(row, col)
+ desc = cut(entity.dc_description(), 50)
+ self.w(u'<a href="%s" title="%s">' % (
+ html_escape(entity.absolute_url()), html_escape(desc)))
+ self.w(html_escape(self.view('textoutofcontext', self.rset,
+ row=row, col=col)))
self.w(u'</a>')
--- a/web/views/boxes.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/boxes.py Thu May 21 00:50:24 2009 +0200
@@ -13,6 +13,7 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import html_escape
@@ -23,7 +24,6 @@
from cubicweb.web import uicfg
from cubicweb.web.box import BoxTemplate
-_ = unicode
class EditBox(BoxTemplate):
"""
@@ -99,10 +99,10 @@
eschema = entity.e_schema
for rschema, teschema, x in self.add_related_schemas(entity):
if x == 'subject':
- label = '%s %s %s %s' % (eschema, rschema, teschema, x)
+ label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
url = self.linkto_url(entity, rschema, teschema, 'object')
else:
- label = '%s %s %s %s' % (teschema, rschema, eschema, x)
+ label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
url = self.linkto_url(entity, rschema, teschema, 'subject')
actions.append(self.mk_action(_(label), url))
return actions
--- a/web/views/calendar.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/calendar.py Thu May 21 00:50:24 2009 +0200
@@ -4,6 +4,8 @@
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
+__docformat__ = "restructuredtext en"
+_ = unicode
from datetime import datetime, date, timedelta
@@ -14,7 +16,6 @@
from cubicweb.utils import strptime, date_range, todate, todatetime
from cubicweb.view import EntityView
-_ = unicode
# useful constants & functions ################################################
@@ -81,7 +82,8 @@
task = self.complete_entity(i)
self.w(u'<div class="vevent">')
self.w(u'<h3 class="summary">%s</h3>' % html_escape(task.dc_title()))
- self.w(u'<div class="description">%s</div>' % html_escape(task.dc_description()))
+ self.w(u'<div class="description">%s</div>'
+ % task.dc_description(format='text/html'))
if task.start:
self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self.format_date(task.start)))
if task.stop:
--- a/web/views/cwproperties.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/cwproperties.py Thu May 21 00:50:24 2009 +0200
@@ -17,12 +17,13 @@
from cubicweb.view import StartupView
from cubicweb.web import uicfg, stdmsgs
from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
+from cubicweb.web.formrenderers import FormRenderer
from cubicweb.web.formfields import FIELDS, StringField
from cubicweb.web.formwidgets import Select, Button, SubmitButton
from cubicweb.web.views import primary
-# some string we want to be internationalizable for nicer display of eproperty
+# some string we want to be internationalizable for nicer display of property
# groups
_('navigation')
_('ui')
@@ -48,10 +49,9 @@
_('category')
-def make_togglable_link(nodeid, label, cookiename):
+def make_togglable_link(nodeid, label):
"""builds a HTML link that switches the visibility & remembers it"""
- action = u"javascript: toggleVisibility('%s', '%s')" % \
- (nodeid, cookiename)
+ action = u"javascript: togglePrefVisibility('%s')" % nodeid
return u'<a href="%s">%s</a>' % (action, label)
def css_class(someclass):
@@ -63,8 +63,8 @@
skip_none = False
-class SystemEPropertiesForm(FormViewMixIn, StartupView):
- id = 'systemepropertiesform'
+class SystemCWPropertiesForm(FormViewMixIn, StartupView):
+ id = 'systempropertiesform'
__select__ = none_rset() & match_user_groups('managers')
title = _('site configuration')
@@ -95,15 +95,15 @@
def call(self, **kwargs):
"""The default view representing the application's index"""
- self.req.add_js('cubicweb.preferences.js')
+ self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
self.req.add_css('cubicweb.preferences.css')
vreg = self.vreg
values = self.defined_keys
groupedopts = {}
mainopts = {}
- # "self.id=='systemepropertiesform'" to skip site wide properties on
+ # "self.id=='systempropertiesform'" to skip site wide properties on
# user's preference but not site's configuration
- for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
+ for key in vreg.user_property_keys(self.id=='systempropertiesform'):
parts = key.split('.')
if parts[0] in vreg:
# appobject configuration
@@ -113,61 +113,73 @@
mainopts.setdefault(parts[0], []).append(key)
# precompute form to consume error message
for group, keys in mainopts.items():
- mainopts[group] = self.form(keys, True)
+ mainopts[group] = self.form(group, keys, False)
+
for group, objects in groupedopts.items():
for oid, keys in objects.items():
- groupedopts[group][oid] = self.form(keys, True)
+ groupedopts[group][oid] = self.form(group + '-' + oid, keys, True)
+
w = self.w
req = self.req
_ = req._
w(u'<h1>%s</h1>\n' % _(self.title))
- # we don't want this in each sub-forms
- w(u'<div id="progress">%s</div>' % self.req._('validating...'))
for label, group, form in sorted((_(g), g, f)
for g, f in mainopts.iteritems()):
status = css_class(self._group_status(group))
w(u'<h2 class="propertiesform">%s</h2>\n' %
- (make_togglable_link('fieldset_' + group, label,
- self._cookie_name(group))))
+ (make_togglable_link('fieldset_' + group, label.capitalize())))
w(u'<div id="fieldset_%s" %s>' % (group, status))
+ w(u'<fieldset class="preferences">')
w(form)
- w(u'</div>')
+ w(u'</fieldset></div>')
+
for label, group, objects in sorted((_(g), g, o)
for g, o in groupedopts.iteritems()):
status = css_class(self._group_status(group))
w(u'<h2 class="propertiesform">%s</h2>\n' %
- (make_togglable_link('fieldset_' + group, label,
- self._cookie_name(group))))
+ (make_togglable_link('fieldset_' + group, label.capitalize())))
w(u'<div id="fieldset_%s" %s>' % (group, status))
- for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
- for o, f in objects.iteritems()):
- w(u'<fieldset class="subentity">')
- w(u'<legend class="componentTitle">%s</legend>\n' % label)
+
+ # create selection
+ sorted_objects = sorted((self.req.__('%s_%s' % (group, o)), o, f)
+ for o, f in objects.iteritems())
+ for label, oid, form in sorted_objects:
+ w(u'<div class="component">')
+ w(u'''<div class="componentLink"><a href="javascript:noop();"
+ onclick="javascript:toggleVisibility('field_%(oid)s_%(group)s')"
+ class="componentTitle">%(label)s</a>''' % {'label':label, 'oid':oid, 'group':group})
+ w(u''' (<div class="openlink"><a href="javascript:noop();"
+ onclick="javascript:openFieldset('fieldset_%(group)s')">%(label)s</a></div>)'''
+ % {'label':_('open all'), 'group':group})
+ w(u'</div>')
docmsgid = '%s_%s_description' % (group, oid)
doc = _(docmsgid)
if doc != docmsgid:
- w(u'<p class="description">%s</p>' % html_escape(doc))
+ w(u'<div class="helper">%s</div>' % html_escape(doc).capitalize())
+ w(u'</div>')
+ w(u'<fieldset id="field_%(oid)s_%(group)s" class="%(group)s preferences hidden">'
+ % {'oid':oid, 'group':group})
w(form)
w(u'</fieldset>')
w(u'</div>')
@property
@cached
- def eprops_rset(self):
+ def cwprops_rset(self):
return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
'P value V, NOT P for_user U')
@property
def defined_keys(self):
values = {}
- for i, entity in enumerate(self.eprops_rset.entities()):
+ for i, entity in enumerate(self.cwprops_rset.entities()):
values[entity.pkey] = i
return values
def entity_for_key(self, key):
values = self.defined_keys
if key in values:
- entity = self.eprops_rset.get_entity(values[key], 0)
+ entity = self.cwprops_rset.get_entity(values[key], 0)
else:
entity = self.vreg.etype_class('CWProperty')(self.req, None, None)
entity.eid = self.req.varmaker.next()
@@ -175,11 +187,11 @@
entity['value'] = self.vreg.property_value(key)
return entity
- def form(self, keys, splitlabel=False):
- buttons = [SubmitButton(),
- Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
- form = CompositeForm(self.req, domid=None, action=self.build_url(),
+ def form(self, formid, keys, splitlabel=False):
+ buttons = [SubmitButton()]
+ form = CompositeForm(self.req, domid=formid, action=self.build_url(),
form_buttons=buttons,
+ onsubmit="return validatePrefsForm('%s')" % formid,
submitmsg=self.req._('changes applied'))
path = self.req.relative_path()
if '?' in path:
@@ -188,7 +200,9 @@
form.form_add_hidden('__redirectpath', path)
for key in keys:
self.form_row(form, key, splitlabel)
- return form.form_render(display_progress_div=False)
+ renderer = CWPropertiesFormRenderer()
+ return form.form_render(display_progress_div=False,
+ renderer=renderer)
def form_row(self, form, key, splitlabel):
entity = self.entity_for_key(key)
@@ -197,20 +211,21 @@
else:
label = key
subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False)
+
subform.append_field(PropertyValueField(name='value', label=label,
eidparam=True))
subform.vreg = self.vreg
subform.form_add_hidden('pkey', key, eidparam=True)
+ subform.form_add_hidden("current-value:%s" % entity.eid,)
form.form_add_subform(subform)
return subform
-
def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
return req.user.eid == rset[row or 0][col]
-class EPropertiesForm(SystemEPropertiesForm):
- id = 'epropertiesform'
+class CWPropertiesForm(SystemCWPropertiesForm):
+ id = 'propertiesform'
__select__ = (
# we don't want guests to be able to come here
match_user_groups('users', 'managers') &
@@ -228,19 +243,19 @@
@property
@cached
- def eprops_rset(self):
+ def cwprops_rset(self):
return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
'P for_user U, U eid %(x)s', {'x': self.user.eid})
def form_row(self, form, key, splitlabel):
- subform = super(EPropertiesForm, self).form_row(form, key, splitlabel)
+ subform = super(CWPropertiesForm, self).form_row(form, key, splitlabel)
# if user is in the managers group and the property is being created,
# we have to set for_user explicitly
if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'):
subform.form_add_hidden('for_user', self.user.eid, eidparam=True)
-# eproperty form objects ######################################################
+# cwproperty form objects ######################################################
class PlaceHolderWidget(object):
@@ -341,3 +356,29 @@
uicfg.autoform_field.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)
+
+
+class CWPropertiesFormRenderer(FormRenderer):
+ """specific renderer for properties"""
+
+ def open_form(self, form, values):
+ err = '<div class="formsg"></div>'
+ return super(CWPropertiesFormRenderer, self).open_form(form, values) + err
+
+ def _render_fields(self, fields, w, form):
+ for field in fields:
+ w(u'<div class="preffield">\n')
+ if self.display_label:
+ w(u'%s' % self.render_label(form, field))
+ error = form.form_field_error(field)
+ w(u'%s' % self.render_help(form, field))
+ w(u'<div class="prefinput">')
+ w(field.render(form, self))
+ w(u'</div>')
+ w(u'</div>')
+
+ def render_buttons(self, w, form):
+ w(u'<div>\n')
+ for button in form.form_buttons:
+ w(u'%s\n' % button.render(form))
+ w(u'</div>')
--- a/web/views/editforms.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/editforms.py Thu May 21 00:50:24 2009 +0200
@@ -52,7 +52,7 @@
# else we will only delete the displayed page
need_navigation = False
- def call(self):
+ def call(self, onsubmit=None):
"""ask for confirmation before real deletion"""
req, w = self.req, self.w
_ = req._
@@ -61,7 +61,7 @@
# XXX above message should have style of a warning
w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
form = CompositeForm(req, domid='deleteconf', copy_nav_params=True,
- action=self.build_url('edit'), onsubmit=None,
+ action=self.build_url('edit'), onsubmit=onsubmit,
form_buttons=[Button(stdmsgs.YES, cwaction='delete'),
Button(stdmsgs.NO, cwaction='cancel')])
done = set()
@@ -350,7 +350,8 @@
def render_form(self, entity, peid, rtype, role, **kwargs):
"""fetch and render the form"""
form = self.vreg.select_object('forms', 'edition', self.req, None,
- entity=entity, set_error_url=False)
+ entity=entity, set_error_url=False,
+ copy_nav_params=False)
self.add_hiddens(form, entity, peid, rtype, role)
divid = '%s-%s-%s' % (peid, rtype, entity.eid)
title = self.schema.rschema(rtype).display_name(self.req, role)
--- a/web/views/ibreadcrumbs.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/ibreadcrumbs.py Thu May 21 00:50:24 2009 +0200
@@ -5,6 +5,7 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import html_escape
@@ -16,7 +17,6 @@
from cubicweb.common.uilib import cut
from cubicweb.web.component import EntityVComponent
-_ = unicode
def bc_title(entity):
textsize = entity.req.property_value('navigation.short-line-size')
@@ -80,7 +80,6 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
- desc = cut(entity.dc_description(), 50)
- self.w(u'<a href="%s" title="%s">%s</a>' % (html_escape(entity.absolute_url()),
- html_escape(desc),
- bc_title(entity)))
+ desc = html_escape(cut(entity.dc_description(), 50))
+ self.w(u'<a href="%s" title="%s">%s</a>' % (
+ html_escape(entity.absolute_url()), desc, bc_title(entity)))
--- a/web/views/idownloadable.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/idownloadable.py Thu May 21 00:50:24 2009 +0200
@@ -26,7 +26,7 @@
def download_box(w, entity, title=None, label=None):
req = entity.req
- w(u'<div class="sideRelated">')
+ w(u'<div class="sideBox">')
if title is None:
title = req._('download')
w(u'<div class="sideBoxTitle downloadBoxTitle"><span>%s</span></div>'
--- a/web/views/management.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/management.py Thu May 21 00:50:24 2009 +0200
@@ -10,7 +10,7 @@
from logilab.mtconverter import html_escape
-from cubicweb.selectors import yes, none_rset, match_user_groups
+from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView
from cubicweb.common.uilib import html_traceback, rest_traceback
from cubicweb.web import formwidgets
@@ -65,6 +65,8 @@
class SecurityManagementView(EntityView, SecurityViewMixIn):
"""display security information for a given entity"""
id = 'security'
+ __select__ = EntityView.__select__ & authenticated_user()
+
title = _('security')
def call(self):
self.w(u'<div id="progress">%s</div>' % self.req._('validating...'))
@@ -181,7 +183,6 @@
self.w(form.form_render(renderer=HTableFormRenderer(display_progress_div=False)))
-
class ErrorView(AnyRsetView):
"""default view when no result has been found"""
__select__ = yes()
@@ -271,6 +272,7 @@
binfo += '\n'
return binfo
+
class ProcessInformationView(StartupView):
id = 'info'
__select__ = none_rset() & match_user_groups('managers')
--- a/web/views/primary.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/primary.py Thu May 21 00:50:24 2009 +0200
@@ -141,7 +141,7 @@
for box in boxes:
if isinstance(box, tuple):
label, rset, vid = box
- self.w(u'<div class="sideRelated">')
+ self.w(u'<div class="sideBox">')
self.wview(vid, rset, title=label)
self.w(u'</div>')
else:
--- a/web/views/timeline.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/timeline.py Thu May 21 00:50:24 2009 +0200
@@ -16,6 +16,7 @@
from cubicweb.selectors import implements
from cubicweb.view import EntityView, StartupView
+_ = unicode
class TimelineJsonView(EntityView):
"""generates a json file to feed Timeline.loadJSON()
@@ -67,7 +68,7 @@
return None
event_data = {'start': start.strftime(self.date_fmt),
'title': html_escape(entity.dc_title()),
- 'description': entity.dc_description(),
+ 'description': entity.dc_description(format='text/html'),
'link': entity.absolute_url(),
}
onclick = self.onclick(entity)
@@ -101,6 +102,7 @@
class TimelineView(TimelineViewMixIn, EntityView):
"""builds a cubicweb timeline widget node"""
id = 'timeline'
+ title = _('timeline')
__select__ = implements(ICalendarable)
need_navigation = False
def call(self, tlunit=None):
--- a/web/views/treeview.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/treeview.py Thu May 21 00:50:24 2009 +0200
@@ -1,22 +1,22 @@
"""Set of tree-building widgets, based on jQuery treeview plugin
:organization: Logilab
-:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+from logilab.common.decorators import monkeypatch
from logilab.mtconverter import html_escape
+from cubicweb.utils import make_uid
from cubicweb.interfaces import ITree
from cubicweb.selectors import implements
from cubicweb.view import EntityView
-from cubicweb.utils import make_uid
def treecookiename(treeid):
return str('treestate-%s' % treeid)
-
class TreeView(EntityView):
id = 'treeview'
itemvid = 'treeitemview'
@@ -25,26 +25,22 @@
def call(self, subvid=None, treeid=None, initial_load=True):
if subvid is None:
- if 'subvid' in self.req.form:
- subvid = self.req.form.pop('subvid') # consume it
- else:
- subvid = 'oneline'
+ subvid = self.req.form.pop('subvid', 'oneline') # consume it
if treeid is None:
- if 'treeid' in self.req.form:
- treeid = self.req.form.pop('treeid')
- else:
+ treeid = self.req.form.pop('treeid', None)
+ if treeid is None:
+ self.warning('Tree state won\'t be properly restored after next reload')
treeid = make_uid('throw away uid')
- self.warning('Tree state won\'t be properly restored after next reload')
- if initial_load:
+ self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes))
+ for rowidx in xrange(len(self.rset)):
+ self.wview(self.itemvid, self.rset, row=rowidx, col=0,
+ vid=subvid, parentvid=self.id, treeid=treeid)
+ self.w(u'</ul>')
+ if initial_load and not self.req.form.get('fname'):
self.req.add_css('jquery.treeview.css')
self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
self.req.html_headers.add_onload(u"""
jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
- self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes))
- for rowidx in xrange(len(self.rset)):
- self.wview(self.itemvid, self.rset, row=rowidx, col=0,
- vid=subvid, parentvid=self.id)
- self.w(u'</ul>')
class FileTreeView(TreeView):
@@ -57,8 +53,6 @@
def call(self, subvid=None, treeid=None, initial_load=True):
super(FileTreeView, self).call(treeid=treeid, subvid='filetree-oneline', initial_load=initial_load)
-
-
class FileItemInnerView(EntityView):
"""inner view used by the TreeItemView instead of oneline view
@@ -70,17 +64,18 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf():
- self.w(u'<div class="folder">%s</div>' % entity.view('oneline'))
+ self.w(u'<div class="folder">%s</div>\n' % entity.view('oneline'))
else:
# XXX define specific CSS classes according to mime types
- self.w(u'<div class="file">%s</div>' % entity.view('oneline'))
+ self.w(u'<div class="file">%s</div>\n' % entity.view('oneline'))
class DefaultTreeViewItemView(EntityView):
"""default treeitem view for entities which don't implement ITree"""
id = 'treeitemview'
- def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
+ def cell_call(self, row, col, vid='oneline', parentvid='treeview', treeid=None):
+ assert treeid is not None
entity = self.entity(row, col)
itemview = self.view(vid, self.rset, row=row, col=col)
if row == len(self.rset) - 1:
@@ -95,32 +90,61 @@
(each item should be expandable if it's not a tree leaf)
"""
id = 'treeitemview'
- __select__ = implements(ITree)
+ __select__ = EntityView.__select__ & implements(ITree) # XXX
- def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
+ def open_state(self, eeid, treeid):
+ cookies = self.req.get_cookie()
+ treestate = cookies.get(treecookiename(treeid))
+ if treestate:
+ return str(eeid) in treestate.value.split(';')
+ return False
+
+ def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview'):
+ w = self.w
entity = self.entity(row, col)
- cssclasses = []
- is_leaf = False
- if row == len(self.rset) - 1:
- is_leaf = True
+ liclasses = []
+ is_last = row == len(self.rset) - 1
+ is_open = self.open_state(entity.eid, treeid)
if not hasattr(entity, 'is_leaf') or entity.is_leaf():
- if is_leaf : cssclasses.append('last')
- self.w(u'<li class="%s">' % u' '.join(cssclasses))
+ if is_last:
+ liclasses.append('last')
+ w(u'<li class="%s">' % u' '.join(liclasses))
else:
rql = entity.children_rql() % {'x': entity.eid}
url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
pageid=self.req.pageid,
- subvid=vid,
- noautoload=True))
- cssclasses.append('expandable')
- divclasses = ['hitarea expandable-hitarea']
- if is_leaf :
- cssclasses.append('lastExpandable')
- divclasses.append('lastExpandable-hitarea')
- self.w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(cssclasses)))
- self.w(u'<div class="%s"> </div>' % u' '.join(divclasses))
+ treeid=treeid,
+ fname='view',
+ subvid=vid))
+ divclasses = ['hitarea']
+ if is_open:
+ liclasses.append('collapsable')
+ divclasses.append('collapsable-hitarea')
+ else:
+ liclasses.append('expandable')
+ divclasses.append('expandable-hitarea')
+ if is_last:
+ if is_open:
+ liclasses.append('lastCollapsable')
+ divclasses.append('lastCollapsable-hitarea')
+ else:
+ liclasses.append('lastExpandable')
+ divclasses.append('lastExpandable-hitarea')
+ if is_open:
+ w(u'<li class="%s">' % u' '.join(liclasses))
+ else:
+ w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(liclasses)))
+ divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ %\
+ (treeid, entity.eid)
+ w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail))
+
# add empty <ul> because jquery's treeview plugin checks for
# sublists presence
- self.w(u'<ul class="placeholder"><li>place holder</li></ul>')
+ if not is_open:
+ w(u'<ul class="placeholder"><li>place holder</li></ul>')
+ # the local node info
self.wview(vid, self.rset, row=row, col=col)
- self.w(u'</li>')
+ if is_open: # => not leaf => rql is defined
+ self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False)
+ w(u'</li>')
+
--- a/web/views/urlrewrite.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/urlrewrite.py Thu May 21 00:50:24 2009 +0200
@@ -73,8 +73,8 @@
rules = [
('/schema', dict(vid='schema')),
('/index', dict(vid='index')),
- ('/myprefs', dict(vid='epropertiesform')),
- ('/siteconfig', dict(vid='systemepropertiesform')),
+ ('/myprefs', dict(vid='propertiesform')),
+ ('/siteconfig', dict(vid='systempropertiesform')),
('/manage', dict(vid='manage')),
('/notfound', dict(vid='404')),
('/error', dict(vid='error')),
--- a/web/views/xmlrss.py Thu May 21 00:44:57 2009 +0200
+++ b/web/views/xmlrss.py Thu May 21 00:50:24 2009 +0200
@@ -5,6 +5,7 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from time import timezone
@@ -17,8 +18,6 @@
from cubicweb.web.box import BoxTemplate
from cubicweb.common.uilib import simple_sgml_tag
-_ = unicode
-
# base xml views ##############################################################