# HG changeset patch # User Sylvain Thénault # Date 1288973500 -3600 # Node ID b00f31b3b045b7b4476935430929868fe2b4876d # Parent 55a94beb521d5bf33ea11cda2542c56f805d92ba# Parent 51ddb4842c567fe075972d5c647b26ffb62156eb backport stable diff -r 55a94beb521d -r b00f31b3b045 cwctl.py --- a/cwctl.py Wed Nov 03 16:39:49 2010 +0100 +++ b/cwctl.py Fri Nov 05 17:11:40 2010 +0100 @@ -235,9 +235,9 @@ tinfo = cwcfg.cube_pkginfo(cube) tversion = tinfo.version cfgpb.add_cube(cube, tversion) - except ConfigurationError: + except ConfigurationError, ex: tinfo = None - tversion = '[missing cube information]' + tversion = '[missing cube information: %s]' % ex print '* %s %s' % (cube.ljust(namesize), tversion) if self.config.verbose: if tinfo: diff -r 55a94beb521d -r b00f31b3b045 devtools/repotest.py --- a/devtools/repotest.py Wed Nov 03 16:39:49 2010 +0100 +++ b/devtools/repotest.py Fri Nov 05 17:11:40 2010 +0100 @@ -22,6 +22,7 @@ __docformat__ = "restructuredtext en" +from copy import deepcopy from pprint import pprint from logilab.common.decorators import clear_cache @@ -388,6 +389,11 @@ return x.value return _orig_choose_term(self, DumbOrderedDict2(sourceterms, get_key)) +from cubicweb.server.sources.pyrorql import PyroRQLSource +_orig_syntax_tree_search = PyroRQLSource.syntax_tree_search + +def _syntax_tree_search(*args, **kwargs): + return deepcopy(_orig_syntax_tree_search(*args, **kwargs)) def do_monkey_patch(): RQLRewriter.insert_snippets = _insert_snippets @@ -397,6 +403,7 @@ ExecutionPlan.init_temp_table = _init_temp_table PartPlanInformation.merge_input_maps = _merge_input_maps PartPlanInformation._choose_term = _choose_term + PyroRQLSource.syntax_tree_search = _syntax_tree_search def undo_monkey_patch(): RQLRewriter.insert_snippets = _orig_insert_snippets @@ -405,3 +412,4 @@ ExecutionPlan.init_temp_table = _orig_init_temp_table PartPlanInformation.merge_input_maps = _orig_merge_input_maps PartPlanInformation._choose_term = _orig_choose_term + PyroRQLSource.syntax_tree_search = _orig_syntax_tree_search diff -r 55a94beb521d -r b00f31b3b045 devtools/testlib.py --- a/devtools/testlib.py Wed Nov 03 16:39:49 2010 +0100 +++ b/devtools/testlib.py Fri Nov 05 17:11:40 2010 +0100 @@ -661,6 +661,13 @@ # content validation ####################################################### + def assertDocTestFile(self, testfile): + # doctest returns tuple (failure_count, test_count) + result = self.shell.process_script(testfile) + if result[0] and result[1]: + raise self.failureException("doctest file '%s' failed" + % testfile) + # validators are used to validate (XML, DTD, whatever) view's content # validators availables are : # DTDValidator : validates XML + declared DTD diff -r 55a94beb521d -r b00f31b3b045 migration.py --- a/migration.py Wed Nov 03 16:39:49 2010 +0100 +++ b/migration.py Fri Nov 05 17:11:40 2010 +0100 @@ -358,8 +358,10 @@ self.commit() else: # script_mode == 'doctest' import doctest - doctest.testfile(migrscript, module_relative=False, - optionflags=doctest.ELLIPSIS, globs=scriptlocals) + return doctest.testfile(migrscript, module_relative=False, + optionflags=doctest.ELLIPSIS, + encoding='utf-8', + globs=scriptlocals) self._context_stack.pop() def cmd_option_renamed(self, oldname, newname): diff -r 55a94beb521d -r b00f31b3b045 misc/migration/3.10.4_Any.py --- a/misc/migration/3.10.4_Any.py Wed Nov 03 16:39:49 2010 +0100 +++ b/misc/migration/3.10.4_Any.py Fri Nov 05 17:11:40 2010 +0100 @@ -1,6 +1,6 @@ for eschema in schema.entities(): if not (eschema.final or 'cw_source' in eschema.subjrels): - add_relation_definition(eschema, 'cw_source', 'CWSource') + add_relation_definition(eschema.type, 'cw_source', 'CWSource') sql('INSERT INTO cw_source_relation(eid_from, eid_to) ' 'SELECT e.eid,s.cw_eid FROM entities as e, cw_CWSource as s ' diff -r 55a94beb521d -r b00f31b3b045 server/migractions.py --- a/server/migractions.py Wed Nov 03 16:39:49 2010 +0100 +++ b/server/migractions.py Fri Nov 05 17:11:40 2010 +0100 @@ -1461,7 +1461,7 @@ return res def rqliter(self, rql, kwargs=None, ask_confirm=True): - return ForRqlIterator(self, rql, None, ask_confirm) + return ForRqlIterator(self, rql, kwargs, ask_confirm) # broken db commands ###################################################### diff -r 55a94beb521d -r b00f31b3b045 server/msplanner.py --- a/server/msplanner.py Wed Nov 03 16:39:49 2010 +0100 +++ b/server/msplanner.py Fri Nov 05 17:11:40 2010 +0100 @@ -745,6 +745,15 @@ and self._need_ext_source_access(term, rel): self.needsplit = True return + else: + # remove sources only accessing to constant nodes + for source, terms in self._sourcesterms.items(): + if source is self.system_source: + continue + if not any(x for x in terms if not isinstance(x, Constant)): + del self._sourcesterms[source] + if len(self._sourcesterms) < 2: + self.needsplit = False @cached def _need_ext_source_access(self, var, rel): diff -r 55a94beb521d -r b00f31b3b045 server/repository.py --- a/server/repository.py Wed Nov 03 16:39:49 2010 +0100 +++ b/server/repository.py Fri Nov 05 17:11:40 2010 +0100 @@ -1120,9 +1120,13 @@ rql = 'DELETE Y %s X WHERE X eid %%(x)s' % rtype if scleanup: # source cleaning: only delete relations stored locally - rql += ', NOT Y cw_source S, S name %(source)s' - session.execute(rql, {'x': eid, 'source': sourceuri}, - build_descr=False) + rql += ', NOT (Y cw_source S, S name %(source)s)' + try: + session.execute(rql, {'x': eid, 'source': sourceuri}, + build_descr=False) + except: + self.exception('error while cascading delete for entity %s ' + 'from %s. RQL: %s', entity, sourceuri, rql) self.system_source.delete_info(session, entity, sourceuri, extid) def locate_relation_source(self, session, subject, rtype, object): diff -r 55a94beb521d -r b00f31b3b045 server/serverconfig.py --- a/server/serverconfig.py Wed Nov 03 16:39:49 2010 +0100 +++ b/server/serverconfig.py Fri Nov 05 17:11:40 2010 +0100 @@ -297,7 +297,12 @@ _sconfig = SourceConfiguration( self, options=SOURCE_TYPES['native'].options) for attr, val in sconfig.items(): - _sconfig.set_option(attr, val) + try: + _sconfig.set_option(attr, val) + except lgconfig.OptionError: + # skip adapter, may be present on pre 3.10 instances + if attr != 'adapter': + self.error('skip unknown option %s in sources file') sconfig = _sconfig print >> stream, '[%s]' % section print >> stream, generate_source_config(sconfig) diff -r 55a94beb521d -r b00f31b3b045 server/sources/pyrorql.py --- a/server/sources/pyrorql.py Wed Nov 03 16:39:49 2010 +0100 +++ b/server/sources/pyrorql.py Fri Nov 05 17:11:40 2010 +0100 @@ -255,7 +255,7 @@ try: for etype, extid in modified: try: - eid = self.local_eid(cnx, extid, session) + eid = self.local_eid(cnx, extid, session)[0] if eid is not None: rset = session.eid_rset(eid, etype) entity = rset.get_entity(0, 0) diff -r 55a94beb521d -r b00f31b3b045 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Wed Nov 03 16:39:49 2010 +0100 +++ b/server/test/unittest_msplanner.py Fri Nov 05 17:11:40 2010 +0100 @@ -811,7 +811,8 @@ # use a guest user self.session = self.user_groups_session('guests') ueid = self.session.user.eid - # note: same as the above query but because of the subquery usage, the display differs (not printing solutions for each union) + # note: same as the above query but because of the subquery usage, the + # display differs (not printing solutions for each union) self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla"', [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])], [self.cards, self.system], None, {'E': 'table1.C0'}, []), @@ -1709,6 +1710,18 @@ ], {'x': ueid, 'y': ueid}) + def test_delete_relation3(self): + repo._type_source_cache[999999] = ('Note', 'cards', 999999) + self._test('DELETE Y multisource_inlined_rel X WHERE X eid %(x)s, NOT (Y cw_source S, S name %(source)s)', + [('DeleteRelationsStep', + [('OneFetchStep', + [('Any Y,999999 WHERE Y multisource_inlined_rel 999999, NOT EXISTS(Y cw_source S, S name "cards"), S is CWSource, Y is IN(Card, Note)', + [{'S': 'CWSource', 'Y': 'Card'}, {'S': 'CWSource', 'Y': 'Note'}])], + None, None, [self.system], {}, + [])] + )], + {'x': 999999, 'source': 'cards'}) + def test_delete_entity1(self): repo._type_source_cache[999999] = ('Note', 'system', 999999) self._test('DELETE Note X WHERE X eid %(x)s, NOT Y multisource_rel X', @@ -1926,7 +1939,6 @@ def test_source_specified_3_2(self): self.skipTest('oops') - self.set_debug('DBG_MS') self._test('Any STN WHERE X is Note, X type XT, X in_state ST, ST name STN, X cw_source S, S name "cards"', [('OneFetchStep', [('Any X,XT WHERE X is Card, X title XT', diff -r 55a94beb521d -r b00f31b3b045 test/unittest_entity.py --- a/test/unittest_entity.py Wed Nov 03 16:39:49 2010 +0100 +++ b/test/unittest_entity.py Fri Nov 05 17:11:40 2010 +0100 @@ -24,6 +24,8 @@ from cubicweb.devtools.testlib import CubicWebTC from cubicweb.mttransforms import HAS_TAL from cubicweb.entities import fetch_config +from cubicweb.uilib import soup2xhtml + class EntityTC(CubicWebTC): @@ -412,24 +414,14 @@ self.assertEqual(e.printable_value('content'), u'hop\nhop\nhip\nmomo') def test_printable_value_bad_html_ms(self): - self.skipTest('fix soup2xhtml to handle this test') req = self.request() e = req.create_entity('Card', title=u'bad html', content=u'
R&D
', content_format=u'text/html') tidy = lambda x: x.replace('\n', '') e.cw_attr_cache['content'] = u'
ms orifice produces weird html
' - self.assertEqual(tidy(e.printable_value('content')), - u'
ms orifice produces weird html
') - import tidy as tidymod # apt-get install python-tidy - tidy = lambda x: str(tidymod.parseString(x.encode('utf-8'), - **{'drop_proprietary_attributes': True, - 'output_xhtml': True, - 'show_body_only' : True, - 'quote-nbsp' : False, - 'char_encoding' : 'utf8'})).decode('utf-8').strip() - self.assertEqual(tidy(e.printable_value('content')), - u'
ms orifice produces weird html
') - + # Caution! current implementation of soup2xhtml strips first div element + content = soup2xhtml(e.printable_value('content'), 'utf-8') + self.assertMultiLineEqual(content, u'
ms orifice produces weird html
') def test_fulltextindex(self): e = self.vreg['etypes'].etype_class('File')(self.request()) diff -r 55a94beb521d -r b00f31b3b045 test/unittest_uilib.py --- a/test/unittest_uilib.py Wed Nov 03 16:39:49 2010 +0100 +++ b/test/unittest_uilib.py Fri Nov 05 17:11:40 2010 +0100 @@ -20,10 +20,16 @@ __docformat__ = "restructuredtext en" + +import pkg_resources from logilab.common.testlib import TestCase, unittest_main +from unittest2 import skipIf from cubicweb import uilib +lxml_version = pkg_resources.get_distribution('lxml').version.split('.') + + class UILIBTC(TestCase): def test_remove_tags(self): @@ -91,7 +97,15 @@ got = uilib.text_cut(text, 30) self.assertEqual(got, expected) + def test_soup2xhtml_0(self): + self.assertEqual(uilib.soup2xhtml('hop\r\nhop', 'ascii'), + 'hop\nhop') + def test_soup2xhtml_1_1(self): + self.assertEqual(uilib.soup2xhtml('hop', 'ascii'), + 'hop') + self.assertEqual(uilib.soup2xhtml('hop
', 'ascii'), + 'hop
') self.assertEqual(uilib.soup2xhtml('hop
', 'ascii'), 'hop
') self.assertEqual(uilib.soup2xhtml('
hop', 'ascii'), @@ -115,11 +129,14 @@ self.assertEqual(uilib.soup2xhtml('hop hop', 'ascii'), 'hop hop') - def test_soup2xhtml_2_2(self): + def test_soup2xhtml_2_2a(self): self.assertEqual(uilib.soup2xhtml('hop ', 'ascii'), 'hop ') self.assertEqual(uilib.soup2xhtml(' hop', 'ascii'), ' hop') + + @skipIf(lxml_version < ['2', '2'], 'expected behaviour on recent version of lxml only') + def test_soup2xhtml_2_2b(self): self.assertEqual(uilib.soup2xhtml('hop hop', 'ascii'), 'hop hop') @@ -139,6 +156,10 @@ self.assertEqual(uilib.soup2xhtml('hop hop', 'ascii'), 'hop hop') + def test_soup2xhtml_3_3(self): + self.assertEqual(uilib.soup2xhtml(' hop ', 'ascii'), + ' hop ') + def test_js(self): self.assertEqual(str(uilib.js.pouet(1, "2")), 'pouet(1,"2")') @@ -147,6 +168,23 @@ self.assertEqual(str(uilib.js.cw.pouet(1, "2").pouet(None)), 'cw.pouet(1,"2").pouet(null)') + def test_embedded_css(self): + incoming = u"""voir le ticket

text

""" + expected = 'voir le ticket

text

' + self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected) + + def test_unknown_namespace(self): + incoming = ''' + + +
XXXXXXX
''' + expected = '''\ +\ +\ +
XXXXXXX
''' + self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected) + + if __name__ == '__main__': unittest_main() diff -r 55a94beb521d -r b00f31b3b045 uilib.py --- a/uilib.py Wed Nov 03 16:39:49 2010 +0100 +++ b/uilib.py Fri Nov 05 17:11:40 2010 +0100 @@ -109,12 +109,6 @@ return u'' return REF_PROG.sub(lambda obj, view=view:_subst_rql(view, obj), text) -# fallback implementation, nicer one defined below if lxml is available -def soup2xhtml(data, encoding): - # normalize line break - # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 - return u'\n'.join(data.splitlines()) - # fallback implementation, nicer one defined below if lxml> 2.0 is available def safe_cut(text, length): """returns a string of length based on , removing any html @@ -132,27 +126,30 @@ fallback_safe_cut = safe_cut REM_ROOT_HTML_TAGS = re.compile('', re.U) + try: - from lxml import etree -except (ImportError, AttributeError): - # gae environment: lxml not available - pass -else: + from lxml import etree, html + from lxml.html import clean, defs + + ALLOWED_TAGS = (defs.general_block_tags | defs.list_tags | defs.table_tags | + defs.phrase_tags | defs.font_style_tags | + set(('span', 'a', 'br', 'img', 'map', 'area', 'sub', 'sup')) + ) + + CLEANER = clean.Cleaner(allow_tags=ALLOWED_TAGS, remove_unknown_tags=False, + style=True, safe_attrs_only=True, + add_nofollow=False, + ) def soup2xhtml(data, encoding): - """tidy (at least try) html soup and return the result - - Note: the function considers a string with no surrounding tag as valid - if
`data`
can be parsed by an XML parser + """tidy html soup by allowing some element tags and return the result """ # remove spurious and tags, then normalize line break # (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1) data = REM_ROOT_HTML_TAGS.sub('', u'\n'.join(data.splitlines())) - # XXX lxml 1.1 support still needed ? - xmltree = etree.HTML('
%s
' % data) - # NOTE: lxml 1.1 (etch platforms) doesn't recognize - # the encoding=unicode parameter (lxml 2.0 does), this is - # why we specify an encoding and re-decode to unicode later + xmltree = etree.HTML(CLEANER.clean_html('
%s
' % data)) + # NOTE: lxml 2.0 does support encoding='unicode', but last time I (syt) + # tried I got weird results (lxml 2.2.8) body = etree.tostring(xmltree[0], encoding=encoding) # remove and and decode to unicode snippet = body[6:-7].decode(encoding) @@ -163,7 +160,29 @@ snippet = snippet[5:-6] return snippet - if hasattr(etree.HTML('
test
'), 'iter'): + # lxml.Cleaner envelops text elements by internal logic (not accessible) + # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 + # TODO drop attributes in elements + # TODO add policy configuration (content only, embedded content, ...) + # XXX this is buggy for "

text1

text2

"... + # XXX drop these two snippets action and follow the lxml behaviour + # XXX (tests need to be updated) + # if snippet.startswith('
') and snippet.endswith('
'): + # snippet = snippet[5:-6] + # if snippet.startswith('

') and snippet.endswith('

'): + # snippet = snippet[3:-4] + return snippet.decode(encoding) + +except (ImportError, AttributeError): + # gae environment: lxml not available + # fallback implementation + def soup2xhtml(data, encoding): + # normalize line break + # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 + return u'\n'.join(data.splitlines()) +else: + + if hasattr(etree.HTML('
test
'), 'iter'): # XXX still necessary? def safe_cut(text, length): """returns an html document of length based on , @@ -342,6 +361,16 @@ import traceback +def exc_message(ex, encoding): + try: + return unicode(ex) + except: + try: + return unicode(str(ex), encoding, 'replace') + except: + return unicode(repr(ex), encoding, 'replace') + + def rest_traceback(info, exception): """return a ReST formated traceback""" res = [u'Traceback\n---------\n::\n'] diff -r 55a94beb521d -r b00f31b3b045 utils.py --- a/utils.py Wed Nov 03 16:39:49 2010 +0100 +++ b/utils.py Fri Nov 05 17:11:40 2010 +0100 @@ -201,6 +201,7 @@ def pop(self, i): self._size -= 1 + class UStringIO(list): """a file wrapper which automatically encode unicode string to an encoding specifed in the constructor diff -r 55a94beb521d -r b00f31b3b045 web/application.py --- a/web/application.py Wed Nov 03 16:39:49 2010 +0100 +++ b/web/application.py Fri Nov 05 17:11:40 2010 +0100 @@ -140,13 +140,7 @@ class CookieSessionHandler(object): - """a session handler using a cookie to store the session identifier - - :cvar SESSION_VAR: - string giving the name of the variable used to store the session - identifier - """ - SESSION_VAR = '__session' + """a session handler using a cookie to store the session identifier""" def __init__(self, appli): self.vreg = appli.vreg @@ -180,6 +174,14 @@ """ self.session_manager.clean_sessions() + def session_cookie(self, req): + """return a string giving the name of the cookie used to store the + session identifier. + """ + if req.https: + return '__%s_https_session' % self.vreg.config.appid + return '__%s_session' % self.vreg.config.appid + def set_session(self, req): """associate a session to the request @@ -193,8 +195,9 @@ :raise Redirect: if authentication has occurred and succeed """ cookie = req.get_cookie() + sessioncookie = self.session_cookie(req) try: - sessionid = str(cookie[self.SESSION_VAR].value) + sessionid = str(cookie[sessioncookie].value) except KeyError: # no session cookie session = self.open_session(req) else: @@ -206,7 +209,7 @@ try: session = self.open_session(req) except AuthenticationError: - req.remove_cookie(cookie, self.SESSION_VAR) + req.remove_cookie(cookie, sessioncookie) raise def get_session(self, req, sessionid): @@ -215,10 +218,11 @@ def open_session(self, req): session = self.session_manager.open_session(req) cookie = req.get_cookie() - cookie[self.SESSION_VAR] = session.sessionid + sessioncookie = self.session_cookie(req) + cookie[sessioncookie] = session.sessionid if req.https and req.base_url().startswith('https://'): - cookie[self.SESSION_VAR]['secure'] = True - req.set_cookie(cookie, self.SESSION_VAR, maxage=None) + cookie[sessioncookie]['secure'] = True + req.set_cookie(cookie, sessioncookie, maxage=None) if not session.anonymous_session: self._postlogin(req) return session @@ -265,7 +269,8 @@ `AuthenticationError` """ self.session_manager.close_session(req.session) - req.remove_cookie(req.get_cookie(), self.SESSION_VAR) + sessioncookie = self.session_cookie(req) + req.remove_cookie(req.get_cookie(), sessioncookie) raise LogOut(url=goto_url) diff -r 55a94beb521d -r b00f31b3b045 web/facet.py --- a/web/facet.py Wed Nov 03 16:39:49 2010 +0100 +++ b/web/facet.py Fri Nov 05 17:11:40 2010 +0100 @@ -709,9 +709,18 @@ subj = self.filtered_variable.name obj = utils.rqlvar_maker(defined=self.rqlst.defined_vars, aliases=self.rqlst.aliases).next() - rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s, %s' % ( - self.filtered_variable.name, subj, self.rtype, obj, - self.rqlst.where.as_string()) + restrictions = [] + if self.rqlst.where: + restrictions.append(self.rqlst.where.as_string()) + if self.rqlst.with_: + restrictions.append('WITH ' + ','.join( + term.as_string() for term in self.rqlst.with_)) + if restrictions: + restrictions = ',' + ','.join(restrictions) + else: + restrictions = '' + rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s%s' % ( + self.filtered_variable.name, subj, self.rtype, obj, restrictions) try: return bool(self.rqlexec(rql, self.cw_rset and self.cw_rset.args)) except: diff -r 55a94beb521d -r b00f31b3b045 web/formwidgets.py --- a/web/formwidgets.py Wed Nov 03 16:39:49 2010 +0100 +++ b/web/formwidgets.py Fri Nov 05 17:11:40 2010 +0100 @@ -888,7 +888,9 @@ values = {} path = req.form.get(field.input_name(form, 'path')) if isinstance(path, basestring): - path = path.strip() or None + path = path.strip() + if path is None: + path = u'' fqs = req.form.get(field.input_name(form, 'fqs')) if isinstance(fqs, basestring): fqs = fqs.strip() or None diff -r 55a94beb521d -r b00f31b3b045 web/test/unittest_formwidgets.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/test/unittest_formwidgets.py Fri Nov 05 17:11:40 2010 +0100 @@ -0,0 +1,44 @@ +# copyright 2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""unittests for cw.web.formwidgets""" + +from logilab.common.testlib import TestCase, unittest_main, mock_object as mock + +from cubicweb.devtools import TestServerConfiguration, fake +from cubicweb.web import uicfg, formwidgets, formfields + +from cubes.file.entities import File + +def setup_module(*args): + global schema + config = TestServerConfiguration('data', apphome=WidgetsTC.datadir) + config.bootstrap_cubes() + schema = config.load_schema() + +class WidgetsTC(TestCase): + + def test_state_fields(self): + field = formfields.guess_field(schema['Bookmark'], schema['path']) + widget = formwidgets.EditableURLWidget() + req = fake.FakeRequest(form={'path-subjectfqs:A': 'param=value&vid=view'}) + form = mock(_cw=req, formvalues={}, edited_entity=mock(eid='A')) + self.assertEqual(widget.process_field_data(form, field), + '?param=value%26vid%3Dview') + +if __name__ == '__main__': + unittest_main() diff -r 55a94beb521d -r b00f31b3b045 web/views/basecomponents.py --- a/web/views/basecomponents.py Wed Nov 03 16:39:49 2010 +0100 +++ b/web/views/basecomponents.py Fri Nov 05 17:11:40 2010 +0100 @@ -80,10 +80,10 @@ __abstract__ = True cw_property_defs = component.override_ctx( component.CtxComponent, - vocabulary=['header-left', 'header-center', 'header-right']) + vocabulary=['header-left', 'header-right']) # don't want user to hide this component using an cwproperty site_wide = True - context = _('header-center') + context = _('header-left') class ApplLogo(HeaderComponent): @@ -99,7 +99,6 @@ class ApplicationName(HeaderComponent): """display the instance name""" __regid__ = 'appliname' - context = _('header-center') def render(self, w): title = self._cw.property_value('ui.site-title') diff -r 55a94beb521d -r b00f31b3b045 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Wed Nov 03 16:39:49 2010 +0100 +++ b/web/views/basecontrollers.py Fri Nov 05 17:11:40 2010 +0100 @@ -27,6 +27,7 @@ from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError, AuthenticationError, typed_eid) from cubicweb.utils import UStringIO, json, json_dumps +from cubicweb.uilib import exc_message from cubicweb.selectors import authenticated_user, anonymous_user, match_form_params from cubicweb.mail import format_mail from cubicweb.web import Redirect, RemoteCallFailed, DirectResponse @@ -252,6 +253,7 @@ # we receive unicode keys which is not supported by the **syntax return dict((str(key), value) for key, value in extraargs.iteritems()) + class JSonController(Controller): __regid__ = 'json' @@ -281,15 +283,15 @@ except ValueError, exc: self.exception('error while decoding json arguments for js_%s: %s', fname, args, exc) - raise RemoteCallFailed(repr(exc)) + raise RemoteCallFailed(exc_message(exc, self._cw.encoding)) try: result = func(*args) except (RemoteCallFailed, DirectResponse): raise - except Exception, ex: + except Exception, exc: self.exception('an exception occurred while calling js_%s(%s): %s', - fname, args, ex) - raise RemoteCallFailed(repr(ex)) + fname, args, exc) + raise RemoteCallFailed(exc_message(exc, self._cw.encoding)) if result is None: return '' # get unicode on @htmlize methods, encoded string on @jsonize methods diff -r 55a94beb521d -r b00f31b3b045 web/views/basetemplates.py --- a/web/views/basetemplates.py Wed Nov 03 16:39:49 2010 +0100 +++ b/web/views/basetemplates.py Fri Nov 05 17:11:40 2010 +0100 @@ -339,8 +339,7 @@ """build the top menu with authentification info and the rql box""" w = self.w w(u'\n') - for colid, context in (('firstcolumn', 'header-left'), - ('headtext', 'header-center'), + for colid, context in (('headtext', 'header-left'), ('header-right', 'header-right'), ): w(u'