--- 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:
--- 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):
--- 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
````````````````````
--- 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.
--- /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 <cubicweb.cwconfig>`
+----------------------------------------
+
+.. automodule:: cubicweb.cwconfig
+ :members:
--- 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 <cubicweb.cwconfig>`
-----------------------------------------
-
-.. automodule:: cubicweb.cwconfig
- :members:
--- 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
--- 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
--- 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)
--- /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')
--- 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))
--- 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'
--- 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
--- 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
--- 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__ = ()
--- 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')
--- 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
--- 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;
--- 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) {
--- 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'<div id="%s" class="hidden">' % divid)
+ if self.name_field:
+ wdgs.append(self.render_subfield(form, self.name_field, renderer))
if self.format_field:
wdgs.append(self.render_subfield(form, self.format_field, renderer))
if self.encoding_field:
@@ -626,7 +632,7 @@
kwargs['max_length'] = cstr.max
return StringField(**kwargs)
if fieldclass is FileField:
- for metadata in ('format', 'encoding'):
+ for metadata in ('format', 'encoding', 'name'):
metaschema = eschema.has_metadata(rschema, metadata)
if metaschema is not None:
kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
--- a/web/uicfg.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/uicfg.py Mon Sep 28 12:37:01 2009 +0200
@@ -207,12 +207,24 @@
else:
self.setdefault(eschema, 'application')
+indexview_etype_section = {'EmailAddress': 'subobject',
+ 'CWUser': 'system',
+ 'CWGroup': 'system',
+ 'CWPermission': 'system',
+ 'CWCache': 'system',
+ 'Workflow': 'system',
+ 'State': 'hidden',
+ 'BaseTransition': 'hidden',
+ 'Transition': 'hidden',
+ 'WorkflowTransition': 'hidden',
+ }
+
indexview_etype_section = InitializableDict(EmailAddress='subobject',
CWUser='system',
CWGroup='system',
CWPermission='system',
CWCache='system',
- BaseTransition='hidden',
+ Workflow='system',
)
# autoform.AutomaticEntityForm configuration ##################################
--- a/web/views/actions.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/views/actions.py Mon Sep 28 12:37:01 2009 +0200
@@ -9,7 +9,7 @@
_ = unicode
from cubicweb.appobject import objectify_selector
-from cubicweb.selectors import (EntitySelector,
+from cubicweb.selectors import (EntitySelector, yes,
one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
nonempty_rset, non_final_entity,
authenticated_user, match_user_groups, match_search_state,
@@ -363,6 +363,18 @@
order = 30
+class PoweredByAction(Action):
+ id = 'poweredby'
+ __select__ = yes()
+
+ category = 'footer'
+ order = 3
+ title = _('powered by CubicWeb')
+
+ def url(self):
+ return 'http://www.cubicweb.org'
+
+
from logilab.common.deprecation import class_moved
from cubicweb.web.views.bookmark import FollowAction
FollowAction = class_moved(FollowAction)
--- a/web/views/autoform.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/views/autoform.py Mon Sep 28 12:37:01 2009 +0200
@@ -111,6 +111,11 @@
@property
def form_needs_multipart(self):
"""true if the form needs enctype=multipart/form-data"""
+ return self._subform_needs_multipart()
+
+ def _subform_needs_multipart(self, _tested=None):
+ if _tested is None:
+ _tested = set()
if super(AutomaticEntityForm, self).form_needs_multipart:
return True
# take a look at inlined forms to check (recursively) if they
@@ -129,11 +134,18 @@
if len(targettypes) != 1:
continue
targettype = targettypes[0]
+ if targettype in _tested:
+ continue
+ _tested.add(targettype)
if self.should_inline_relation_form(rschema, targettype, role):
entity = self._cw.vreg['etypes'].etype_class(targettype)(self._cw)
subform = self._cw.vreg['forms'].select('edition', self._cw,
entity=entity)
- if subform.form_needs_multipart:
+ if hasattr(subform, '_subform_needs_multipart'):
+ needs_multipart = subform._subform_needs_multipart(_tested)
+ else:
+ needs_multipart = subform.form_needs_multipart
+ if needs_multipart:
return True
return False
--- a/web/views/basetemplates.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/views/basetemplates.py Mon Sep 28 12:37:01 2009 +0200
@@ -424,14 +424,13 @@
def call(self, **kwargs):
req = self._cw
self.w(u'<div class="footer">')
- # XXX Take object from the registry if in there? would be
- # better anyway
- from cubicweb.web.views.wdoc import ChangeLogView
- self.w(u'<a href="%s">%s</a> | ' % (req.build_url('changelog'),
- req._(ChangeLogView.title).lower()))
- self.w(u'<a href="%s">%s</a> | ' % (req.build_url('doc/about'),
- req._('about this site')))
- self.w(u'<a href="http://www.cubicweb.org">%s</a>' % req._('powered by CubicWeb'))
+ actions = self.vreg['actions'].possible_actions(self.req, rset=self.rset)
+ footeractions = actions.get('footer', ())
+ for i, action in enumerate(footeractions):
+ self.w(u'<a href="%s">%s</a>' % (action.url(),
+ self.req._(action.title)))
+ if i < (len(footeractions) - 1):
+ self.w(u' | ')
self.w(u'</div>')
@@ -528,8 +527,3 @@
if config.get('https-url'):
return req.url().replace(req.base_url(), config['https-url'])
return req.url()
-
-
-## vregistry registration callback ############################################
-def registration_callback(vreg):
- vreg.register_all(globals().values(), __name__)
--- a/web/views/startup.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/views/startup.py Mon Sep 28 12:37:01 2009 +0200
@@ -21,6 +21,7 @@
__regid__ = 'manage'
title = _('manage')
http_cache_manager = httpcache.EtagHTTPCacheManager
+ add_etype_links = ()
def display_folders(self):
return False
@@ -68,6 +69,16 @@
self.w(u'<h4>%s</h4>\n' % self._cw._('Browse by category'))
self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
+ def create_links(self):
+ self.w(u'<ul class="createLink">')
+ for etype in self.add_etype_links:
+ eschema = self.schema.eschema(etype)
+ if eschema.has_perm(self.req, 'add'):
+ self.w(u'<li><a href="%s">%s</a></li>' % (
+ self.req.build_url('add/%s' % eschema),
+ self.req.__('add a %s' % eschema).capitalize()))
+ self.w(u'</ul>')
+
def startup_views(self):
self.w(u'<h4>%s</h4>\n' % self._cw._('Startup views'))
self.startupviews_table()
--- a/web/views/wdoc.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/views/wdoc.py Mon Sep 28 12:37:01 2009 +0200
@@ -15,12 +15,11 @@
from logilab.common.changelog import ChangeLog
from logilab.mtconverter import CHARSET_DECL_RGX
-from cubicweb.selectors import match_form_params
+from cubicweb.selectors import match_form_params, yes
from cubicweb.view import StartupView
from cubicweb.utils import strptime, todate
from cubicweb.common.uilib import rest_publish
-from cubicweb.web import NotFound
-
+from cubicweb.web import NotFound, action
_ = unicode
# table of content management #################################################
@@ -235,3 +234,28 @@
break
w('') # blank line
self.w(rest_publish(self, '\n'.join(restdata)))
+
+
+class ChangeLogAction(action.Action):
+ id = 'changelog'
+ __select__ = yes()
+
+ category = 'footer'
+ order = 1
+ title = ChangeLogView.title
+
+ def url(self):
+ return self.req.build_url('changelog')
+
+
+class AboutAction(action.Action):
+ id = 'about'
+ __select__ = yes()
+
+ category = 'footer'
+ order = 2
+ title = _('about this site')
+
+ def url(self):
+ return self.req.build_url('doc/about')
+
--- a/web/views/workflow.py Sat Sep 26 11:44:35 2009 +0200
+++ b/web/views/workflow.py Mon Sep 28 12:37:01 2009 +0200
@@ -24,6 +24,11 @@
from cubicweb.web import formfields as ff, formwidgets as fwdgs
from cubicweb.web.views import TmpFileViewMixin, forms, primary
+_pvs = uicfg.primaryview_section
+_pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
+_pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
+_pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
+
_abaa = uicfg.actionbox_appearsin_addmenu
_abaa.tag_subject_of(('BaseTransition', 'condition', 'RQLExpression'), False)
_abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)