# HG changeset patch # User Sylvain Thénault # Date 1254134221 -7200 # Node ID 06bced8edddff8be0e4ac5a9a15d97f16d6697d6 # Parent 48b26bfd6f022cce622ae05362603def640bbf59# Parent 9638213410e9e643d360568b3318f66e09d74fb9 backport stable diff -r 48b26bfd6f02 -r 06bced8edddf cwctl.py --- a/cwctl.py Sat Sep 26 11:44:35 2009 +0200 +++ b/cwctl.py Mon Sep 28 12:37:01 2009 +0200 @@ -172,8 +172,7 @@ """run the command with its specific arguments""" if args: raise BadCommandUsage('Too much arguments') - print 'CubicWeb version:', cwcfg.cubicweb_version() - print 'Detected mode:', cwcfg.mode + print 'CubicWeb %s (%s mode)' % (cwcfg.cubicweb_version(), cwcfg.mode) print print 'Available configurations:' for config in CONFIGURATIONS: diff -r 48b26bfd6f02 -r 06bced8edddf devtools/dataimport.py --- a/devtools/dataimport.py Sat Sep 26 11:44:35 2009 +0200 +++ b/devtools/dataimport.py Mon Sep 28 12:37:01 2009 +0200 @@ -151,7 +151,7 @@ return len(self.items) - 1 def add(self, type, item): - assert isinstance(item, dict), item + assert isinstance(item, dict), 'item is not a dict but a %s' % type(item) eid = item['eid'] = self._put(type, item) self.eids[eid] = item self.types.setdefault(type, []).append(eid) @@ -172,7 +172,7 @@ def get_one(self, name, key): eids = self.indexes[name].get(key, []) - assert len(eids) == 1 + assert len(eids) == 1, 'expected a single one got %i' % len(eids) return eids[0] def find(self, type, key, value): diff -r 48b26bfd6f02 -r 06bced8edddf doc/book/en/admin/setup.rst --- a/doc/book/en/admin/setup.rst Sat Sep 26 11:44:35 2009 +0200 +++ b/doc/book/en/admin/setup.rst Mon Sep 28 12:37:01 2009 +0200 @@ -63,19 +63,26 @@ .. _`ftp site`: http://ftp.logilab.org/pub/cubicweb/ -or keep up to date with on-going development by using Mercurial and its forest -extension:: +Make sure you have installed the dependencies (see appendixes for the list). + +Install from version control system +``````````````````````````````````` + +You can keep up to date with on-going development by using Mercurial and its +forest extension:: hg fclone http://www.logilab.org/hg/forests/cubicweb See :ref:`MercurialPresentation` for more details about Mercurial. + When cloning a repository, you might be set in a development branch (the 'default' branch). You should check that the branches of the repositories are set to 'stable' (using `hg up stable` for each one) if you do not intend to develop the framework itself. -In both cases, make sure you have installed the dependencies (see appendixes for -the list). +Do not forget to update the forest itself (using `cd path/to/forest ; hg up`). + +Make sure you have installed the dependencies (see appendixes for the list). Windows installation ```````````````````` diff -r 48b26bfd6f02 -r 06bced8edddf doc/book/en/annexes/depends.rst --- a/doc/book/en/annexes/depends.rst Sat Sep 26 11:44:35 2009 +0200 +++ b/doc/book/en/annexes/depends.rst Mon Sep 28 12:37:01 2009 +0200 @@ -47,5 +47,17 @@ * fyzz - http://www.logilab.org/project/fyzz - http://pypi.python.org/pypi/fyzz - included in the forest +* psycopg2 - http://initd.org/projects/psycopg2 - http://pypi.python.org/pypi/psycopg2 + +* docsutils - http://docutils.sourceforge.net/ - http://pypi.python.org/pypi/docutils + +For the google-appengine extension to be available, you also need: + +* dateutil - http://labix.org/python-dateutil - + http://pypi.python.org/pypi/python-dateutil/ + +* vobject - http://vobject.skyhouseconsulting.com/ - + http://pypi.python.org/pypi/vobject + Any help with the packaging of CubicWeb for more than Debian/Ubuntu (including eggs, buildouts, etc) will be greatly appreciated. diff -r 48b26bfd6f02 -r 06bced8edddf doc/book/en/development/devcore/cwconfig.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/development/devcore/cwconfig.rst Mon Sep 28 12:37:01 2009 +0200 @@ -0,0 +1,5 @@ +:mod:`Configuration ` +---------------------------------------- + +.. automodule:: cubicweb.cwconfig + :members: diff -r 48b26bfd6f02 -r 06bced8edddf doc/book/en/development/devcore/index.rst --- a/doc/book/en/development/devcore/index.rst Sat Sep 26 11:44:35 2009 +0200 +++ b/doc/book/en/development/devcore/index.rst Mon Sep 28 12:37:01 2009 +0200 @@ -8,10 +8,5 @@ appobject.rst selectors.rst dbapi.rst - + cwconfig.rst -:mod:`Configuration ` ----------------------------------------- - -.. automodule:: cubicweb.cwconfig - :members: diff -r 48b26bfd6f02 -r 06bced8edddf doc/book/en/development/devweb/js.rst --- a/doc/book/en/development/devweb/js.rst Sat Sep 26 11:44:35 2009 +0200 +++ b/doc/book/en/development/devweb/js.rst Mon Sep 28 12:37:01 2009 +0200 @@ -23,7 +23,7 @@ XXX external_resources variable (which needs love) -CubicWeb javascrip api +CubicWeb javascript api ~~~~~~~~~~~~~~~~~~~~~~ Javascript resources are typically loaded on demand, from views. The diff -r 48b26bfd6f02 -r 06bced8edddf doc/book/en/development/devweb/property.rst --- a/doc/book/en/development/devweb/property.rst Sat Sep 26 11:44:35 2009 +0200 +++ b/doc/book/en/development/devweb/property.rst Mon Sep 28 12:37:01 2009 +0200 @@ -1,4 +1,4 @@ -The property mecanims +The property mecanism --------------------- XXX CWProperty and co @@ -9,4 +9,4 @@ Registering and using your own property ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -XXX feed me \ No newline at end of file +XXX feed me diff -r 48b26bfd6f02 -r 06bced8edddf etwist/server.py --- a/etwist/server.py Sat Sep 26 11:44:35 2009 +0200 +++ b/etwist/server.py Mon Sep 28 12:37:01 2009 +0200 @@ -10,10 +10,10 @@ import sys import os import select +import hotshot from time import mktime from datetime import date, timedelta from urlparse import urlsplit, urlunsplit -import hotshot from twisted.application import strports from twisted.internet import reactor, task, threads @@ -55,20 +55,9 @@ def start_task(interval, func): lc = task.LoopingCall(func) - lc.start(interval) - -def start_looping_tasks(repo): - for interval, func, args in repo._looping_tasks: - repo.info('starting twisted task %s with interval %.2fs', - func.__name__, interval) - def catch_error_func(repo=repo, func=func, args=args): - try: - func(*args) - except: - repo.exception('error in looping task') - start_task(interval, catch_error_func) - # ensure no tasks will be further added - repo._looping_tasks = () + # wait until interval has expired to actually start the task, else we have + # to wait all task to be finished for the server to be actually started + lc.start(interval, now=False) def host_prefixed_baseurl(baseurl, host): scheme, netloc, url, query, fragment = urlsplit(baseurl) @@ -122,14 +111,14 @@ reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown_event) # monkey patch start_looping_task to get proper reactor integration - self.appli.repo.__class__.start_looping_tasks = start_looping_tasks + #self.appli.repo.__class__.start_looping_tasks = start_looping_tasks if config.pyro_enabled(): # if pyro is enabled, we have to register to the pyro name # server, create a pyro daemon, and create a task to handle pyro # requests self.pyro_daemon = self.appli.repo.pyro_register() self.pyro_listen_timeout = 0.02 - start_task(1, self.pyro_loop_event) + self.appli.repo.looping_task(1, self.pyro_loop_event) self.appli.repo.start_looping_tasks() self.set_url_rewriter() CW_EVENT_MANAGER.bind('after-registry-reload', self.set_url_rewriter) diff -r 48b26bfd6f02 -r 06bced8edddf misc/migration/3.5.3_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.5.3_Any.py Mon Sep 28 12:37:01 2009 +0200 @@ -0,0 +1,2 @@ +sync_schema_props_perms('state_of') +sync_schema_props_perms('transition_of') diff -r 48b26bfd6f02 -r 06bced8edddf rqlrewrite.py --- a/rqlrewrite.py Sat Sep 26 11:44:35 2009 +0200 +++ b/rqlrewrite.py Mon Sep 28 12:37:01 2009 +0200 @@ -14,8 +14,74 @@ from logilab.common.compat import any -from cubicweb import Unauthorized, server, typed_eid -from cubicweb.server.ssplanner import add_types_restriction +from cubicweb import Unauthorized, typed_eid + + +def add_types_restriction(schema, rqlst, newroot=None, solutions=None): + if newroot is None: + assert solutions is None + if hasattr(rqlst, '_types_restr_added'): + return + solutions = rqlst.solutions + newroot = rqlst + rqlst._types_restr_added = True + else: + assert solutions is not None + rqlst = rqlst.stmt + eschema = schema.eschema + allpossibletypes = {} + for solution in solutions: + for varname, etype in solution.iteritems(): + if not varname in newroot.defined_vars or eschema(etype).is_final(): + continue + allpossibletypes.setdefault(varname, set()).add(etype) + for varname in sorted(allpossibletypes): + try: + var = newroot.defined_vars[varname] + except KeyError: + continue + stinfo = var.stinfo + if stinfo.get('uidrels'): + continue # eid specified, no need for additional type specification + try: + typerels = rqlst.defined_vars[varname].stinfo.get('typerels') + except KeyError: + assert varname in rqlst.aliases + continue + if newroot is rqlst and typerels: + mytyperel = iter(typerels).next() + else: + for vref in newroot.defined_vars[varname].references(): + rel = vref.relation() + if rel and rel.is_types_restriction(): + mytyperel = rel + break + else: + mytyperel = None + possibletypes = allpossibletypes[varname] + if mytyperel is not None: + # variable as already some types restriction. new possible types + # can only be a subset of existing ones, so only remove no more + # possible types + for cst in mytyperel.get_nodes(n.Constant): + if not cst.value in possibletypes: + cst.parent.remove(cst) + try: + stinfo['possibletypes'].remove(cst.value) + except KeyError: + # restriction on a type not used by this query, may + # occurs with X is IN(...) + pass + else: + # we have to add types restriction + if stinfo.get('scope') is not None: + rel = var.scope.add_type_restriction(var, possibletypes) + else: + # tree is not annotated yet, no scope set so add the restriction + # to the root + rel = newroot.add_type_restriction(var, possibletypes) + stinfo['typerels'] = frozenset((rel,)) + stinfo['possibletypes'] = possibletypes def remove_solutions(origsolutions, solutions, defined): @@ -73,8 +139,6 @@ snippets: (varmap, list of rql expression) with varmap a *tuple* (select var, snippet var) """ - if server.DEBUG: - print '---- rewrite', select, snippets, solutions self.select = self.insert_scope = select self.solutions = solutions self.kwargs = kwargs @@ -102,8 +166,6 @@ newsolutions = self.remove_ambiguities(snippets, newsolutions) select.solutions = newsolutions add_types_restriction(self.schema, select) - if server.DEBUG: - print '---- rewriten', select def insert_snippets(self, snippets, varexistsmap=None): self.rewritten = {} @@ -134,8 +196,6 @@ try: new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, parent) except Unsupported: - import traceback - traceback.print_exc() continue inserted = True if new is not None: @@ -322,28 +382,38 @@ # no more references, undefine the variable del self.select.defined_vars[vref.name] - def _may_be_shared(self, relation, target, searchedvarname): - """return True if the snippet relation can be skipped to use a relation - from the original query + def _may_be_shared_with(self, sniprel, target, searchedvarname): + """if the snippet relation can be skipped to use a relation from the + original query, return that relation node """ - # if cardinality is in '?1', we can ignore the relation and use variable - # from the original query - rschema = self.schema.rschema(relation.r_type) - if target == 'object': - cardindex = 0 - ttypes_func = rschema.objects - rprop = rschema.rproperty - else: # target == 'subject': - cardindex = 1 - ttypes_func = rschema.subjects - rprop = lambda x, y, z: rschema.rproperty(y, x, z) + rschema = self.schema.rschema(sniprel.r_type) + try: + if target == 'object': + orel = self.varinfo['lhs_rels'][sniprel.r_type] + cardindex = 0 + ttypes_func = rschema.objects + rprop = rschema.rproperty + else: # target == 'subject': + orel = self.varinfo['rhs_rels'][sniprel.r_type] + cardindex = 1 + ttypes_func = rschema.subjects + rprop = lambda x, y, z: rschema.rproperty(y, x, z) + except KeyError, ex: + # may be raised by self.varinfo['xhs_rels'][sniprel.r_type] + return None + # can't share neged relation or relations with different outer join + if (orel.neged(strict=True) or sniprel.neged(strict=True) + or (orel.optional and orel.optional != sniprel.optional)): + return None + # if cardinality is in '?1', we can ignore the snippet relation and use + # variable from the original query for etype in self.varinfo['stinfo']['possibletypes']: for ttype in ttypes_func(etype): if rprop(etype, ttype, 'cardinality')[cardindex] in '+*': - return False - return True + return None + return orel - def _use_outer_term(self, snippet_varname, term): + def _use_orig_term(self, snippet_varname, term): key = (self.current_expr, self.varmap, snippet_varname) if key in self.rewritten: insertedvar = self.select.defined_vars.pop(self.rewritten[key]) @@ -417,27 +487,17 @@ key = (self.current_expr, self.varmap, rhs.name) self.pending_keys.append( (key, action) ) return - if lhs.name in self.revvarmap: - # on lhs - # see if we can reuse this relation - rels = self.varinfo['lhs_rels'] - if (node.r_type in rels and isinstance(rhs, n.VariableRef) - and rhs.name != 'U' and not rels[node.r_type].neged(strict=True) - and self._may_be_shared(node, 'object', lhs.name)): - # ok, can share variable - term = rels[node.r_type].children[1].children[0] - self._use_outer_term(rhs.name, term) - return - elif isinstance(rhs, n.VariableRef) and rhs.name in self.revvarmap and lhs.name != 'U': - # on rhs - # see if we can reuse this relation - rels = self.varinfo['rhs_rels'] - if (node.r_type in rels and not rels[node.r_type].neged(strict=True) - and self._may_be_shared(node, 'subject', rhs.name)): - # ok, can share variable - term = rels[node.r_type].children[0] - self._use_outer_term(lhs.name, term) - return + if isinstance(rhs, n.VariableRef): + if lhs.name in self.revvarmap and rhs.name != 'U': + orel = self._may_be_shared_with(node, 'object', lhs.name) + if orel is not None: + self._use_orig_term(rhs.name, orel.children[1].children[0]) + return + elif rhs.name in self.revvarmap and lhs.name != 'U': + orel = self._may_be_shared_with(node, 'subject', rhs.name) + if orel is not None: + self._use_orig_term(lhs.name, orel.children[0]) + return rel = n.Relation(node.r_type, node.optional) for c in node.children: rel.append(c.accept(self)) diff -r 48b26bfd6f02 -r 06bced8edddf schemas/workflow.py --- a/schemas/workflow.py Sat Sep 26 11:44:35 2009 +0200 +++ b/schemas/workflow.py Mon Sep 28 12:37:01 2009 +0200 @@ -57,7 +57,7 @@ allowed_transition = SubjectRelation('BaseTransition', cardinality='**', constraints=[RQLConstraint('S state_of WF, O transition_of WF')], description=_('allowed transitions from this state')) - state_of = SubjectRelation('Workflow', cardinality='1*', + state_of = SubjectRelation('Workflow', cardinality='1*', composite='object', description=_('workflow to which this state belongs'), constraints=[RQLUniqueConstraint('S name N, Y state_of O, Y name N')]) @@ -80,7 +80,7 @@ require_group = SubjectRelation('CWGroup', cardinality='**', description=_('group in which a user should be to be ' 'allowed to pass this transition')) - transition_of = SubjectRelation('Workflow', cardinality='1*', + transition_of = SubjectRelation('Workflow', cardinality='1*', composite='object', description=_('workflow to which this transition belongs'), constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N')]) @@ -223,7 +223,7 @@ } inlined = True - cardinality='1*' + cardinality = '1*' composite = 'object' fulltext_container = composite subject = 'TrInfo' diff -r 48b26bfd6f02 -r 06bced8edddf server/sources/native.py --- a/server/sources/native.py Sat Sep 26 11:44:35 2009 +0200 +++ b/server/sources/native.py Mon Sep 28 12:37:01 2009 +0200 @@ -554,7 +554,7 @@ """ try: self.indexer.cursor_unindex_object(eid, session.pool['system']) - except: + except Exception: # let KeyboardInterrupt / SystemExit propagate if self.indexer is not None: self.exception('error while unindexing %s', eid) @@ -565,7 +565,7 @@ try: self.indexer.cursor_reindex_object(entity.eid, entity, session.pool['system']) - except: + except Exception: # let KeyboardInterrupt / SystemExit propagate if self.indexer is not None: self.exception('error while reindexing %s', entity) # update entities.mtime diff -r 48b26bfd6f02 -r 06bced8edddf server/ssplanner.py --- a/server/ssplanner.py Sat Sep 26 11:44:35 2009 +0200 +++ b/server/ssplanner.py Mon Sep 28 12:37:01 2009 +0200 @@ -14,72 +14,8 @@ from cubicweb import QueryError, typed_eid from cubicweb.schema import VIRTUAL_RTYPES +from cubicweb.rqlrewrite import add_types_restriction -def add_types_restriction(schema, rqlst, newroot=None, solutions=None): - if newroot is None: - assert solutions is None - if hasattr(rqlst, '_types_restr_added'): - return - solutions = rqlst.solutions - newroot = rqlst - rqlst._types_restr_added = True - else: - assert solutions is not None - rqlst = rqlst.stmt - eschema = schema.eschema - allpossibletypes = {} - for solution in solutions: - for varname, etype in solution.iteritems(): - if not varname in newroot.defined_vars or eschema(etype).is_final(): - continue - allpossibletypes.setdefault(varname, set()).add(etype) - for varname in sorted(allpossibletypes): - try: - var = newroot.defined_vars[varname] - except KeyError: - continue - stinfo = var.stinfo - if stinfo.get('uidrels'): - continue # eid specified, no need for additional type specification - try: - typerels = rqlst.defined_vars[varname].stinfo.get('typerels') - except KeyError: - assert varname in rqlst.aliases - continue - if newroot is rqlst and typerels: - mytyperel = iter(typerels).next() - else: - for vref in newroot.defined_vars[varname].references(): - rel = vref.relation() - if rel and rel.is_types_restriction(): - mytyperel = rel - break - else: - mytyperel = None - possibletypes = allpossibletypes[varname] - if mytyperel is not None: - # variable as already some types restriction. new possible types - # can only be a subset of existing ones, so only remove no more - # possible types - for cst in mytyperel.get_nodes(Constant): - if not cst.value in possibletypes: - cst.parent.remove(cst) - try: - stinfo['possibletypes'].remove(cst.value) - except KeyError: - # restriction on a type not used by this query, may - # occurs with X is IN(...) - pass - else: - # we have to add types restriction - if stinfo.get('scope') is not None: - rel = var.scope.add_type_restriction(var, possibletypes) - else: - # tree is not annotated yet, no scope set so add the restriction - # to the root - rel = newroot.add_type_restriction(var, possibletypes) - stinfo['typerels'] = frozenset((rel,)) - stinfo['possibletypes'] = possibletypes class SSPlanner(object): """SingleSourcePlanner: build execution plan for rql queries diff -r 48b26bfd6f02 -r 06bced8edddf skeleton/__pkginfo__.py.tmpl --- a/skeleton/__pkginfo__.py.tmpl Sat Sep 26 11:44:35 2009 +0200 +++ b/skeleton/__pkginfo__.py.tmpl Mon Sep 28 12:37:01 2009 +0200 @@ -43,8 +43,8 @@ # Note: here, you'll need to add subdirectories if you want # them to be included in the debian package - -cube_eid = None # <=== FIXME if you need direct bug-subscription +__depends_cubes__ = {} +__depends__ = {'cubicweb': '>= 3.5.0'} __use__ = (%(dependancies)s) __recommend__ = () diff -r 48b26bfd6f02 -r 06bced8edddf skeleton/migration/precreate.py --- a/skeleton/migration/precreate.py Sat Sep 26 11:44:35 2009 +0200 +++ b/skeleton/migration/precreate.py Mon Sep 28 12:37:01 2009 +0200 @@ -7,4 +7,4 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ # You could create your own groups here, like in : -# add_entity('CWGroup', name=u'mygroup') +# create_entity('CWGroup', name=u'mygroup') diff -r 48b26bfd6f02 -r 06bced8edddf test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Sat Sep 26 11:44:35 2009 +0200 +++ b/test/unittest_rqlrewrite.py Mon Sep 28 12:37:01 2009 +0200 @@ -136,19 +136,72 @@ "(Any C,T WHERE C in_state B, D in_group F, G require_state B, G name 'read', " "G require_group F, C title T, D eid %(A)s, C is Card)") - def test_relation_optimization(self): + def test_relation_optimization_1_lhs(self): # since Card in_state State as monovalued cardinality, the in_state # relation used in the rql expression can be ignored and S replaced by # the variable from the incoming query - card_constraint = ('X in_state S, U in_group G, P require_state S,' - 'P name "read", P require_group G') + snippet = ('X in_state S, S name "hop"') rqlst = parse('Card C WHERE C in_state STATE') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE, C is Card, " + "EXISTS(STATE name 'hop'), STATE is State") + def test_relation_optimization_1_rhs(self): + snippet = ('TW subworkflow_exit X, TW name "hop"') + rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT') + rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " + "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") + + def test_relation_optimization_2_lhs(self): + # optional relation can be shared if also optional in the snippet + snippet = ('X in_state S?, S name "hop"') + rqlst = parse('Card C WHERE C in_state STATE?') + rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE?, C is Card, " + "EXISTS(STATE name 'hop'), STATE is State") + def test_relation_optimization_2_rhs(self): + snippet = ('TW? subworkflow_exit X, TW name "hop"') + rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT') + rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C in_state STATE, C is Card, A eid %(B)s, " - "EXISTS(A in_group D, E require_state STATE, " - "E name 'read', E require_group D, D is CWGroup, E is CWPermission), " - "STATE is State") + "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " + "EXISTS(C name 'hop'), C is WorkflowTransition") + + def test_relation_optimization_3_lhs(self): + # optional relation in the snippet but not in the orig tree can be shared + snippet = ('X in_state S?, S name "hop"') + rqlst = parse('Card C WHERE C in_state STATE') + rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE, C is Card, " + "EXISTS(STATE name 'hop'), STATE is State") + def test_relation_optimization_3_rhs(self): + snippet = ('TW? subworkflow_exit X, TW name "hop"') + rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT') + rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " + "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") + + def test_relation_non_optimization_1_lhs(self): + # but optional relation in the orig tree but not in the snippet can't be shared + snippet = ('X in_state S, S name "hop"') + rqlst = parse('Card C WHERE C in_state STATE?') + rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE?, C is Card, " + "EXISTS(C in_state A, A name 'hop', A is State), STATE is State") + def test_relation_non_optimization_1_rhs(self): + snippet = ('TW subworkflow_exit X, TW name "hop"') + rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT') + rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " + "EXISTS(A subworkflow_exit EXIT, A name 'hop', A is WorkflowTransition), " + "C is WorkflowTransition") def test_unsupported_constraint_1(self): # CWUser doesn't have require_permission diff -r 48b26bfd6f02 -r 06bced8edddf web/data/cubicweb.css --- a/web/data/cubicweb.css Sat Sep 26 11:44:35 2009 +0200 +++ b/web/data/cubicweb.css Mon Sep 28 12:37:01 2009 +0200 @@ -482,22 +482,6 @@ padding-right: 1em; } -div.boxPref { - margin: 10px 0px 0px; -} - -div.boxPrefTitle { - font-weight: bold; - background: #cfceb7; - margin-bottom: 6px; - padding-bottom: 0.2em; - overflow: hidden; -} - -div.boxPrefTitle span{ - padding:0px 5px; -} - input.rqlsubmit{ background: #fffff8 url("go.png") 50% 50% no-repeat; width: 20px; diff -r 48b26bfd6f02 -r 06bced8edddf web/data/cubicweb.htmlhelpers.js --- a/web/data/cubicweb.htmlhelpers.js Sat Sep 26 11:44:35 2009 +0200 +++ b/web/data/cubicweb.htmlhelpers.js Mon Sep 28 12:37:01 2009 +0200 @@ -154,7 +154,7 @@ CubicWeb.rounded = [ ['div.sideBoxBody', 'bottom 6px'], - ['div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month', 'top 6px'] + ['div.boxTitle, div.sideBoxTitle, th.month', 'top 6px'] ]; function roundedCorners(node) { diff -r 48b26bfd6f02 -r 06bced8edddf web/formfields.py --- a/web/formfields.py Sat Sep 26 11:44:35 2009 +0200 +++ b/web/formfields.py Mon Sep 28 12:37:01 2009 +0200 @@ -336,10 +336,12 @@ widget = FileInput needs_multipart = True - def __init__(self, format_field=None, encoding_field=None, **kwargs): + def __init__(self, format_field=None, encoding_field=None, name_field=None, + **kwargs): super(FileField, self).__init__(**kwargs) self.format_field = format_field self.encoding_field = encoding_field + self.name_field = name_field def actual_fields(self, form): yield self @@ -347,6 +349,8 @@ yield self.format_field if self.encoding_field: yield self.encoding_field + if self.name_field: + yield self.name_field def render(self, form, renderer): wdgs = [self.get_widget(form).render(form, self, renderer)] @@ -358,6 +362,8 @@ xml_escape(form._cw.build_url('data/puce_down.png')), form._cw._('show advanced fields'))) wdgs.append(u'