# HG changeset patch # User David Douard # Date 1330588514 -3600 # Node ID bca3e67fdba9be0ebc32e77c10ab0127ebd8d9d6 # Parent fa167153d3844cab26aedf4fe3f6f7e41d945dfd# Parent ada08505cf127ca744612bb76bfb6d3ba60e6a64 backport 3.14.4-2 diff -r ada08505cf12 -r bca3e67fdba9 .hgignore --- a/.hgignore Thu Mar 01 08:51:32 2012 +0100 +++ b/.hgignore Thu Mar 01 08:55:14 2012 +0100 @@ -14,6 +14,7 @@ .*/data/database/.*\.sqlite .*/data/database/.*\.config .*/data/database/tmpdb.* +.*/data/ldapdb/.* ^doc/html/ ^doc/doctrees/ ^doc/book/en/devweb/js_api/ diff -r ada08505cf12 -r bca3e67fdba9 MANIFEST.in --- a/MANIFEST.in Thu Mar 01 08:51:32 2012 +0100 +++ b/MANIFEST.in Thu Mar 01 08:55:14 2012 +0100 @@ -12,7 +12,7 @@ include web/views/*.pt recursive-include web/data external_resources *.js *.css *.py *.png *.gif *.ico *.ttf recursive-include web/wdoc *.rst *.png *.xml ChangeLog* -recursive-include devtools/data *.js *.css +recursive-include devtools/data *.js *.css *.sh recursive-include i18n *.pot *.po recursive-include schemas *.py *.sql diff -r ada08505cf12 -r bca3e67fdba9 devtools/data/xvfb-run.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/devtools/data/xvfb-run.sh Thu Mar 01 08:55:14 2012 +0100 @@ -0,0 +1,191 @@ +#!/bin/sh + +# $Id: xvfb-run 2027 2004-11-16 14:54:16Z branden $ + +# This script starts an instance of Xvfb, the "fake" X server, runs a command +# with that server available, and kills the X server when done. The return +# value of the command becomes the return value of this script. +# +# If anyone is using this to build a Debian package, make sure the package +# Build-Depends on xvfb, xbase-clients, and xfonts-base. + +set -e + +PROGNAME=xvfb-run +SERVERNUM=99 +AUTHFILE= +ERRORFILE=/dev/null +STARTWAIT=3 +XVFBARGS="-screen 0 640x480x8" +LISTENTCP="-nolisten tcp" +XAUTHPROTO=. + +# Query the terminal to establish a default number of columns to use for +# displaying messages to the user. This is used only as a fallback in the event +# the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH while the +# script is running, and this cannot, only being calculated once.) +DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true +if ! expr "$DEFCOLUMNS" : "[[:digit:]]\+$" >/dev/null 2>&1; then + DEFCOLUMNS=80 +fi + +# Display a message, wrapping lines at the terminal width. +message () { + echo "$PROGNAME: $*" | fmt -t -w ${COLUMNS:-$DEFCOLUMNS} +} + +# Display an error message. +error () { + message "error: $*" >&2 +} + +# Display a usage message. +usage () { + if [ -n "$*" ]; then + message "usage error: $*" + fi + cat <>"$ERRORFILE" 2>&1 + fi + if [ -n "$XVFB_RUN_TMPDIR" ]; then + if ! rm -r "$XVFB_RUN_TMPDIR"; then + error "problem while cleaning up temporary directory" + exit 5 + fi + fi + if [ -n "$XVFBPID" ]; then + kill $XVFBPID + fi +} + +# Parse the command line. +ARGS=$(getopt --options +ae:f:hn:lp:s:w: \ + --long auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-protocol:,server-args:,wait: \ + --name "$PROGNAME" -- "$@") +GETOPT_STATUS=$? + +if [ $GETOPT_STATUS -ne 0 ]; then + error "internal error; getopt exited with status $GETOPT_STATUS" + exit 6 +fi + +eval set -- "$ARGS" + +while :; do + case "$1" in + -a|--auto-servernum) SERVERNUM=$(find_free_servernum); AUTONUM="yes" ;; + -e|--error-file) ERRORFILE="$2"; shift ;; + -f|--auth-file) AUTHFILE="$2"; shift ;; + -h|--help) SHOWHELP="yes" ;; + -n|--server-num) SERVERNUM="$2"; shift ;; + -l|--listen-tcp) LISTENTCP="" ;; + -p|--xauth-protocol) XAUTHPROTO="$2"; shift ;; + -s|--server-args) XVFBARGS="$2"; shift ;; + -w|--wait) STARTWAIT="$2"; shift ;; + --) shift; break ;; + *) error "internal error; getopt permitted \"$1\" unexpectedly" + exit 6 + ;; + esac + shift +done + +if [ "$SHOWHELP" ]; then + usage + exit 0 +fi + +if [ -z "$*" ]; then + usage "need a command to run" >&2 + exit 2 +fi + +if ! which xauth >/dev/null; then + error "xauth command not found" + exit 3 +fi + +# tidy up after ourselves +trap clean_up EXIT + +# If the user did not specify an X authorization file to use, set up a temporary +# directory to house one. +if [ -z "$AUTHFILE" ]; then + XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)" + # Create empty file to avoid xauth warning + AUTHFILE=$(tempfile -n "$XVFB_RUN_TMPDIR/Xauthority") +fi + +# Start Xvfb. +MCOOKIE=$(mcookie) +tries=10 +while [ $tries -gt 0 ]; do + tries=$(( $tries - 1 )) + XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1 +add :$SERVERNUM $XAUTHPROTO $MCOOKIE +EOF + XAUTHORITY=$AUTHFILE Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1 & + XVFBPID=$! + + sleep "$STARTWAIT" + if kill -0 $XVFBPID 2>/dev/null; then + break + elif [ -n "$AUTONUM" ]; then + # The display is in use so try another one (if '-a' was specified). + SERVERNUM=$((SERVERNUM + 1)) + SERVERNUM=$(find_free_servernum) + continue + fi + error "Xvfb failed to start" >&2 + exit 1 +done + +# Start the command and save its exit status. +set +e +DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 2>&1 +RETVAL=$? +set -e + +# Return the executed command's exit status. +exit $RETVAL + +# vim:set ai et sts=4 sw=4 tw=80: diff -r ada08505cf12 -r bca3e67fdba9 devtools/devctl.py --- a/devtools/devctl.py Thu Mar 01 08:51:32 2012 +0100 +++ b/devtools/devctl.py Thu Mar 01 08:55:14 2012 +0100 @@ -32,7 +32,7 @@ from logilab.common import STD_BLACKLIST from cubicweb.__pkginfo__ import version as cubicwebversion -from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage +from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage, ExecutionError from cubicweb.cwctl import CWCTL from cubicweb.cwconfig import CubicWebNoAppConfiguration from cubicweb.toolsutils import (SKEL_EXCLUDE, Command, copy_skeleton, @@ -303,7 +303,7 @@ from logilab.common.shellutils import globfind, find, rm from logilab.common.modutils import get_module_files from cubicweb.i18n import extract_from_tal, execute - tempdir = tempfile.mkdtemp() + tempdir = tempfile.mkdtemp(prefix='cw-') cwi18ndir = WebConfiguration.i18n_lib_dir() print '-> extract schema messages.' schemapot = osp.join(tempdir, 'schema.pot') @@ -377,7 +377,8 @@ for cube in DevConfiguration.available_cubes()] cubes = [cubepath for cubepath in cubes if osp.exists(osp.join(cubepath, 'i18n'))] - update_cubes_catalogs(cubes) + if not update_cubes_catalogs(cubes): + raise ExecutionError("update cubes i18n catalog failed") def update_cubes_catalogs(cubes): @@ -391,6 +392,7 @@ import traceback traceback.print_exc() print '-> error while updating catalogs for cube', cubedir + return False else: # instructions pour la suite if toedit: @@ -399,6 +401,7 @@ print '* ' + '\n* '.join(toedit) print ('When you are done, run "cubicweb-ctl i18ninstance ' '" to see changes in your instances.') + return True def update_cube_catalogs(cubedir): import shutil diff -r ada08505cf12 -r bca3e67fdba9 devtools/qunit.py --- a/devtools/qunit.py Thu Mar 01 08:51:32 2012 +0100 +++ b/devtools/qunit.py Thu Mar 01 08:55:14 2012 +0100 @@ -64,7 +64,7 @@ def __init__(self, url=None): self._process = None - self._tmp_dir = mkdtemp() + self._tmp_dir = mkdtemp(prefix='cwtest-ffxprof-') self._profile_data = {'uid': uuid4()} self._profile_name = self.profile_name_mask % self._profile_data fnull = open(os.devnull, 'w') @@ -72,7 +72,7 @@ stderr = TemporaryFile() self.firefox_cmd = ['firefox', '-no-remote'] if os.name == 'posix': - self.firefox_cmd = ['xvfb-run', '-a'] + self.firefox_cmd + self.firefox_cmd = [osp.join(osp.dirname(__file__), 'data', 'xvfb-run.sh'), '-a'] + self.firefox_cmd try: home = osp.expanduser('~') user = getlogin() diff -r ada08505cf12 -r bca3e67fdba9 ext/test/unittest_rest.py --- a/ext/test/unittest_rest.py Thu Mar 01 08:51:32 2012 +0100 +++ b/ext/test/unittest_rest.py Thu Mar 01 08:55:14 2012 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,9 +15,6 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""" - -""" from logilab.common.testlib import unittest_main from cubicweb.devtools.testlib import CubicWebTC @@ -60,23 +57,23 @@ def test_rql_role_with_vid(self): context = self.context() out = rest_publish(context, ':rql:`Any X WHERE X is CWUser:table`') - self.assert_(out.endswith('anon' - '\n\n

\n')) + self.assertTrue(out.endswith('anon' + '\n

\n')) def test_rql_role_with_vid_empty_rset(self): context = self.context() out = rest_publish(context, ':rql:`Any X WHERE X is CWUser, X login "nono":table`') - self.assert_(out.endswith('

No result matching query
\n

\n')) + self.assertTrue(out.endswith('

No result matching query
\n

\n')) def test_rql_role_with_unknown_vid(self): context = self.context() out = rest_publish(context, ':rql:`Any X WHERE X is CWUser:toto`') - self.assert_(out.startswith("

an error occured while interpreting this rql directive: ObjectNotFound(u'toto',)

")) + self.assertTrue(out.startswith("

an error occured while interpreting this rql directive: ObjectNotFound(u'toto',)

")) def test_rql_role_without_vid(self): context = self.context() out = rest_publish(context, ':rql:`Any X WHERE X is CWUser`') - self.assertEqual(out, u'

cwuser_plural

\n') + self.assertEqual(out, u'

CWUser_plural

\n') if __name__ == '__main__': unittest_main() diff -r ada08505cf12 -r bca3e67fdba9 i18n/de.po --- a/i18n/de.po Thu Mar 01 08:51:32 2012 +0100 +++ b/i18n/de.po Thu Mar 01 08:55:14 2012 +0100 @@ -962,6 +962,9 @@ msgid "a float is expected" msgstr "Eine Dezimalzahl (float) wird erwartet." +msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" +msgstr "" + msgid "" "a simple cache entity characterized by a name and a validity date. The " "target application is responsible for updating timestamp when necessary to " diff -r ada08505cf12 -r bca3e67fdba9 i18n/en.po --- a/i18n/en.po Thu Mar 01 08:51:32 2012 +0100 +++ b/i18n/en.po Thu Mar 01 08:55:14 2012 +0100 @@ -922,6 +922,9 @@ msgid "a float is expected" msgstr "" +msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" +msgstr "" + msgid "" "a simple cache entity characterized by a name and a validity date. The " "target application is responsible for updating timestamp when necessary to " diff -r ada08505cf12 -r bca3e67fdba9 i18n/es.po --- a/i18n/es.po Thu Mar 01 08:51:32 2012 +0100 +++ b/i18n/es.po Thu Mar 01 08:55:14 2012 +0100 @@ -967,6 +967,9 @@ msgid "a float is expected" msgstr "un nĂºmero flotante es requerido" +msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" +msgstr "" + msgid "" "a simple cache entity characterized by a name and a validity date. The " "target application is responsible for updating timestamp when necessary to " diff -r ada08505cf12 -r bca3e67fdba9 i18n/fr.po --- a/i18n/fr.po Thu Mar 01 08:51:32 2012 +0100 +++ b/i18n/fr.po Thu Mar 01 08:55:14 2012 +0100 @@ -967,6 +967,9 @@ msgid "a float is expected" msgstr "un nombre flottant est attendu" +msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" +msgstr "un nombre (en seconde) ou 20s, 10min, 24h ou 4d sont attendus" + msgid "" "a simple cache entity characterized by a name and a validity date. The " "target application is responsible for updating timestamp when necessary to " diff -r ada08505cf12 -r bca3e67fdba9 misc/migration/3.12.0_Any.py --- a/misc/migration/3.12.0_Any.py Thu Mar 01 08:51:32 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -if schema['TZDatetime'].eid is None: - add_entity_type('TZDatetime') -if schema['TZTime'].eid is None: - add_entity_type('TZTime') diff -r ada08505cf12 -r bca3e67fdba9 misc/migration/3.14.4_Any.py --- a/misc/migration/3.14.4_Any.py Thu Mar 01 08:51:32 2012 +0100 +++ b/misc/migration/3.14.4_Any.py Thu Mar 01 08:55:14 2012 +0100 @@ -4,6 +4,7 @@ rdefdef = schema['CWSource'].rdef('name') attrtype = y2sql.type_from_constraints(dbhelper, rdefdef.object, rdefdef.constraints).split()[0] -sql(dbhelper.sql_change_col_type('entities', 'asource', attrtype, False)) -sql(dbhelper.sql_change_col_type('entities', 'source', attrtype, False)) -sql(dbhelper.sql_change_col_type('deleted_entities', 'source', attrtype, False)) +cursor = session.cnxset['system'] +dbhelper.change_col_type(cursor, 'entities', 'asource', attrtype, False) +dbhelper.change_col_type(cursor, 'entities', 'source', attrtype, False) +dbhelper.change_col_type(cursor, 'deleted_entities', 'source', attrtype, False) diff -r ada08505cf12 -r bca3e67fdba9 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Thu Mar 01 08:51:32 2012 +0100 +++ b/misc/migration/bootstrapmigration_repository.py Thu Mar 01 08:55:14 2012 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -40,6 +40,13 @@ sql('UPDATE entities SET asource=cw_name ' 'FROM cw_CWSource, cw_source_relation ' 'WHERE entities.eid=cw_source_relation.eid_from AND cw_source_relation.eid_to=cw_CWSource.cw_eid') + commit() + +if schema['TZDatetime'].eid is None: + add_entity_type('TZDatetime', auto=False) +if schema['TZTime'].eid is None: + add_entity_type('TZTime', auto=False) + if applcubicwebversion <= (3, 14, 0) and cubicwebversion >= (3, 14, 0): if 'require_permission' in schema and not 'localperms'in repo.config.cubes(): diff -r ada08505cf12 -r bca3e67fdba9 rqlrewrite.py --- a/rqlrewrite.py Thu Mar 01 08:51:32 2012 +0100 +++ b/rqlrewrite.py Thu Mar 01 08:55:14 2012 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -228,19 +228,19 @@ if not r in sti['rhsrelations']) else: vi['rhs_rels'] = vi['lhs_rels'] = {} - parent = None + previous = None inserted = False for rqlexpr in rqlexprs: self.current_expr = rqlexpr if varexistsmap is None: try: - new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, parent) + new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, previous) except Unsupported: continue inserted = True if new is not None and self._insert_scope is None: self.exists_snippet[rqlexpr] = new - parent = parent or new + previous = previous or new else: # called to reintroduce snippet due to ambiguity creation, # so skip snippets which are not introducing this ambiguity @@ -251,16 +251,21 @@ # no rql expression found matching rql solutions. User has no access right raise Unauthorized() # XXX may also be because of bad constraints in schema definition - def insert_snippet(self, varmap, snippetrqlst, parent=None): + def insert_snippet(self, varmap, snippetrqlst, previous=None): new = snippetrqlst.where.accept(self) existing = self.existingvars self.existingvars = None try: - return self._insert_snippet(varmap, parent, new) + return self._insert_snippet(varmap, previous, new) finally: self.existingvars = existing - def _insert_snippet(self, varmap, parent, new): + def _insert_snippet(self, varmap, previous, new): + """insert `new` snippet into the syntax tree, which have been rewritten + using `varmap`. In cases where an action is protected by several rql + expresssion, `previous` will be the first rql expression which has been + inserted, and so should be ORed with the following expressions. + """ if new is not None: if self._insert_scope is None: insert_scope = None @@ -274,28 +279,28 @@ insert_scope = self._insert_scope if self._insert_scope is None and any(vi.get('stinfo', {}).get('optrelations') for vi in self.varinfos): - assert parent is None - self._insert_scope = self.snippet_subquery(varmap, new) + assert previous is None + self._insert_scope, new = self.snippet_subquery(varmap, new) self.insert_pending() #self._insert_scope = None - return + return new if not isinstance(new, (n.Exists, n.Not)): new = n.Exists(new) - if parent is None: + if previous is None: insert_scope.add_restriction(new) else: - grandpa = parent.parent - or_ = n.Or(parent, new) - grandpa.replace(parent, or_) + grandpa = previous.parent + or_ = n.Or(previous, new) + grandpa.replace(previous, or_) if not self.removing_ambiguity: try: self.compute_solutions() except Unsupported: # some solutions have been lost, can't apply this rql expr - if parent is None: + if previous is None: self.current_statement().remove_node(new, undefine=True) else: - grandpa.replace(or_, parent) + grandpa.replace(or_, previous) self._cleanup_inserted(new) raise else: @@ -419,7 +424,7 @@ # some solutions have been lost, can't apply this rql expr self.select.remove_subquery(self.select.with_[-1]) raise - return subselect + return subselect, snippetrqlst def remove_ambiguities(self, snippets, newsolutions): # the snippet has introduced some ambiguities, we have to resolve them diff -r ada08505cf12 -r bca3e67fdba9 schema.py --- a/schema.py Thu Mar 01 08:51:32 2012 +0100 +++ b/schema.py Thu Mar 01 08:55:14 2012 +0100 @@ -27,7 +27,7 @@ from logilab.common.decorators import cached, clear_cache, monkeypatch from logilab.common.logging_ext import set_log_methods -from logilab.common.deprecation import deprecated, class_moved +from logilab.common.deprecation import deprecated, class_moved, moved from logilab.common.textutils import splitstrip from logilab.common.graph import get_cycles from logilab.common.compat import any @@ -1250,7 +1250,10 @@ from yams.buildobjs import RichString from yams.constraints import StaticVocabularyConstraint -RichString = class_moved(RichString) +try: # for yams < 0.35 + RichString = class_moved(RichString) +except TypeError: + RichString = moved('yams.buildobjs', 'RichString') StaticVocabularyConstraint = class_moved(StaticVocabularyConstraint) FormatConstraint = class_moved(FormatConstraint) diff -r ada08505cf12 -r bca3e67fdba9 server/migractions.py --- a/server/migractions.py Thu Mar 01 08:51:32 2012 +0100 +++ b/server/migractions.py Thu Mar 01 08:55:14 2012 +0100 @@ -1112,7 +1112,7 @@ schemaobj = getattr(rdef, attr) if getattr(schemaobj, 'eid', None) is None: schemaobj.eid = self.repo.schema[schemaobj].eid - assert schemaobj.eid is not None + assert schemaobj.eid is not None, schemaobj return rdef def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True): diff -r ada08505cf12 -r bca3e67fdba9 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Thu Mar 01 08:51:32 2012 +0100 +++ b/server/sources/rql2sql.py Thu Mar 01 08:55:14 2012 +0100 @@ -1454,6 +1454,8 @@ lhs, rhs = mexpr.get_parts() # check for string concatenation operator = mexpr.operator + if operator == '%': + operator = '%%' try: if mexpr.operator == '+' and mexpr.get_type(self._state.solution, self._args) == 'String': return '(%s)' % self.dbhelper.sql_concat_string(lhs.accept(self), diff -r ada08505cf12 -r bca3e67fdba9 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Thu Mar 01 08:51:32 2012 +0100 +++ b/server/test/unittest_rql2sql.py Thu Mar 01 08:55:14 2012 +0100 @@ -1744,6 +1744,9 @@ GROUP BY CAST(EXTRACT(YEAR from _X.cw_modification_date) AS INTEGER),CAST(EXTRACT(MONTH from _X.cw_modification_date) AS INTEGER) ORDER BY 1'''), + def test_modulo(self): + self._check('Any 5 % 2', '''SELECT (5 % 2)''') + class SqlServer2005SQLGeneratorTC(PostgresSQLGeneratorTC): backend = 'sqlserver2005' diff -r ada08505cf12 -r bca3e67fdba9 test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Thu Mar 01 08:51:32 2012 +0100 +++ b/test/unittest_rqlrewrite.py Thu Mar 01 08:55:14 2012 +0100 @@ -183,9 +183,9 @@ self.assertEqual(rqlst.as_string(), "Any A,C,T WHERE A documented_by C?, A is Affaire " "WITH C,T BEING (Any C,T WHERE C title T, " - "EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F), " - "D eid %(A)s, C is Card, " - "EXISTS(C in_state E, E name 'public'))") + "(EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F)) " + "OR (EXISTS(C in_state E, E name 'public')), " + "D eid %(A)s, C is Card)") def test_optional_var_4(self): constraint1 = 'A created_by U, X documented_by A' @@ -199,8 +199,8 @@ u'Any X,LA,Y WHERE LA? documented_by X, LA concerne Y, B eid %(C)s, ' 'EXISTS(X created_by B), EXISTS(Y created_by B), ' 'X is Card, Y is IN(Division, Note, Societe) ' - 'WITH LA BEING (Any LA WHERE EXISTS(A created_by B, LA documented_by A), ' - 'B eid %(D)s, LA is Affaire, EXISTS(E created_by B, LA concerne E))') + 'WITH LA BEING (Any LA WHERE (EXISTS(A created_by B, LA documented_by A)) OR (EXISTS(E created_by B, LA concerne E)), ' + 'B eid %(D)s, LA is Affaire)') def test_optional_var_inlined(self): c1 = ('X require_permission P') @@ -431,6 +431,18 @@ self.assertEqual(rqlst.as_string(), u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') + def test_rqlexpr_multiexpr_outerjoin(self): + c1 = RRQLExpression('X owned_by Z, Z login "hop"', 'X') + c2 = RRQLExpression('X owned_by Z, Z login "hip"', 'X') + c3 = RRQLExpression('X owned_by Z, Z login "momo"', 'X') + rqlst = rqlhelper.parse('Any A WHERE A documented_by C?', annotate=False) + rewrite(rqlst, {('C', 'X'): (c1, c2, c3)}, {}, 'X') + self.assertEqual(rqlst.as_string(), + u'Any A WHERE A documented_by C?, A is Affaire ' + 'WITH C BEING (Any C WHERE ((EXISTS(C owned_by B, B login "hop")) ' + 'OR (EXISTS(C owned_by D, D login "momo"))) ' + 'OR (EXISTS(C owned_by A, A login "hip")), C is Card)') + if __name__ == '__main__': unittest_main() diff -r ada08505cf12 -r bca3e67fdba9 web/controller.py --- a/web/controller.py Thu Mar 01 08:51:32 2012 +0100 +++ b/web/controller.py Thu Mar 01 08:55:14 2012 +0100 @@ -207,10 +207,12 @@ if '__redirectpath' in self._cw.form: # if redirect path was explicitly specified in the form, use it path = self._cw.form['__redirectpath'] - url = self._cw.build_url(path, **newparams) + url = self._cw.build_url(path) url = append_url_params(url, self._cw.form.get('__redirectparams')) else: url = self._cw.last_visited_page() + # The newparams must update the params in all cases + url = self._cw.rebuild_url(url, **newparams) raise Redirect(url) diff -r ada08505cf12 -r bca3e67fdba9 web/formfields.py --- a/web/formfields.py Thu Mar 01 08:51:32 2012 +0100 +++ b/web/formfields.py Thu Mar 01 08:55:14 2012 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -43,6 +43,7 @@ .. autoclass:: cubicweb.web.formfields.DateField() .. autoclass:: cubicweb.web.formfields.DateTimeField() .. autoclass:: cubicweb.web.formfields.TimeField() +.. autoclass:: cubicweb.web.formfields.TimeIntervalField() Compound fields '''''''''''''''' @@ -63,11 +64,13 @@ __docformat__ = "restructuredtext en" from warnings import warn -from datetime import datetime +from datetime import datetime, timedelta from logilab.mtconverter import xml_escape from logilab.common import nullobject from logilab.common.date import ustrftime +from logilab.common.configuration import format_time +from logilab.common.textutils import apply_units, TIME_UNITS from yams.schema import KNOWN_METAATTRIBUTES, role_name from yams.constraints import (SizeConstraint, StaticVocabularyConstraint, @@ -929,6 +932,38 @@ return None +class TimeIntervalField(StringField): + """Use this field to edit time interval (`Interval` yams type). + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.TextInput`. + """ + widget = fw.TextInput + + def format_single_value(self, req, value): + if value: + value = format_time(value.days * 24 * 3600 + value.seconds) + return unicode(value) + return u'' + + def example_format(self, req): + """return a sample string describing what can be given as input for this + field + """ + return u'20s, 10min, 24h, 4d' + + def _ensure_correctly_typed(self, form, value): + if isinstance(value, basestring): + value = value.strip() + if not value: + return None + try: + value = apply_units(value, TIME_UNITS) + except ValueError: + raise ProcessFormError(form._cw._('a number (in seconds) or 20s, 10min, 24h or 4d are expected')) + return timedelta(0, value) + + class DateField(StringField): """Use this field to edit date (`Date` yams type). @@ -1201,5 +1236,5 @@ 'TZDatetime': DateTimeField, 'Time': TimeField, 'TZTime': TimeField, - # XXX implement 'Interval': TimeIntervalField, + 'Interval': TimeIntervalField, } diff -r ada08505cf12 -r bca3e67fdba9 web/test/unittest_views_basetemplates.py --- a/web/test/unittest_views_basetemplates.py Thu Mar 01 08:51:32 2012 +0100 +++ b/web/test/unittest_views_basetemplates.py Thu Mar 01 08:55:14 2012 +0100 @@ -35,6 +35,13 @@ self.set_option('allow-email-login', 'no') self.assertEqual(self._login_labels(), ['login', 'password']) + +class MainNoTopTemplateTC(CubicWebTC): + + def test_valid_xhtml(self): + self.view('index', template='main-no-top') + + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r ada08505cf12 -r bca3e67fdba9 web/views/basetemplates.py --- a/web/views/basetemplates.py Thu Mar 01 08:51:32 2012 +0100 +++ b/web/views/basetemplates.py Thu Mar 01 08:55:14 2012 +0100 @@ -256,10 +256,10 @@ whead(u'\n'.join(additional_headers) + u'\n') self.wview('htmlheader', rset=self.cw_rset) w = self.w - w(u'%s\n' % xml_escape(page_title)) + whead(u'%s\n' % xml_escape(page_title)) w(u'\n') w(u'
') - w(u'\n') + w(u'
\n') w(u'') - w(u'
') - w(u'
\n') - vtitle = self._cw.form.get('vtitle') - if vtitle: - w(u'
%s
' % xml_escape(vtitle)) + w(u'
') def topleft_header(self): logo = self._cw.vreg['components'].select_or_none('logo', self._cw,