--- 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:
--- 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
--- 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
--- 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):
--- 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 '
--- 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 ######################################################
--- 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):
--- 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):
--- 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)
--- 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)
--- 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',
--- 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'<div>R&D<br>',
content_format=u'text/html')
tidy = lambda x: x.replace('\n', '')
e.cw_attr_cache['content'] = u'<div x:foo="bar">ms orifice produces weird html</div>'
- self.assertEqual(tidy(e.printable_value('content')),
- u'<div>ms orifice produces weird html</div>')
- 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'<div>ms orifice produces weird html</div>')
-
+ # Caution! current implementation of soup2xhtml strips first div element
+ content = soup2xhtml(e.printable_value('content'), 'utf-8')
+ self.assertMultiLineEqual(content, u'<div>ms orifice produces weird html</div>')
def test_fulltextindex(self):
e = self.vreg['etypes'].etype_class('File')(self.request())
--- 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<div>', 'ascii'),
+ 'hop<div/>')
self.assertEqual(uilib.soup2xhtml('hop <div>', 'ascii'),
'hop <div/>')
self.assertEqual(uilib.soup2xhtml('<div> hop', 'ascii'),
@@ -115,11 +129,14 @@
self.assertEqual(uilib.soup2xhtml('hop <body> hop', 'ascii'),
'hop hop')
- def test_soup2xhtml_2_2(self):
+ def test_soup2xhtml_2_2a(self):
self.assertEqual(uilib.soup2xhtml('hop </body>', 'ascii'),
'hop ')
self.assertEqual(uilib.soup2xhtml('</body> 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 </body> hop', 'ascii'),
'hop hop')
@@ -139,6 +156,10 @@
self.assertEqual(uilib.soup2xhtml('hop </html> hop', 'ascii'),
'hop hop')
+ def test_soup2xhtml_3_3(self):
+ self.assertEqual(uilib.soup2xhtml('<script>test</script> 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 <style type="text/css">@font-face { font-family: "Cambria"; }p.MsoNormal, li.MsoNormal, div.MsoNormal { margin: 0cm 0cm 10pt; font-size: 12pt; font-family: "Times New Roman"; }a:link, span.MsoHyperlink { color: blue; text-decoration: underline; }a:visited, span.MsoHyperlinkFollowed { color: purple; text-decoration: underline; }div.Section1 { page: Section1; }</style></p><p class="MsoNormal">text</p>"""
+ expected = 'voir le ticket <p class="MsoNormal">text</p>'
+ self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected)
+
+ def test_unknown_namespace(self):
+ incoming = '''<table cellspacing="0" cellpadding="0" width="81" border="0" x:str="" style="width: 61pt; border-collapse: collapse">
+<colgroup><col width="81" style="width: 61pt; mso-width-source: userset; mso-width-alt: 2962"/></colgroup>
+<tbody><tr height="17" style="height: 12.75pt"><td width="81" height="17" style="border-right: #e0dfe3; border-top: #e0dfe3; border-left: #e0dfe3; width: 61pt; border-bottom: #e0dfe3; height: 12.75pt; background-color: transparent"><font size="2">XXXXXXX</font></td></tr></tbody>
+</table>'''
+ expected = '''<table cellspacing="0" cellpadding="0" width="81" border="0">\
+<colgroup><col width="81"/></colgroup>\
+<tbody><tr height="17"><td width="81" height="17">XXXXXXX</td></tr></tbody>\
+</table>'''
+ self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected)
+
+
if __name__ == '__main__':
unittest_main()
--- 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 <length> based on <text>, removing any html
@@ -132,27 +126,30 @@
fallback_safe_cut = safe_cut
REM_ROOT_HTML_TAGS = re.compile('</(body|html)>', 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 <div>`data`</div> can be parsed by an XML parser
+ """tidy html soup by allowing some element tags and return the result
"""
# remove spurious </body> and </html> 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('<div>%s</div>' % 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('<div>%s</div>' % 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 <body> and </body> and decode to unicode
snippet = body[6:-7].decode(encoding)
@@ -163,7 +160,29 @@
snippet = snippet[5:-6]
return snippet
- if hasattr(etree.HTML('<div>test</div>'), '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 "<p>text1</p><p>text2</p>"...
+ # XXX drop these two snippets action and follow the lxml behaviour
+ # XXX (tests need to be updated)
+ # if snippet.startswith('<div>') and snippet.endswith('</div>'):
+ # snippet = snippet[5:-6]
+ # if snippet.startswith('<p>') and snippet.endswith('</p>'):
+ # 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('<div>test</div>'), 'iter'): # XXX still necessary?
def safe_cut(text, length):
"""returns an html document of length <length> based on <text>,
@@ -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']
--- 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
--- 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)
--- 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:
--- 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
--- /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 <http://www.gnu.org/licenses/>.
+"""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()
--- 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')
--- 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
--- 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'<table id="header"><tr>\n')
- for colid, context in (('firstcolumn', 'header-left'),
- ('headtext', 'header-center'),
+ for colid, context in (('headtext', 'header-left'),
('header-right', 'header-right'),
):
w(u'<td id="%s">' % colid)
--- a/web/views/bookmark.py Wed Nov 03 16:39:49 2010 +0100
+++ b/web/views/bookmark.py Fri Nov 05 17:11:40 2010 +0100
@@ -24,8 +24,8 @@
from cubicweb import Unauthorized
from cubicweb.selectors import is_instance, one_line_rset
-from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
-from cubicweb.web import action, component, uicfg, formwidgets as fw
+from cubicweb.web import (action, component, uicfg, htmlwidgets,
+ formwidgets as fw)
from cubicweb.web.views import primary
_abaa = uicfg.actionbox_appearsin_addmenu
@@ -105,7 +105,7 @@
label = '<div>%s %s</div>' % (dlink, label)
self.append(label)
if self.can_edit:
- menu = BoxMenu(req._('manage bookmarks'))
+ menu = htmlwidgets.BoxMenu(req._('manage bookmarks'))
linkto = 'bookmarked_by:%s:subject' % ueid
# use a relative path so that we can move the instance without
# loosing bookmarks
--- a/web/views/boxes.py Wed Nov 03 16:39:49 2010 +0100
+++ b/web/views/boxes.py Fri Nov 05 17:11:40 2010 +0100
@@ -170,9 +170,10 @@
"""display a box containing links to all possible views"""
__regid__ = 'possible_views_box'
- visible = False
+ contextual = True
title = _('possible views')
order = 10
+ visible = False # disabled by default
def init_rendering(self):
self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
@@ -195,9 +196,10 @@
"""display a box containing links to all startup views"""
__regid__ = 'startup_views_box'
- visible = False # disabled by default
+ contextual = False
title = _('startup views')
order = 70
+ visible = False # disabled by default
def init_rendering(self):
self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw)
--- a/web/views/management.py Wed Nov 03 16:39:49 2010 +0100
+++ b/web/views/management.py Fri Nov 05 17:11:40 2010 +0100
@@ -24,7 +24,7 @@
from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView, View
-from cubicweb.uilib import html_traceback, rest_traceback
+from cubicweb.uilib import html_traceback, rest_traceback, exc_message
from cubicweb.web import formwidgets as wdgs
from cubicweb.web.formfields import guess_field
from cubicweb.web.views.schema import SecurityViewMixIn
@@ -220,15 +220,6 @@
form.render(w=w)
-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 text_error_description(ex, excinfo, req, eversion, cubes):
binfo = rest_traceback(excinfo, xml_escape(ex))
binfo += u'\n\n:URL: %s\n' % req.url()
--- a/web/views/timetable.py Wed Nov 03 16:39:49 2010 +0100
+++ b/web/views/timetable.py Fri Nov 05 17:11:40 2010 +0100
@@ -54,7 +54,7 @@
for row in xrange(self.cw_rset.rowcount):
task = self.cw_rset.get_entity(row, 0)
icalendarable = task.cw_adapt_to('ICalendarable')
- if len(self.cw_rset[row]) > 1:
+ if len(self.cw_rset[row]) > 1 and self.cw_rset.description[row][1] == 'CWUser':
user = self.cw_rset.get_entity(row, 1)
else:
user = ALL_USERS