Commit 6d1e3625 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

backport stable branch

......@@ -76,3 +76,7 @@ f3d2adf483320d7726136433a41c57b130cbdc15 cubicweb-version-3.4.11
f476cecd46904f215bd29249ded8508d8f5634d7 cubicweb-debian-version-3.5.1-1
1f0aa3cd5af2c92df8f9695773b8e465eb6f1795 cubicweb-version-3.5.2
75cc4aa76fb12c06d4190956aa050cdf19ba4d8f cubicweb-debian-version-3.5.2-1
540210e138d323e5224d7b08cbd71f5a23ed630d cubicweb-version-3.5.3
2e22b975f9c23aebfe3e0a16a798c3fe81fa2a82 cubicweb-debian-version-3.5.3-1
312349b3712e0a3e32247e03fdc7408e17bd19de cubicweb-version-3.5.4
37d025b2aa7735dae4a861059014c560b45b19e6 cubicweb-debian-version-3.5.4-1
......@@ -55,7 +55,7 @@ class Binary(StringIO):
"Binary objects must use raw strings, not %s" % data.__class__
StringIO.write(self, data)
# use this dictionary for renaming of entity types while keeping bw compath
# use this dictionary for renaming of entity types while keeping bw compat
ETYPE_NAME_MAP = {# 3.2 migration
'ECache': 'CWCache',
'EUser': 'CWUser',
......
......@@ -7,7 +7,7 @@ software
distname = "cubicweb"
modname = "cubicweb"
numversion = (3, 5, 2)
numversion = (3, 5, 4)
version = '.'.join(str(num) for num in numversion)
license = 'LGPL v2'
......
......@@ -71,7 +71,10 @@ class InstanceCommand(Command):
def ordered_instances(self):
"""return instances in the order in which they should be started,
considering $REGISTRY_DIR/startorder file if it exists (useful when
some instances depends on another as external source
some instances depends on another as external source).
Instance used by another one should appears first in the file (one
instance per line)
"""
regdir = cwcfg.registry_dir()
_allinstances = list_instances(regdir)
......
cubicweb (3.5.4-1) unstable; urgency=low
* new upstream release
-- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 07 Oct 2009 20:58:35 +0200
cubicweb (3.5.3-1) unstable; urgency=low
* new upstream release
-- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 07 Oct 2009 15:35:10 +0200
cubicweb (3.5.2-1) unstable; urgency=low
* new upstream release
......
......@@ -158,7 +158,7 @@ class WorkflowTC(CubicWebTC):
'WHERE T name "deactivate"')
self._test_stduser_deactivate()
def test_subworkflow_base(self):
def test_swf_base(self):
"""subworkflow
+-----------+ tr1 +-----------+
......@@ -239,7 +239,7 @@ class WorkflowTC(CubicWebTC):
('swfstate3', 'state3', 'swftr1', 'exiting from subworkflow subworkflow'),
])
def test_subworkflow_exit_consistency(self):
def test_swf_exit_consistency(self):
# sub-workflow
swf = add_wf(self, 'CWGroup', name='subworkflow')
swfstate1 = swf.add_state(u'swfstate1', initial=True)
......@@ -255,6 +255,68 @@ class WorkflowTC(CubicWebTC):
ex = self.assertRaises(ValidationError, self.commit)
self.assertEquals(ex.errors, {'subworkflow_exit': u"can't have multiple exits on the same state"})
def test_swf_fire_in_a_row(self):
# sub-workflow
subwf = add_wf(self, 'CWGroup', name='subworkflow')
xsigning = subwf.add_state('xsigning', initial=True)
xaborted = subwf.add_state('xaborted')
xsigned = subwf.add_state('xsigned')
xabort = subwf.add_transition('xabort', (xsigning,), xaborted)
xsign = subwf.add_transition('xsign', (xsigning,), xsigning)
xcomplete = subwf.add_transition('xcomplete', (xsigning,), xsigned,
type=u'auto')
# main workflow
twf = add_wf(self, 'CWGroup', name='mainwf', default=True)
created = twf.add_state(_('created'), initial=True)
identified = twf.add_state(_('identified'))
released = twf.add_state(_('released'))
closed = twf.add_state(_('closed'))
twf.add_wftransition(_('identify'), subwf, (created,),
[(xsigned, identified), (xaborted, created)])
twf.add_wftransition(_('release'), subwf, (identified,),
[(xsigned, released), (xaborted, identified)])
twf.add_wftransition(_('close'), subwf, (released,),
[(xsigned, closed), (xaborted, released)])
self.commit()
group = self.add_entity('CWGroup', name=u'grp1')
self.commit()
for trans in ('identify', 'release', 'close'):
group.fire_transition(trans)
self.commit()
def test_swf_magic_tr(self):
# sub-workflow
subwf = add_wf(self, 'CWGroup', name='subworkflow')
xsigning = subwf.add_state('xsigning', initial=True)
xaborted = subwf.add_state('xaborted')
xsigned = subwf.add_state('xsigned')
xabort = subwf.add_transition('xabort', (xsigning,), xaborted)
xsign = subwf.add_transition('xsign', (xsigning,), xsigned)
# main workflow
twf = add_wf(self, 'CWGroup', name='mainwf', default=True)
created = twf.add_state(_('created'), initial=True)
identified = twf.add_state(_('identified'))
released = twf.add_state(_('released'))
twf.add_wftransition(_('identify'), subwf, created,
[(xaborted, None), (xsigned, identified)])
twf.add_wftransition(_('release'), subwf, identified,
[(xaborted, None)])
self.commit()
group = self.add_entity('CWGroup', name=u'grp1')
self.commit()
for trans, nextstate in (('identify', 'xsigning'),
('xabort', 'created'),
('identify', 'xsigning'),
('xsign', 'identified'),
('release', 'xsigning'),
('xabort', 'identified')
):
group.fire_transition(trans)
self.commit()
group.clear_all_caches()
self.assertEquals(group.state, nextstate)
class CustomWorkflowTC(CubicWebTC):
......
......@@ -124,27 +124,28 @@ class Workflow(AnyEntity):
tr.set_transition_permissions(requiredgroups, conditions, reset=False)
return tr
def add_transition(self, name, fromstates, tostate,
def add_transition(self, name, fromstates, tostate=None,
requiredgroups=(), conditions=(), **kwargs):
"""add a transition to this workflow from some state(s) to another"""
tr = self._add_transition('Transition', name, fromstates,
requiredgroups, conditions, **kwargs)
if hasattr(tostate, 'eid'):
tostate = tostate.eid
self._cw.execute('SET T destination_state S '
'WHERE S eid %(s)s, T eid %(t)s',
{'t': tr.eid, 's': tostate}, ('s', 't'))
if tostate is not None:
if hasattr(tostate, 'eid'):
tostate = tostate.eid
self._cw.execute('SET T destination_state S '
'WHERE S eid %(s)s, T eid %(t)s',
{'t': tr.eid, 's': tostate}, ('s', 't'))
return tr
def add_wftransition(self, name, subworkflow, fromstates, exitpoints,
def add_wftransition(self, name, subworkflow, fromstates, exitpoints=(),
requiredgroups=(), conditions=(), **kwargs):
"""add a workflow transition to this workflow"""
tr = self._add_transition('WorkflowTransition', name, fromstates,
requiredgroups, conditions, **kwargs)
if hasattr(subworkflow, 'eid'):
subworkflow = subworkflow.eid
self._cw.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
{'t': tr.eid, 'wf': subworkflow}, ('wf', 't'))
assert _cw.req.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
{'t': tr.eid, 'wf': subworkflow}, ('wf', 't'))
for fromstate, tostate in exitpoints:
tr.add_exit_point(fromstate, tostate)
return tr
......@@ -258,28 +259,37 @@ class WorkflowTransition(BaseTransition):
def add_exit_point(self, fromstate, tostate):
if hasattr(fromstate, 'eid'):
fromstate = fromstate.eid
if hasattr(tostate, 'eid'):
tostate = tostate.eid
self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
'X subworkflow_state FS, X destination_state TS '
'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s',
{'t': self.eid, 'fs': fromstate, 'ts': tostate},
('t', 'fs', 'ts'))
def get_exit_point(self, state):
if tostate is None:
self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
'X subworkflow_state FS WHERE T eid %(t)s, FS eid %(fs)s',
{'t': self.eid, 'fs': fromstate}, ('t', 'fs'))
else:
if hasattr(tostate, 'eid'):
tostate = tostate.eid
self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
'X subworkflow_state FS, X destination_state TS '
'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s',
{'t': self.eid, 'fs': fromstate, 'ts': tostate},
('t', 'fs', 'ts'))
def get_exit_point(self, entity, stateeid):
"""if state is an exit point, return its associated destination state"""
if hasattr(state, 'eid'):
state = state.eid
stateeid = self.exit_points().get(state)
if stateeid is not None:
return self._cw.entity_from_eid(stateeid)
return None
if hasattr(stateeid, 'eid'):
stateeid = stateeid.eid
try:
tostateeid = self.exit_points()[stateeid]
except KeyError:
return None
if tostateeid is None:
# go back to state from which we've entered the subworkflow
return entity.subworkflow_input_trinfo().previous_state
return self._cw.entity_from_eid(tostateeid)
@cached
def exit_points(self):
result = {}
for ep in self.subworkflow_exit:
result[ep.subwf_state.eid] = ep.destination.eid
result[ep.subwf_state.eid] = ep.destination and ep.destination.eid
return result
def clear_all_caches(self):
......@@ -297,7 +307,7 @@ class SubWorkflowExitPoint(AnyEntity):
@property
def destination(self):
return self.destination_state[0]
return self.destination_state and self.destination_state[0] or None
class State(AnyEntity):
......@@ -458,11 +468,10 @@ class WorkflowableMixIn(object):
"""
assert self.current_workflow
if isinstance(tr, basestring):
tr = self.current_workflow.transition_by_name(tr)
tr = self.current_workflow.transition_by_name(trname)
if tr is None:
raise WorkflowException('not a %s transition: %s' % (self.__regid__,
trname))
_tr = self.current_workflow.transition_by_name(tr)
assert _tr is not None, 'not a %s transition: %s' % (
self.__regid__, tr)
tr = _tr
return self._add_trinfo(comment, commentformat, tr.eid)
def change_state(self, statename, comment=None, commentformat=None, tr=None):
......@@ -487,18 +496,20 @@ class WorkflowableMixIn(object):
# XXX try to find matching transition?
return self._add_trinfo(comment, commentformat, tr and tr.eid, stateeid)
def subworkflow_input_transition(self):
"""return the transition which has went through the current sub-workflow
def subworkflow_input_trinfo(self):
"""return the TrInfo which has be recorded when this entity went into
the current sub-workflow
"""
if self.main_workflow.eid == self.current_workflow.eid:
return # doesn't make sense
subwfentries = []
for trinfo in reversed(self.workflow_history):
for trinfo in self.workflow_history:
if (trinfo.transition and
trinfo.previous_state.workflow.eid != trinfo.new_state.workflow.eid):
# entering or leaving a subworkflow
if (subwfentries and
subwfentries[-1].new_state.workflow.eid == trinfo.previous_state.workflow.eid):
subwfentries[-1].new_state.workflow.eid == trinfo.previous_state.workflow.eid and
subwfentries[-1].previous_state.workflow.eid == trinfo.new_state.workflow.eid):
# leave
del subwfentries[-1]
else:
......@@ -506,7 +517,12 @@ class WorkflowableMixIn(object):
subwfentries.append(trinfo)
if not subwfentries:
return None
return subwfentries[-1].transition
return subwfentries[-1]
def subworkflow_input_transition(self):
"""return the transition which has went through the current sub-workflow
"""
return getattr(self.subworkflow_input_trinfo(), 'transition', None)
def clear_all_caches(self):
super(WorkflowableMixIn, self).clear_all_caches()
......
......@@ -61,7 +61,7 @@ class Entity(AppObject, dict):
:cvar skip_copy_for: a list of relations that should be skipped when copying
this kind of entity. Note that some relations such
as composite relations or relations that have '?1' as object
cardinality
cardinality are always skipped.
"""
__registry__ = 'etypes'
__select__ = yes()
......@@ -373,7 +373,9 @@ class Entity(AppObject, dict):
# entity cloning ##########################################################
def copy_relations(self, ceid):
"""copy relations of the object with the given eid on this object
"""copy relations of the object with the given eid on this
object (this method is called on the newly created copy, and
ceid designates the original entity).
By default meta and composite relations are skipped.
Overrides this if you want another behaviour
......
......@@ -35,10 +35,10 @@ def daemonize():
# (start-repository command)
# See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
if os.fork(): # launch child and...
os._exit(0)
return 1
os.setsid()
if os.fork(): # launch child and...
os._exit(0) # kill off parent again.
if os.fork(): # launch child again.
return 1
# move to the root to avoit mount pb
os.chdir('/')
# set paranoid umask
......@@ -97,14 +97,17 @@ class CubicWebRootResource(resource.PostableResource):
addSlash = False
def __init__(self, config, debug=None):
self.appli = CubicWebPublisher(config, debug=debug)
self.debugmode = debug
self.config = config
self.base_url = config['base-url'] or config.default_base_url()
self.versioned_datadir = 'data%s' % config.instance_md5_version()
assert self.base_url[-1] == '/'
self.https_url = config['https-url']
assert not self.https_url or self.https_url[-1] == '/'
def init_publisher(self):
config = self.config
self.appli = CubicWebPublisher(config, debug=self.debugmode)
self.versioned_datadir = 'data%s' % config.instance_md5_version()
# when we have an in-memory repository, clean unused sessions every XX
# seconds and properly shutdown the server
if config.repo_method == 'inmemory':
......@@ -122,6 +125,9 @@ class CubicWebRootResource(resource.PostableResource):
self.appli.repo.start_looping_tasks()
self.set_url_rewriter()
CW_EVENT_MANAGER.bind('after-registry-reload', self.set_url_rewriter)
def start_service(self):
config = self.config
interval = min(config['cleanup-session-time'] or 120,
config['cleanup-anonymous-session-time'] or 720) / 2.
start_task(interval, self.appli.session_handler.clean_sessions)
......@@ -375,12 +381,12 @@ def run(config, debug):
# serve it via standard HTTP on port set in the configuration
port = config['port'] or 8080
reactor.listenTCP(port, channel.HTTPFactory(website))
baseurl = config['base-url'] or config.default_base_url()
logger = getLogger('cubicweb.twisted')
logger.info('instance started on %s', baseurl)
logger.info('instance started on %s', root_resource.base_url)
if not debug:
print 'instance starting in the background'
daemonize()
if daemonize():
return # child process
if config['pid-file']:
# ensure the directory where the pid-file should be set exists (for
# instance /var/run/cubicweb may be deleted on computer restart)
......@@ -388,6 +394,7 @@ def run(config, debug):
if not os.path.exists(piddir):
os.makedirs(piddir)
file(config['pid-file'], 'w').write(str(os.getpid()))
root_resource.init_publisher() # before changing uid
if config['uid'] is not None:
try:
uid = int(config['uid'])
......@@ -395,6 +402,7 @@ def run(config, debug):
from pwd import getpwnam
uid = getpwnam(config['uid']).pw_uid
os.setuid(uid)
root_resource.start_service()
if config['profile']:
prof = hotshot.Profile(config['profile'])
prof.runcall(reactor.run)
......
......@@ -242,7 +242,7 @@ class FiredTransitionHook(WorkflowHook):
# inconsistency detected
msg = session._("state doesn't belong to entity's current workflow")
raise ValidationError(entity.eid, {'to_state': msg})
tostate = wftr.get_exit_point(entity['to_state'])
tostate = wftr.get_exit_point(forentity, entity['to_state'])
if tostate is not None:
# reached an exit point
msg = session._('exiting from subworkflow %s')
......
......@@ -30,6 +30,14 @@ msgstr ""
msgid " from state %(fromstate)s to state %(tostate)s\n"
msgstr ""
#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr ""
#, python-format
msgid "%(attr)s updated from %(oldvalue)s to %(newvalue)s"
msgstr ""
#, python-format
msgid "%(cstr)s constraint failed for value %(value)r"
msgstr ""
......@@ -118,6 +126,10 @@ msgstr ""
msgid "%s software version of the database"
msgstr ""
#, python-format
msgid "%s updated"
msgstr ""
msgid "(UNEXISTANT EID)"
msgstr ""
......@@ -533,12 +545,12 @@ msgstr "This constraint"
msgid "This CWConstraintType"
msgstr "This constraint type"
msgctxt "inlined:CWRelation.from_entity.subject"
msgid "This CWEType"
msgstr ""
msgstr "This entity type"
msgctxt "inlined:CWRelation.from_entity.subject"
msgid "This CWEType"
msgstr "This entity type"
msgstr ""
msgctxt "inlined:CWRelation.to_entity.subject"
msgid "This CWEType"
......@@ -709,6 +721,12 @@ msgstr ""
msgid "actions"
msgstr ""
msgid "actions_about"
msgstr ""
msgid "actions_about_description"
msgstr ""
msgid "actions_addentity"
msgstr "add an entity of this type"
......@@ -727,6 +745,12 @@ msgstr "cancel the selection"
msgid "actions_cancel_description"
msgstr ""
msgid "actions_changelog"
msgstr ""
msgid "actions_changelog_description"
msgstr ""
msgid "actions_copy"
msgstr "copy"
......@@ -799,6 +823,12 @@ msgstr "my preferences"
msgid "actions_myprefs_description"
msgstr ""
msgid "actions_poweredby"
msgstr ""
msgid "actions_poweredby_description"
msgstr ""
msgid "actions_prefs"
msgstr "preferences"
......@@ -856,6 +886,9 @@ msgstr ""
msgid "add"
msgstr ""
msgid "add BaseTransition transition_of Workflow object"
msgstr ""
msgid "add Bookmark bookmarked_by CWUser object"
msgstr "bookmark"
......@@ -934,11 +967,11 @@ msgstr "subworkflow exit-point"
msgid "add WorkflowTransition transition_of Workflow object"
msgstr "workflow-transition"
msgctxt "inlined:CWRelation.to_entity.subject"
msgctxt "inlined:CWRelation.from_entity.subject"
msgid "add a CWEType"
msgstr "add an entity type"
msgctxt "inlined:CWRelation.from_entity.subject"
msgctxt "inlined:CWRelation.to_entity.subject"
msgid "add a CWEType"
msgstr "add an entity type"
......@@ -974,6 +1007,10 @@ msgctxt "CWRType"
msgid "add_permission"
msgstr "add permission"
msgctxt "CWGroup"
msgid "add_permission_object"
msgstr "can add"
msgctxt "RQLExpression"
msgid "add_permission_object"
msgstr "used to define add permission on"
......@@ -981,10 +1018,6 @@ msgstr "used to define add permission on"
msgid "add_permission_object"
msgstr "has permission to add"
msgctxt "CWGroup"
msgid "add_permission_object"
msgstr "can add"
#, python-format
msgid "added %(etype)s #%(eid)s (%(title)s)"
msgstr ""
......@@ -996,7 +1029,7 @@ msgid ""
msgstr ""
msgid "addrelated"
msgstr ""
msgstr "add"
msgid "address"
msgstr ""
......@@ -1005,12 +1038,12 @@ msgctxt "EmailAddress"
msgid "address"
msgstr "address"
msgctxt "EmailAddress"
msgid "alias"
msgstr "alias"
msgstr ""
msgctxt "EmailAddress"
msgid "alias"
msgstr ""
msgstr "alias"
msgid "allow to set a specific workflow for an entity"
msgstr ""
......@@ -1028,18 +1061,18 @@ msgctxt "State"
msgid "allowed_transition"
msgstr "allowed transition"
msgctxt "BaseTransition"
msgid "allowed_transition_object"
msgstr "incoming states"
msgctxt "WorkflowTransition"
msgctxt "Transition"
msgid "allowed_transition_object"
msgstr "incoming states"
msgctxt "Transition"
msgctxt "WorkflowTransition"
msgid "allowed_transition_object"
msgstr "incoming states"
msgctxt "BaseTransition"
msgid "allowed_transition_object"
msgstr "incoming states"
......@@ -1098,6 +1131,9 @@ msgstr ""
msgid "authentication failure"
msgstr ""
msgid "auto"
msgstr "automatic"
msgid "automatic"
msgstr ""
......@@ -1211,22 +1247,22 @@ msgstr ""
msgid "by relation"
msgstr ""
msgctxt "TrInfo"
msgid "by_transition"
msgstr "by transition"
msgctxt "TrInfo"
msgid "by_transition"
msgstr "by transition"
msgctxt "WorkflowTransition"
msgctxt "BaseTransition"
msgid "by_transition_object"
msgstr "transition information"
msgctxt "BaseTransition"
msgctxt "Transition"
msgid "by_transition_object"
msgstr "transition information"
msgctxt "Transition"
msgctxt "WorkflowTransition"
msgid "by_transition_object"
msgstr "transition information"
......@@ -1278,14 +1314,14 @@ msgstr ""
msgid "cancel this insert"
msgstr ""
msgctxt "CWRelation"
msgid "cardinality"
msgstr "cardinality"
msgctxt "CWAttribute"
msgid "cardinality"
msgstr "cardinality"
msgctxt "CWAttribute"
msgctxt "CWRelation"
msgid "cardinality"
msgstr "cardinality"
......@@ -1389,31 +1425,31 @@ msgctxt "CWRelation"
msgid "composite"
msgstr "composite"
msgctxt "WorkflowTransition"
msgid "condition"
msgstr "condition"
msgctxt "Transition"