--- a/common/mixins.py Fri Aug 21 16:26:20 2009 +0200
+++ b/common/mixins.py Wed Aug 26 14:45:56 2009 +0200
@@ -251,7 +251,7 @@
"""a recursive path view"""
id = 'path'
item_vid = 'oneline'
- separator = u' > '
+ separator = u' > '
def call(self, **kwargs):
self.w(u'<div class="pathbar">')
--- a/common/test/unittest_uilib.py Fri Aug 21 16:26:20 2009 +0200
+++ b/common/test/unittest_uilib.py Wed Aug 26 14:45:56 2009 +0200
@@ -81,8 +81,6 @@
got = uilib.text_cut(text, 30)
self.assertEquals(got, expected)
-
-
if __name__ == '__main__':
unittest_main()
--- a/common/uilib.py Fri Aug 21 16:26:20 2009 +0200
+++ b/common/uilib.py Wed Aug 26 14:45:56 2009 +0200
@@ -263,7 +263,6 @@
res = unicode(res, 'UTF8')
return res
-
# traceback formatting ########################################################
import traceback
@@ -309,7 +308,7 @@
xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
if stackentry[3]:
string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
- strings.append(u' %s<br/>\n' % (string))
+ strings.append(u'  %s<br/>\n' % (string))
# add locals info for each entry
try:
local_context = tcbk.tb_frame.f_locals
--- a/debian/control Fri Aug 21 16:26:20 2009 +0200
+++ b/debian/control Wed Aug 26 14:45:56 2009 +0200
@@ -76,7 +76,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.44.0), python-yams (>= 0.24.0), python-rql (>= 0.22.1), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.44.0), python-yams (>= 0.24.0), python-rql (>= 0.22.3), python-lxml
Recommends: python-simpletal (>= 4.0)
Conflicts: cubicweb-core
Replaces: cubicweb-core
--- a/debian/cubicweb-ctl.cubicweb.init Fri Aug 21 16:26:20 2009 +0200
+++ b/debian/cubicweb-ctl.cubicweb.init Wed Aug 26 14:45:56 2009 +0200
@@ -22,11 +22,14 @@
# Check if we are sure to not want the start-stop-daemon machinery here
# Refer to Debian Policy Manual section 9.3.2 (Writing the scripts) for details.
-case "$1" in
- "force-reload")
- /usr/bin/cubicweb-ctl reload --force
- ;;
- "*|restart")
- /usr/bin/cubicweb-ctl $1 --force
- ;;
+case $1 in
+ force-reload)
+ /usr/bin/cubicweb-ctl reload --force
+ ;;
+ status)
+ /usr/bin/cubicweb-ctl status
+ ;;
+ *)
+ /usr/bin/cubicweb-ctl $1 --force
+ ;;
esac
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/dataimport.py Wed Aug 26 14:45:56 2009 +0200
@@ -0,0 +1,278 @@
+# -*- coding: utf-8 -*-
+"""This module provides tools to import tabular data.
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+
+
+Example of use (run this with `cubicweb-ctl shell instance import-script.py`):
+
+.. sourcecode:: python
+
+ # define data generators
+ GENERATORS = []
+
+ USERS = [('Prenom', 'firstname', ()),
+ ('Nom', 'surname', ()),
+ ('Identifiant', 'login', ()),
+ ]
+
+ def gen_users(ctl):
+ for row in ctl.get_data('utilisateurs'):
+ entity = mk_entity(row, USERS)
+ entity['upassword'] = u'motdepasse'
+ ctl.check('login', entity['login'], None)
+ ctl.store.add('CWUser', entity)
+ email = {'address': row['email']}
+ ctl.store.add('EmailAddress', email)
+ ctl.store.relate(entity['eid'], 'use_email', email['eid'])
+ ctl.store.rql('SET U in_group G WHERE G name "users", U eid %(x)s', {'x':entity['eid']})
+
+ CHK = [('login', check_doubles, 'Utilisateurs Login',
+ 'Deux utilisateurs ne devraient pas avoir le même login.'),
+ ]
+
+ GENERATORS.append( (gen_users, CHK) )
+
+ # progress callback
+ def tell(msg):
+ print msg
+
+ # create controller
+ ctl = CWImportController(RQLObjectStore())
+ ctl.askerror = True
+ ctl._tell = tell
+ ctl.generators = GENERATORS
+ ctl.store._checkpoint = checkpoint
+ ctl.store._rql = rql
+ ctl.data['utilisateurs'] = lazytable(utf8csvreader(open('users.csv')))
+ # run
+ ctl.run()
+ sys.exit(0)
+
+"""
+__docformat__ = "restructuredtext en"
+
+import sys, csv, traceback
+
+from logilab.common import shellutils
+
+def utf8csvreader(file, encoding='utf-8', separator=',', quote='"'):
+ """A csv reader that accepts files with any encoding and outputs
+ unicode strings."""
+ for row in csv.reader(file, delimiter=separator, quotechar=quote):
+ yield [item.decode(encoding) for item in row]
+
+def lazytable(reader):
+ """The first row is taken to be the header of the table and
+ used to output a dict for each row of data.
+
+ >>> data = lazytable(utf8csvreader(open(filename)))
+ """
+ header = reader.next()
+ for row in reader:
+ yield dict(zip(header, row))
+
+# base sanitizing functions #####
+
+def capitalize_if_unicase(txt):
+ if txt.isupper() or txt.islower():
+ return txt.capitalize()
+ return txt
+
+def no_space(txt):
+ return txt.replace(' ','')
+
+def no_uspace(txt):
+ return txt.replace(u'\xa0','')
+
+def no_dash(txt):
+ return txt.replace('-','')
+
+def alldigits(txt):
+ if txt.isdigit():
+ return txt
+ else:
+ return u''
+
+def strip(txt):
+ return txt.strip()
+
+# base checks #####
+
+def check_doubles(buckets):
+ """Extract the keys that have more than one item in their bucket."""
+ return [(key, len(value)) for key,value in buckets.items() if len(value) > 1]
+
+# make entity helper #####
+
+def mk_entity(row, map):
+ """Return a dict made from sanitized mapped values.
+
+ >>> row = {'myname': u'dupont'}
+ >>> map = [('myname', u'name', (capitalize_if_unicase,))]
+ >>> mk_entity(row, map)
+ {'name': u'Dupont'}
+ """
+ res = {}
+ for src, dest, funcs in map:
+ res[dest] = row[src]
+ for func in funcs:
+ res[dest] = func(res[dest])
+ return res
+
+# object stores
+
+class ObjectStore(object):
+ """Store objects in memory for faster testing. Will not
+ enforce the constraints of the schema and hence will miss
+ some problems.
+
+ >>> store = ObjectStore()
+ >>> user = {'login': 'johndoe'}
+ >>> store.add('CWUser', user)
+ >>> group = {'name': 'unknown'}
+ >>> store.add('CWUser', group)
+ >>> store.relate(user['eid'], 'in_group', group['eid'])
+ """
+
+ def __init__(self):
+ self.items = []
+ self.eids = {}
+ self.types = {}
+ self.relations = set()
+ self.indexes = {}
+ self._rql = None
+ self._checkpoint = None
+
+ def _put(self, type, item):
+ self.items.append(item)
+ return len(self.items) - 1
+
+ def add(self, type, item):
+ assert isinstance(item, dict), item
+ eid = item['eid'] = self._put(type, item)
+ self.eids[eid] = item
+ self.types.setdefault(type, []).append(eid)
+
+ def relate(self, eid_from, rtype, eid_to):
+ eids_valid = (eid_from < len(self.items) and eid_to <= len(self.items))
+ assert eids_valid, 'eid error %s %s' % (eid_from, eid_to)
+ self.relations.add( (eid_from, rtype, eid_to) )
+
+ def build_index(self, name, type, func):
+ index = {}
+ for eid in self.types[type]:
+ index.setdefault(func(self.eids[eid]), []).append(eid)
+ self.indexes[name] = index
+
+ def get_many(self, name, key):
+ return self.indexes[name].get(key, [])
+
+ def get_one(self, name, key):
+ eids = self.indexes[name].get(key, [])
+ assert len(eids) == 1
+ return eids[0]
+
+ def find(self, type, key, value):
+ for idx in self.types[type]:
+ item = self.items[idx]
+ if item[key] == value:
+ yield item
+
+ def rql(self, query, args):
+ if self._rql:
+ return self._rql(query, args)
+
+ def checkpoint(self):
+ if self._checkpoint:
+ self._checkpoint()
+
+class RQLObjectStore(ObjectStore):
+ """ObjectStore that works with an actual RQL repository."""
+
+ def _put(self, type, item):
+ query = ('INSERT %s X: ' % type) + ', '.join(['X %s %%(%s)s' % (key,key) for key in item])
+ return self.rql(query, item)[0][0]
+
+ def relate(self, eid_from, rtype, eid_to):
+ query = 'SET X %s Y WHERE X eid %%(from)s, Y eid %%(to)s' % rtype
+ self.rql(query, {'from': int(eid_from), 'to': int(eid_to)})
+ self.relations.add( (eid_from, rtype, eid_to) )
+
+# import controller #####
+
+class CWImportController(object):
+ """Controller of the data import process.
+
+ >>> ctl = CWImportController(store)
+ >>> ctl.generators = list_of_data_generators
+ >>> ctl.data = dict_of_data_tables
+ >>> ctl.run()
+ """
+
+ def __init__(self, store):
+ self.store = store
+ self.generators = None
+ self.data = {}
+ self.errors = None
+ self.askerror = False
+
+ def check(self, type, key, value):
+ self._checks.setdefault(type, {}).setdefault(key, []).append(value)
+
+ def check_map(self, entity, key, map, default):
+ try:
+ entity[key] = map[entity[key]]
+ except KeyError:
+ self.check(key, entity[key], None)
+ entity[key] = default
+
+ def run(self):
+ self.errors = {}
+ for func, checks in self.generators:
+ self._checks = {}
+ func_name = func.__name__[4:]
+ question = 'Importation de %s' % func_name
+ self.tell(question)
+ try:
+ func(self)
+ except:
+ import StringIO
+ tmp = StringIO.StringIO()
+ traceback.print_exc(file=tmp)
+ print tmp.getvalue()
+ self.errors[func_name] = ('Erreur lors de la transformation',
+ tmp.getvalue().splitlines())
+ for key, func, title, help in checks:
+ buckets = self._checks.get(key)
+ if buckets:
+ err = func(buckets)
+ if err:
+ self.errors[title] = (help, err)
+ self.store.checkpoint()
+ errors = sum(len(err[1]) for err in self.errors.values())
+ self.tell('Importation terminée. (%i objets, %i types, %i relations et %i erreurs).'
+ % (len(self.store.eids), len(self.store.types),
+ len(self.store.relations), errors))
+ if self.errors and self.askerror and confirm('Afficher les erreurs ?'):
+ import pprint
+ pprint.pprint(self.errors)
+
+ def get_data(self, key):
+ return self.data.get(key)
+
+ def index(self, name, key, value):
+ self.store.indexes.setdefault(name, {}).setdefault(key, []).append(value)
+
+ def tell(self, msg):
+ self._tell(msg)
+
+def confirm(question):
+ """A confirm function that asks for yes/no/abort and exits on abort."""
+ answer = shellutils.ASK.ask(question, ('Y','n','abort'), 'Y')
+ if answer == 'abort':
+ sys.exit(1)
+ return answer == 'Y'
--- a/entities/test/unittest_wfobjs.py Fri Aug 21 16:26:20 2009 +0200
+++ b/entities/test/unittest_wfobjs.py Wed Aug 26 14:45:56 2009 +0200
@@ -1,12 +1,15 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb import ValidationError
-def add_wf(self, etype, name=None):
+def add_wf(self, etype, name=None, default=False):
if name is None:
- name = unicode(etype)
- wf = self.execute('INSERT Workflow X: X name %(n)s', {'n': name}).get_entity(0, 0)
+ name = etype
+ wf = self.execute('INSERT Workflow X: X name %(n)s', {'n': unicode(name)}).get_entity(0, 0)
self.execute('SET WF workflow_of ET WHERE WF eid %(wf)s, ET name %(et)s',
{'wf': wf.eid, 'et': etype})
+ if default:
+ self.execute('SET ET default_workflow WF WHERE WF eid %(wf)s, ET name %(et)s',
+ {'wf': wf.eid, 'et': etype})
return wf
def parse_hist(wfhist):
@@ -31,10 +34,15 @@
def test_duplicated_state(self):
wf = add_wf(self, 'Company')
wf.add_state(u'foo', initial=True)
+ self.commit()
wf.add_state(u'foo')
ex = self.assertRaises(ValidationError, self.commit)
# XXX enhance message
self.assertEquals(ex.errors, {'state_of': 'unique constraint S name N, Y state_of O, Y name N failed'})
+ # no pb if not in the same workflow
+ wf2 = add_wf(self, 'Company')
+ foo = wf2.add_state(u'foo', initial=True)
+ self.commit()
def test_duplicated_transition(self):
wf = add_wf(self, 'Company')
@@ -98,9 +106,20 @@
trinfo = self._test_manager_deactivate(user)
self.assertEquals(trinfo.transition, None)
+ def test_set_in_state_bad_wf(self):
+ wf = add_wf(self, 'CWUser')
+ s = wf.add_state(u'foo', initial=True)
+ self.commit()
+ ex = self.assertRaises(ValidationError, self.session().unsafe_execute,
+ 'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
+ {'x': self.user().eid, 's': s.eid}, 'x')
+ self.assertEquals(ex.errors, {'in_state': "state doesn't belong to entity's workflow. "
+ "You may want to set a custom workflow for this entity first."})
+
def test_fire_transition(self):
user = self.user()
user.fire_transition('deactivate', comment=u'deactivate user')
+ user.clear_all_caches()
self.assertEquals(user.state, 'deactivated')
self._test_manager_deactivate(user)
trinfo = self._test_manager_deactivate(user)
@@ -139,64 +158,102 @@
'WHERE T name "deactivate"')
self._test_stduser_deactivate()
- def _init_wf_with_shared_state_or_tr(self):
- req = self.request()
- etypes = dict(self.execute('Any N, ET WHERE ET is CWEType, ET name N'
- ', ET name IN ("CWGroup", "Bookmark")'))
- self.grpwf = req.create_entity('Workflow', ('workflow_of', 'ET'),
- ET=etypes['CWGroup'], name=u'group workflow')
- self.bmkwf = req.execute('Any X WHERE X is Workflow, X workflow_of ET, ET name "Bookmark"').get_entity(0, 0)
- self.state1 = self.grpwf.add_state(u'state1', initial=True)
- self.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
- {'s': self.state1.eid, 'wf': self.bmkwf.eid})
- self.execute('SET WF initial_state S WHERE S eid %(s)s, WF eid %(wf)s',
- {'s': self.state1.eid, 'wf': self.bmkwf.eid})
- self.state2 = self.grpwf.add_state(u'state2')
- self.group = self.add_entity('CWGroup', name=u't1')
- self.bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view')
- # commit to link to the initial state
- self.commit()
+ def test_subworkflow_base(self):
+ """subworkflow
- def test_transitions_selection(self):
- """
- ------------------------ tr1 -----------------
- | state1 (CWGroup, Bookmark) | ------> | state2 (CWGroup) |
- ------------------------ -----------------
- | tr2 ------------------
- `------> | state3 (Bookmark) |
- ------------------
+ +-----------+ tr1 +-----------+
+ | swfstate1 | ------>| swfstate2 |
+ +-----------+ +-----------+
+ | tr2 +-----------+
+ `------>| swfstate3 |
+ +-----------+
+
+ main workflow
+
+ +--------+ swftr1 +--------+
+ | state1 | -------[swfstate2]->| state2 |
+ +--------+ | +--------+
+ | +--------+
+ `-[swfstate3]-->| state3 |
+ +--------+
"""
- self._init_wf_with_shared_state_or_tr()
- state3 = self.bmkwf.add_state(u'state3')
- tr1 = self.grpwf.add_transition(u'tr1', (self.state1,), self.state2)
- tr2 = self.bmkwf.add_transition(u'tr2', (self.state1,), state3)
- transitions = list(self.group.possible_transitions())
- self.assertEquals(len(transitions), 1)
- self.assertEquals(transitions[0].name, 'tr1')
- transitions = list(self.bookmark.possible_transitions())
- self.assertEquals(len(transitions), 1)
- self.assertEquals(transitions[0].name, 'tr2')
-
+ # sub-workflow
+ swf = add_wf(self, 'CWGroup', name='subworkflow')
+ swfstate1 = swf.add_state(u'swfstate1', initial=True)
+ swfstate2 = swf.add_state(u'swfstate2')
+ swfstate3 = swf.add_state(u'swfstate3')
+ tr1 = swf.add_transition(u'tr1', (swfstate1,), swfstate2)
+ tr2 = swf.add_transition(u'tr2', (swfstate1,), swfstate3)
+ # main workflow
+ mwf = add_wf(self, 'CWGroup', name='main workflow', default=True)
+ state1 = mwf.add_state(u'state1', initial=True)
+ state2 = mwf.add_state(u'state2')
+ state3 = mwf.add_state(u'state3')
+ swftr1 = mwf.add_wftransition(u'swftr1', swf, state1,
+ [(swfstate2, state2), (swfstate3, state3)])
+ self.assertEquals(swftr1.destination().eid, swfstate1.eid)
+ # workflows built, begin test
+ self.group = self.add_entity('CWGroup', name=u'grp1')
+ self.commit()
+ self.assertEquals(self.group.current_state.eid, state1.eid)
+ self.assertEquals(self.group.current_workflow.eid, mwf.eid)
+ self.assertEquals(self.group.main_workflow.eid, mwf.eid)
+ self.assertEquals(self.group.subworkflow_input_transition(), None)
+ self.group.fire_transition('swftr1', u'go')
+ self.commit()
+ self.group.clear_all_caches()
+ self.assertEquals(self.group.current_state.eid, swfstate1.eid)
+ self.assertEquals(self.group.current_workflow.eid, swf.eid)
+ self.assertEquals(self.group.main_workflow.eid, mwf.eid)
+ self.assertEquals(self.group.subworkflow_input_transition().eid, swftr1.eid)
+ self.group.fire_transition('tr1', u'go')
+ self.commit()
+ self.group.clear_all_caches()
+ self.assertEquals(self.group.current_state.eid, state2.eid)
+ self.assertEquals(self.group.current_workflow.eid, mwf.eid)
+ self.assertEquals(self.group.main_workflow.eid, mwf.eid)
+ self.assertEquals(self.group.subworkflow_input_transition(), None)
+ # force back to swfstate1 is impossible since we can't any more find
+ # subworkflow input transition
+ ex = self.assertRaises(ValidationError,
+ self.group.change_state, swfstate1, u'gadget')
+ self.assertEquals(ex.errors, {'to_state': "state doesn't belong to entity's current workflow"})
+ self.rollback()
+ # force back to state1
+ self.group.change_state('state1', u'gadget')
+ self.group.fire_transition('swftr1', u'au')
+ self.group.clear_all_caches()
+ self.group.fire_transition('tr2', u'chapeau')
+ self.commit()
+ self.group.clear_all_caches()
+ self.assertEquals(self.group.current_state.eid, state3.eid)
+ self.assertEquals(self.group.current_workflow.eid, mwf.eid)
+ self.assertEquals(self.group.main_workflow.eid, mwf.eid)
+ self.assertListEquals(parse_hist(self.group.workflow_history),
+ [('state1', 'swfstate1', 'swftr1', 'go'),
+ ('swfstate1', 'swfstate2', 'tr1', 'go'),
+ ('swfstate2', 'state2', 'swftr1', 'exiting from subworkflow subworkflow'),
+ ('state2', 'state1', None, 'gadget'),
+ ('state1', 'swfstate1', 'swftr1', 'au'),
+ ('swfstate1', 'swfstate3', 'tr2', 'chapeau'),
+ ('swfstate3', 'state3', 'swftr1', 'exiting from subworkflow subworkflow'),
+ ])
- def test_transitions_selection2(self):
- """
- ------------------------ tr1 (Bookmark) -----------------------
- | state1 (CWGroup, Bookmark) | -------------> | state2 (CWGroup,Bookmark) |
- ------------------------ -----------------------
- | tr2 (CWGroup) |
- `---------------------------------/
- """
- self._init_wf_with_shared_state_or_tr()
- self.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
- {'s': self.state2.eid, 'wf': self.bmkwf.eid})
- tr1 = self.bmkwf.add_transition(u'tr1', (self.state1,), self.state2)
- tr2 = self.grpwf.add_transition(u'tr2', (self.state1,), self.state2)
- transitions = list(self.group.possible_transitions())
- self.assertEquals(len(transitions), 1)
- self.assertEquals(transitions[0].name, 'tr2')
- transitions = list(self.bookmark.possible_transitions())
- self.assertEquals(len(transitions), 1)
- self.assertEquals(transitions[0].name, 'tr1')
+ def test_subworkflow_exit_consistency(self):
+ # sub-workflow
+ swf = add_wf(self, 'CWGroup', name='subworkflow')
+ swfstate1 = swf.add_state(u'swfstate1', initial=True)
+ swfstate2 = swf.add_state(u'swfstate2')
+ tr1 = swf.add_transition(u'tr1', (swfstate1,), swfstate2)
+ # main workflow
+ mwf = add_wf(self, 'CWGroup', name='main workflow', default=True)
+ state1 = mwf.add_state(u'state1', initial=True)
+ state2 = mwf.add_state(u'state2')
+ state3 = mwf.add_state(u'state3')
+ mwf.add_wftransition(u'swftr1', swf, state1,
+ [(swfstate2, state2), (swfstate2, state3)])
+ ex = self.assertRaises(ValidationError, self.commit)
+ self.assertEquals(ex.errors, {'subworkflow_exit': u"can't have multiple exits on the same state"})
class CustomWorkflowTC(CubicWebTC):
@@ -239,25 +296,6 @@
('deactivated', 'activated', 'activate', None),
('activated', 'asleep', None, 'workflow changed to "CWUser"')])
- def test_custom_wf_shared_state(self):
- """member in some state shared by the new workflow, nothing has to be
- done
- """
- self.member.fire_transition('deactivate')
- self.assertEquals(self.member.state, 'deactivated')
- wf = add_wf(self, 'CWUser')
- wf.add_state('asleep', initial=True)
- self.execute('SET S state_of WF WHERE S name "deactivated", WF eid %(wf)s',
- {'wf': wf.eid})
- self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
- {'wf': wf.eid, 'x': self.member.eid})
- self.commit()
- self.member.clear_all_caches()
- self.assertEquals(self.member.current_workflow.eid, wf.eid)
- self.assertEquals(self.member.state, 'deactivated')
- self.assertEquals(parse_hist(self.member.workflow_history),
- [('activated', 'deactivated', 'deactivate', None)])
-
def test_custom_wf_no_initial_state(self):
"""try to set a custom workflow which has no initial state"""
self.member.fire_transition('deactivate')
@@ -269,8 +307,7 @@
self.assertEquals(ex.errors, {'custom_workflow': u'workflow has no initial state'})
def test_custom_wf_bad_etype(self):
- """try to set a custom workflow which has no initial state"""
- self.member.fire_transition('deactivate')
+ """try to set a custom workflow which doesn't apply to entity type"""
wf = add_wf(self, 'Company')
wf.add_state('asleep', initial=True)
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
--- a/entities/wfobjs.py Fri Aug 21 16:26:20 2009 +0200
+++ b/entities/wfobjs.py Wed Aug 26 14:45:56 2009 +0200
@@ -16,6 +16,7 @@
from cubicweb.interfaces import IWorkflowable
from cubicweb.common.mixins import MI_REL_TRIGGERS
+class WorkflowException(Exception): pass
class Workflow(AnyEntity):
id = 'Workflow'
@@ -40,6 +41,23 @@
return self.workflow_of[0].rest_path(), {'vid': 'workflow'}
return super(Workflow, self).after_deletion_path()
+ def iter_workflows(self, _done=None):
+ """return an iterator on actual workflows, eg this workflow and its
+ subworkflows
+ """
+ # infinite loop safety belt
+ if _done is None:
+ _done = set()
+ yield self
+ _done.add(self.eid)
+ for tr in self.req.execute('Any T WHERE T is WorkflowTransition, '
+ 'T transition_of WF, WF eid %(wf)s',
+ {'wf': self.eid}).entities():
+ if tr.subwf.eid in _done:
+ continue
+ for subwf in tr.subwf.iter_workflows(_done):
+ yield subwf
+
# state / transitions accessors ############################################
def state_by_name(self, statename):
@@ -77,9 +95,7 @@
# wf construction methods ##################################################
def add_state(self, name, initial=False, **kwargs):
- """method to ease workflow definition: add a state for one or more
- entity type(s)
- """
+ """add a state to this workflow"""
state = self.req.create_entity('State', name=unicode(name), **kwargs)
self.req.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
{'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
@@ -90,27 +106,47 @@
{'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
return state
- def add_transition(self, name, fromstates, tostate,
- requiredgroups=(), conditions=(), **kwargs):
- """method to ease workflow definition: add a transition for one or more
- entity type(s), from one or more state and to a single state
- """
- tr = self.req.create_entity('Transition', name=unicode(name), **kwargs)
+ def _add_transition(self, trtype, name, fromstates,
+ requiredgroups=(), conditions=(), **kwargs):
+ tr = self.req.create_entity(trtype, name=unicode(name), **kwargs)
self.req.execute('SET T transition_of WF '
'WHERE T eid %(t)s, WF eid %(wf)s',
{'t': tr.eid, 'wf': self.eid}, ('t', 'wf'))
+ assert fromstates, fromstates
+ if not isinstance(fromstates, (tuple, list)):
+ fromstates = (fromstates,)
for state in fromstates:
if hasattr(state, 'eid'):
state = state.eid
self.req.execute('SET S allowed_transition T '
'WHERE S eid %(s)s, T eid %(t)s',
{'s': state, 't': tr.eid}, ('s', 't'))
+ tr.set_transition_permissions(requiredgroups, conditions, reset=False)
+ return tr
+
+ def add_transition(self, name, fromstates, tostate,
+ 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.req.execute('SET T destination_state S '
'WHERE S eid %(s)s, T eid %(t)s',
{'t': tr.eid, 's': tostate}, ('s', 't'))
- tr.set_transition_permissions(requiredgroups, conditions, reset=False)
+ return tr
+
+ 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.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
@@ -125,9 +161,18 @@
def __init__(self, *args, **kwargs):
if self.id == 'BaseTransition':
- raise Exception('should not be instantiated')
+ raise WorkflowException('should not be instantiated')
super(BaseTransition, self).__init__(*args, **kwargs)
+ @property
+ def workflow(self):
+ return self.transition_of[0]
+
+ def has_input_state(self, state):
+ if hasattr(state, 'eid'):
+ state = state.eid
+ return any(s for s in self.reverse_allowed_transition if s.eid == state)
+
def may_be_fired(self, eid):
"""return true if the logged user may fire this transition
@@ -170,7 +215,6 @@
self.req.execute('DELETE T condition R WHERE T eid %(x)s',
{'x': self.eid}, 'x')
for gname in requiredgroups:
- ### XXX ensure gname validity
rset = self.req.execute('SET T require_group G '
'WHERE T eid %(x)s, G name %(gn)s',
{'x': self.eid, 'gn': gname}, 'x')
@@ -194,11 +238,6 @@
def destination(self):
return self.destination_state[0]
- def has_input_state(self, state):
- if hasattr(state, 'eid'):
- state = state.eid
- return any(s for s in self.reverse_allowed_transition if s.eid == state)
-
class WorkflowTransition(BaseTransition):
"""customized class for WorkflowTransition entities"""
@@ -211,6 +250,50 @@
def destination(self):
return self.subwf.initial
+ def add_exit_point(self, fromstate, tostate):
+ if hasattr(fromstate, 'eid'):
+ fromstate = fromstate.eid
+ if hasattr(tostate, 'eid'):
+ tostate = tostate.eid
+ self.req.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 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.req.entity_from_eid(stateeid)
+ return None
+
+ @cached
+ def exit_points(self):
+ result = {}
+ for ep in self.subworkflow_exit:
+ result[ep.subwf_state.eid] = ep.destination.eid
+ return result
+
+ def clear_all_caches(self):
+ super(WorkflowableMixIn, self).clear_all_caches()
+ clear_cache(self, 'exit_points')
+
+
+class SubWorkflowExitPoint(AnyEntity):
+ """customized class for SubWorkflowExitPoint entities"""
+ id = 'SubWorkflowExitPoint'
+
+ @property
+ def subwf_state(self):
+ return self.subworkflow_state[0]
+
+ @property
+ def destination(self):
+ return self.destination_state[0]
+
class State(AnyEntity):
"""customized class for State entities"""
@@ -218,6 +301,10 @@
fetch_attrs, fetch_order = fetch_config(['name'])
rest_attr = 'eid'
+ @property
+ def workflow(self):
+ return self.state_of[0]
+
def after_deletion_path(self):
"""return (path, parameters) which should be used as redirect
information when this entity is being deleted
@@ -266,13 +353,18 @@
__implements__ = (IWorkflowable,)
@property
- def current_workflow(self):
+ def main_workflow(self):
"""return current workflow applied to this entity"""
if self.custom_workflow:
return self.custom_workflow[0]
return self.cwetype_workflow()
@property
+ def current_workflow(self):
+ """return current workflow applied to this entity"""
+ return self.current_state and self.current_state.workflow or self.main_workflow
+
+ @property
def current_state(self):
"""return current state entity"""
return self.in_state and self.in_state[0] or None
@@ -337,13 +429,21 @@
if tr.may_be_fired(self.eid):
yield tr
- def _get_tr_kwargs(self, comment, commentformat):
+ def _add_trinfo(self, comment, commentformat, treid=None, tseid=None):
kwargs = {}
if comment is not None:
kwargs['comment'] = comment
if commentformat is not None:
kwargs['comment_format'] = commentformat
- return kwargs
+ args = [('wf_info_for', 'E')]
+ kwargs['E'] = self.eid
+ if treid is not None:
+ args.append( ('by_transition', 'T') )
+ kwargs['T'] = treid
+ if tseid is not None:
+ args.append( ('to_state', 'S') )
+ kwargs['S'] = tseid
+ return self.req.create_entity('TrInfo', *args, **kwargs)
def fire_transition(self, trname, comment=None, commentformat=None):
"""change the entity's state by firing transition of the given name in
@@ -351,30 +451,51 @@
"""
assert self.current_workflow
tr = self.current_workflow.transition_by_name(trname)
- assert tr is not None, 'not a %s transition: %s' % (self.id, state)
- # XXX try to find matching transition?
- self.req.create_entity('TrInfo', ('by_transition', 'T'),
- ('wf_info_for', 'E'), T=tr.eid, E=self.eid,
- **self._get_tr_kwargs(comment, commentformat))
+ assert tr is not None, 'not a %s transition: %s' % (self.id, trname)
+ return self._add_trinfo(comment, commentformat, tr.eid)
- def change_state(self, statename, comment=None, commentformat=None):
- """change the entity's state to the state of the given name in entity's
- workflow. This method should only by used by manager to fix an entity's
- state when their is no matching transition, otherwise fire_transition
- should be used.
+ def change_state(self, statename, comment=None, commentformat=None, tr=None):
+ """change the entity's state to the given state (name or entity) in
+ entity's workflow. This method should only by used by manager to fix an
+ entity's state when their is no matching transition, otherwise
+ fire_transition should be used.
"""
assert self.current_workflow
- if not isinstance(statename, basestring):
- warn('give a state name')
- state = self.current_workflow.state_by_eid(statename)
- assert state is not None, 'not a %s state: %s' % (self.id, state)
+ if hasattr(statename, 'eid'):
+ stateeid = statename.eid
else:
- state = self.current_workflow.state_by_name(statename)
+ if not isinstance(statename, basestring):
+ warn('give a state name')
+ state = self.current_workflow.state_by_eid(statename)
+ else:
+ state = self.current_workflow.state_by_name(statename)
+ if state is None:
+ raise WorkflowException('not a %s state: %s' % (self.id,
+ statename))
+ stateeid = state.eid
# XXX try to find matching transition?
- self.req.create_entity('TrInfo', ('to_state', 'S'),
- ('wf_info_for', 'E'), S=state.eid, E=self.eid,
- **self._get_tr_kwargs(comment, commentformat))
+ 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
+ """
+ if self.main_workflow.eid == self.current_workflow.eid:
+ return # doesn't make sense
+ subwfentries = []
+ for trinfo in reversed(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):
+ # leave
+ del subwfentries[-1]
+ else:
+ # enter
+ subwfentries.append(trinfo)
+ if not subwfentries:
+ return None
+ return subwfentries[-1].transition
def clear_all_caches(self):
super(WorkflowableMixIn, self).clear_all_caches()
--- a/entity.py Fri Aug 21 16:26:20 2009 +0200
+++ b/entity.py Wed Aug 26 14:45:56 2009 +0200
@@ -209,6 +209,9 @@
def __hash__(self):
return id(self)
+ def __cmp__(self):
+ raise NotImplementedError('comparison not implemented for %s' % self.__class__)
+
def pre_add_hook(self):
"""hook called by the repository before doing anything to add the entity
(before_add entity hooks have not been called yet). This give the
--- a/goa/appobjects/components.py Fri Aug 21 16:26:20 2009 +0200
+++ b/goa/appobjects/components.py Wed Aug 26 14:45:56 2009 +0200
@@ -74,7 +74,7 @@
label = display_name(req, etype, 'plural')
view = self.vreg.select('views', 'list', req, req.etype_rset(etype))
url = view.url()
- etypelink = u' <a href="%s">%s</a>' % (xml_escape(url), label)
+ etypelink = u' <a href="%s">%s</a>' % (xml_escape(url), label)
yield (label, etypelink, self.add_entity_link(eschema, req))
ManageView.entity_types = entity_types_no_count
--- a/goa/appobjects/gauthservice.py Fri Aug 21 16:26:20 2009 +0200
+++ b/goa/appobjects/gauthservice.py Wed Aug 26 14:45:56 2009 +0200
@@ -17,7 +17,7 @@
def anon_user_link(self):
self.w(self.req._('anonymous'))
- self.w(u' [<a class="logout" href="%s">%s</a>]'
+ self.w(u' [<a class="logout" href="%s">%s</a>]'
% (users.create_login_url(self.req.url()), self.req._('login')))
class GAELogoutAction(LogoutAction):
--- a/hooks/notification.py Fri Aug 21 16:26:20 2009 +0200
+++ b/hooks/notification.py Wed Aug 26 14:45:56 2009 +0200
@@ -47,9 +47,10 @@
if view is None:
return
comment = entity.printable_value('comment', format='text/plain')
- if comment:
- comment = normalize_text(comment, 80,
- rest=entity.comment_format=='text/rest')
+ # XXX don't try to wrap rest until we've a proper transformation (see
+ # #103822)
+ if comment and entity.comment_format != 'text/rest':
+ comment = normalize_text(comment, 80)
RenderAndSendNotificationView(self._cw, view=view, viewargs={
'comment': comment, 'previous_state': entity.previous_state.name,
'current_state': entity.new_state.name})
--- a/i18n/en.po Fri Aug 21 16:26:20 2009 +0200
+++ b/i18n/en.po Wed Aug 26 14:45:56 2009 +0200
@@ -75,31 +75,31 @@
msgstr ""
#, python-format
-msgid "%d days"
+msgid "%d days"
msgstr ""
#, python-format
-msgid "%d hours"
+msgid "%d hours"
msgstr ""
#, python-format
-msgid "%d minutes"
+msgid "%d minutes"
msgstr ""
#, python-format
-msgid "%d months"
+msgid "%d months"
msgstr ""
#, python-format
-msgid "%d seconds"
+msgid "%d seconds"
msgstr ""
#, python-format
-msgid "%d weeks"
+msgid "%d weeks"
msgstr ""
#, python-format
-msgid "%d years"
+msgid "%d years"
msgstr ""
#, python-format
--- a/i18n/es.po Fri Aug 21 16:26:20 2009 +0200
+++ b/i18n/es.po Wed Aug 26 14:45:56 2009 +0200
@@ -80,32 +80,32 @@
msgstr "%d años"
#, python-format
-msgid "%d days"
-msgstr "%d días"
+msgid "%d days"
+msgstr "%d días"
#, python-format
-msgid "%d hours"
-msgstr "%d horas"
+msgid "%d hours"
+msgstr "%d horas"
#, python-format
-msgid "%d minutes"
-msgstr "%d minutos"
+msgid "%d minutes"
+msgstr "%d minutos"
#, python-format
-msgid "%d months"
-msgstr "%d meses"
+msgid "%d months"
+msgstr "%d meses"
#, python-format
-msgid "%d seconds"
-msgstr "%d segundos"
+msgid "%d seconds"
+msgstr "%d segundos"
#, python-format
-msgid "%d weeks"
-msgstr "%d semanas"
+msgid "%d weeks"
+msgstr "%d semanas"
#, python-format
-msgid "%d years"
-msgstr "%d años"
+msgid "%d years"
+msgstr "%d años"
#, python-format
msgid "%s error report"
--- a/i18n/fr.po Fri Aug 21 16:26:20 2009 +0200
+++ b/i18n/fr.po Wed Aug 26 14:45:56 2009 +0200
@@ -80,32 +80,32 @@
msgstr "%d années"
#, python-format
-msgid "%d days"
-msgstr "%d jours"
+msgid "%d days"
+msgstr "%d jours"
#, python-format
-msgid "%d hours"
-msgstr "%d heures"
+msgid "%d hours"
+msgstr "%d heures"
#, python-format
-msgid "%d minutes"
-msgstr "%d minutes"
+msgid "%d minutes"
+msgstr "%d minutes"
#, python-format
-msgid "%d months"
-msgstr "%d mois"
+msgid "%d months"
+msgstr "%d mois"
#, python-format
-msgid "%d seconds"
-msgstr "%d secondes"
+msgid "%d seconds"
+msgstr "%d secondes"
#, python-format
-msgid "%d weeks"
-msgstr "%d semaines"
+msgid "%d weeks"
+msgstr "%d semaines"
#, python-format
-msgid "%d years"
-msgstr "%d années"
+msgid "%d years"
+msgstr "%d années"
#, python-format
msgid "%s error report"
--- a/misc/migration/postcreate.py Fri Aug 21 16:26:20 2009 +0200
+++ b/misc/migration/postcreate.py Wed Aug 26 14:45:56 2009 +0200
@@ -6,14 +6,30 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-activatedeid = add_state(_('activated'), 'CWUser', initial=True)
-deactivatedeid = add_state(_('deactivated'), 'CWUser')
-add_transition(_('deactivate'), 'CWUser',
- (activatedeid,), deactivatedeid,
- requiredgroups=('managers',))
-add_transition(_('activate'), 'CWUser',
- (deactivatedeid,), activatedeid,
- requiredgroups=('managers',))
+# insert versions
+create_entity('CWProperty', pkey=u'system.version.cubicweb',
+ value=unicode(config.cubicweb_version()))
+for cube in config.cubes():
+ create_entity('CWProperty', pkey=u'system.version.%s' % cube.lower(),
+ value=unicode(config.cube_version(cube)))
+
+# some entities have been added before schema entities, fix the 'is' and
+# 'is_instance_of' relations
+for rtype in ('is', 'is_instance_of'):
+ sql('INSERT INTO %s_relation '
+ 'SELECT X.eid, ET.cw_eid FROM entities as X, cw_CWEType as ET '
+ 'WHERE X.type=ET.cw_name AND NOT EXISTS('
+ ' SELECT 1 from is_relation '
+ ' WHERE eid_from=X.eid AND eid_to=ET.cw_eid)' % rtype)
+
+# user workflow
+userwf = add_workflow(_('default user workflow'), 'CWUser')
+activated = userwf.add_state(_('activated'), initial=True)
+deactivated = userwf.add_state(_('deactivated'))
+userwf.add_transition(_('deactivate'), (activated,), deactivated,
+ requiredgroups=('managers',))
+userwf.add_transition(_('activate'), (deactivated,), activated,
+ requiredgroups=('managers',))
# create anonymous user if all-in-one config and anonymous user has been specified
if hasattr(config, 'anonymous_user'):
@@ -26,23 +42,23 @@
# need this since we already have at least one user in the database (the default admin)
for user in rql('Any X WHERE X is CWUser').entities():
session.unsafe_execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
- {'x': user.eid, 's': activatedeid}, 'x')
-
-cfg = config.persistent_options_configuration()
-if interactive_mode:
- cfg.input_config(inputlevel=0)
+ {'x': user.eid, 's': activated.eid}, 'x')
-for section, options in cfg.options_by_section():
- for optname, optdict, value in options:
- key = '%s.%s' % (section, optname)
- default = cfg.option_default(optname, optdict)
- # only record values differing from default
- if value != default:
- rql('INSERT CWProperty X: X pkey %(k)s, X value %(v)s', {'k': key, 'v': value})
+# on interactive mode, ask for level 0 persistent options
+if interactive_mode:
+ cfg = config.persistent_options_configuration()
+ cfg.input_config(inputlevel=0)
+ for section, options in cfg.options_by_section():
+ for optname, optdict, value in options:
+ key = '%s.%s' % (section, optname)
+ default = cfg.option_default(optname, optdict)
+ # only record values differing from default
+ if value != default:
+ rql('INSERT CWProperty X: X pkey %(k)s, X value %(v)s', {'k': key, 'v': value})
# add PERM_USE_TEMPLATE_FORMAT permission
from cubicweb.schema import PERM_USE_TEMPLATE_FORMAT
-eid = add_entity('CWPermission', name=PERM_USE_TEMPLATE_FORMAT,
- label=_('use template languages'))
+usetmplperm = create_entity('CWPermission', name=PERM_USE_TEMPLATE_FORMAT,
+ label=_('use template languages'))
rql('SET X require_group G WHERE G name "managers", X eid %(x)s',
- {'x': eid}, 'x')
+ {'x': usetmplperm.eid}, 'x')
--- a/rset.py Fri Aug 21 16:26:20 2009 +0200
+++ b/rset.py Wed Aug 26 14:45:56 2009 +0200
@@ -447,7 +447,7 @@
if rqlst.TYPE == 'select':
# UNION query, find the subquery from which this entity has been
# found
- rqlst = rqlst.locate_subquery(col, etype, self.args)
+ rqlst, col = rqlst.locate_subquery(col, etype, self.args)
# take care, due to outer join support, we may find None
# values for non final relation
for i, attr, x in attr_desc_iterator(rqlst, col):
@@ -547,7 +547,8 @@
if len(self.column_types(i)) > 1:
break
# UNION query, find the subquery from which this entity has been found
- select = rqlst.locate_subquery(locate_query_col, etype, self.args)
+ select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0]
+ col = rqlst.subquery_selection_index(select, col)
try:
myvar = select.selection[col].variable
except AttributeError:
@@ -555,7 +556,7 @@
return None, None
rel = myvar.main_relation()
if rel is not None:
- index = rel.children[0].variable.selected_index()
+ index = rel.children[0].root_selection_index()
if index is not None and self.rows[row][index]:
return self.get_entity(row, index), rel.r_type
return None, None
--- a/schemas/workflow.py Fri Aug 21 16:26:20 2009 +0200
+++ b/schemas/workflow.py Wed Aug 26 14:45:56 2009 +0200
@@ -58,7 +58,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='+*',
+ state_of = SubjectRelation('Workflow', cardinality='1*',
description=_('workflow to which this state belongs'),
constraints=[RQLUniqueConstraint('S name N, Y state_of O, Y name N')])
@@ -81,7 +81,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='+*',
+ transition_of = SubjectRelation('Workflow', cardinality='1*',
description=_('workflow to which this transition belongs'),
constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N')])
--- a/schemaviewer.py Fri Aug 21 16:26:20 2009 +0200
+++ b/schemaviewer.py Wed Aug 26 14:45:56 2009 +0200
@@ -104,7 +104,7 @@
"""get a layout for an entity schema"""
etype = eschema.type
layout = Section(children=' ', klass='clear')
- layout.append(Link(etype,' ' , id=etype)) # anchor
+ layout.append(Link(etype,' ' , id=etype)) # anchor
title = Link(self.eschema_link_url(eschema), etype)
boxchild = [Section(children=(title, ' (%s)'% eschema.display_name(self.req)), klass='title')]
table = Table(cols=4, rheaders=1,
--- a/server/__init__.py Fri Aug 21 16:26:20 2009 +0200
+++ b/server/__init__.py Wed Aug 26 14:45:56 2009 +0200
@@ -180,22 +180,6 @@
handler = config.migration_handler(schema, interactive=False,
cnx=cnx, repo=repo)
initialize_schema(config, schema, handler)
- # insert versions
- handler.cmd_add_entity('CWProperty', pkey=u'system.version.cubicweb',
- value=unicode(config.cubicweb_version()))
- for cube in config.cubes():
- handler.cmd_add_entity('CWProperty',
- pkey=u'system.version.%s' % cube.lower(),
- value=unicode(config.cube_version(cube)))
- # some entities have been added before schema entities, fix the 'is' and
- # 'is_instance_of' relations
- for rtype in ('is', 'is_instance_of'):
- handler.sqlexec(
- 'INSERT INTO %s_relation '
- 'SELECT X.eid, ET.cw_eid FROM entities as X, cw_CWEType as ET '
- 'WHERE X.type=ET.cw_name AND NOT EXISTS('
- ' SELECT 1 from is_relation '
- ' WHERE eid_from=X.eid AND eid_to=ET.cw_eid)' % rtype)
# yoo !
cnx.commit()
config.enabled_sources = None
--- a/server/migractions.py Fri Aug 21 16:26:20 2009 +0200
+++ b/server/migractions.py Wed Aug 26 14:45:56 2009 +0200
@@ -1022,7 +1022,7 @@
if commit:
self.commit()
- @deprecated('use entity.change_state("state")')
+ @deprecated('use entity.fire_transition("transition") or entity.change_state("state")')
def cmd_set_state(self, eid, statename, commit=False):
self.session.set_pool() # ensure pool is set
self.session.entity_from_eid(eid).change_state(statename)
--- a/server/serverctl.py Fri Aug 21 16:26:20 2009 +0200
+++ b/server/serverctl.py Wed Aug 26 14:45:56 2009 +0200
@@ -257,12 +257,19 @@
'help': 'verbose mode: will ask all possible configuration questions',
}
),
+ ('automatic',
+ {'short': 'a', 'type' : 'yn', 'metavar': '<auto>',
+ 'default': 'n',
+ 'help': 'automatic mode: never ask and use default answer to every question',
+ }
+ ),
)
def run(self, args):
"""run the command with its specific arguments"""
from logilab.common.adbh import get_adv_func_helper
from indexer import get_indexer
verbose = self.get('verbose')
+ automatic = self.get('automatic')
appid = pop_arg(args, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
create_db = self.config.create_db
@@ -277,13 +284,13 @@
try:
if helper.users_support:
user = source['db-user']
- if not helper.user_exists(cursor, user) and \
- ASK.confirm('Create db user %s ?' % user, default_is_yes=False):
+ if not helper.user_exists(cursor, user) and (automatic or \
+ ASK.confirm('Create db user %s ?' % user, default_is_yes=False)):
helper.create_user(source['db-user'], source['db-password'])
print '-> user %s created.' % user
dbname = source['db-name']
if dbname in helper.list_databases(cursor):
- if ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
+ if automatic or ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
cursor.execute('DROP DATABASE %s' % dbname)
else:
return
@@ -311,7 +318,7 @@
cnx.commit()
print '-> database for instance %s created and necessary extensions installed.' % appid
print
- if ASK.confirm('Run db-init to initialize the system database ?'):
+ if automatic or ASK.confirm('Run db-init to initialize the system database ?'):
cmd_run('db-init', config.appid)
else:
print ('-> nevermind, you can do it later with '
--- a/server/sources/pyrorql.py Fri Aug 21 16:26:20 2009 +0200
+++ b/server/sources/pyrorql.py Wed Aug 26 14:45:56 2009 +0200
@@ -26,6 +26,12 @@
from cubicweb.server.sources import (AbstractSource, ConnectionWrapper,
TimedCache, dbg_st_search, dbg_results)
+
+def uidtype(union, col, etype, args):
+ select, col = union.locate_subquery(col, etype, args)
+ return getattr(select.selection[col], 'uidtype', None)
+
+
class ReplaceByInOperator(Exception):
def __init__(self, eids):
self.eids = eids
@@ -295,8 +301,8 @@
needtranslation = []
rows = rset.rows
for i, etype in enumerate(descr[0]):
- if (etype is None or not self.schema.eschema(etype).is_final() or
- getattr(union.locate_subquery(i, etype, args).selection[i], 'uidtype', None)):
+ if (etype is None or not self.schema.eschema(etype).is_final()
+ or uidtype(union, i, etype, args)):
needtranslation.append(i)
if needtranslation:
cnx = session.pool.connection(self.uri)
--- a/sobjects/notification.py Fri Aug 21 16:26:20 2009 +0200
+++ b/sobjects/notification.py Wed Aug 26 14:45:56 2009 +0200
@@ -104,8 +104,12 @@
entity = self.rset.get_entity(self.row or 0, self.col or 0)
content = entity.printable_value(self.content_attr, format='text/plain')
if content:
- contentformat = getattr(entity, self.content_attr + '_format', 'text/rest')
- content = normalize_text(content, 80, rest=contentformat=='text/rest')
+ contentformat = getattr(entity, self.content_attr + '_format',
+ 'text/rest')
+ # XXX don't try to wrap rest until we've a proper transformation (see
+ # #103822)
+ if contentformat != 'text/rest':
+ content = normalize_text(content, 80)
return super(ContentAddedView, self).context(content=content, **kwargs)
def subject(self):
--- a/test/unittest_rset.py Fri Aug 21 16:26:20 2009 +0200
+++ b/test/unittest_rset.py Wed Aug 26 14:45:56 2009 +0200
@@ -328,6 +328,7 @@
entity, rtype = rset.related_entity(1, 1)
self.assertEquals(entity.id, 'CWGroup')
self.assertEquals(rtype, 'name')
+ #
rset = self.execute('Any X,N ORDERBY N WHERE X is Bookmark WITH X,N BEING '
'((Any X,N WHERE X is CWGroup, X name N)'
' UNION '
@@ -335,6 +336,14 @@
entity, rtype = rset.related_entity(0, 1)
self.assertEquals(entity.eid, e.eid)
self.assertEquals(rtype, 'title')
+ #
+ rset = self.execute('Any X,N ORDERBY N WITH N,X BEING '
+ '((Any N,X WHERE X is CWGroup, X name N)'
+ ' UNION '
+ ' (Any N,X WHERE X is Bookmark, X title N))')
+ entity, rtype = rset.related_entity(0, 1)
+ self.assertEquals(entity.eid, e.eid)
+ self.assertEquals(rtype, 'title')
def test_entities(self):
rset = self.execute('Any U,G WHERE U in_group G')
--- a/view.py Fri Aug 21 16:26:20 2009 +0200
+++ b/view.py Wed Aug 26 14:45:56 2009 +0200
@@ -474,8 +474,11 @@
__registry__ = 'components'
__select__ = yes()
+ # XXX huummm, much probably useless
+ htmlclass = 'mainRelated'
def div_class(self):
- return '%s %s' % (self.cw_propval('htmlclass'), self.id)
+ return '%s %s' % (self.htmlclass, self.id)
+ # XXX a generic '%s%s' % (self.id, self.__registry__.capitalize()) would probably be nicer
def div_id(self):
return '%sComponent' % self.id
--- a/web/component.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/component.py Wed Aug 26 14:45:56 2009 +0200
@@ -8,6 +8,8 @@
__docformat__ = "restructuredtext en"
_ = unicode
+from simplejson import dumps
+
from logilab.common.deprecation import class_renamed
from logilab.mtconverter import xml_escape
@@ -43,8 +45,6 @@
_('navcontenttop'), _('navcontentbottom')),
#vocabulary=(_('header'), _('incontext'), _('footer')),
help=_('context where this component should be displayed')),
- _('htmlclass'):dict(type='String', default='mainRelated',
- help=_('html class of the component')),
}
context = 'navcontentbottom' # 'footer' | 'header' | 'incontext'
@@ -72,7 +72,8 @@
page_link_templ = u'<span class="slice"><a href="%s" title="%s">%s</a></span>'
selected_page_link_templ = u'<span class="selectedSlice"><a href="%s" title="%s">%s</a></span>'
previous_page_link_templ = next_page_link_templ = page_link_templ
- no_previous_page_link = no_next_page_link = u''
+ no_previous_page_link = u'<<'
+ no_next_page_link = u'>>'
def __init__(self, req, rset, **kwargs):
super(NavigationComponent, self).__init__(req, rset=rset, **kwargs)
@@ -112,33 +113,40 @@
if self.stop_param in params:
del params[self.stop_param]
+ def page_url(self, path, params, start, stop):
+ params = merge_dicts(params, {self.start_param : start,
+ self.stop_param : stop,})
+ if path == 'json':
+ rql = params.pop('rql', self.rset.printable_rql())
+ # latest 'true' used for 'swap' mode
+ url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
+ dumps(params.get('divid', 'paginated-content')),
+ dumps(rql), dumps(params.pop('vid', None)), dumps(params))
+ else:
+ url = self.build_url(path, **params)
+ return url
+
def page_link(self, path, params, start, stop, content):
- url = self.build_url(path, **merge_dicts(params, {self.start_param : start,
- self.stop_param : stop,}))
- url = xml_escape(url)
+ url = xml_escape(self.page_url(path, params, start, stop))
if start == self.starting_from:
return self.selected_page_link_templ % (url, content, content)
return self.page_link_templ % (url, content, content)
- def previous_link(self, params, content='<<', title=_('previous_results')):
+ def previous_link(self, path, params, content='<<', title=_('previous_results')):
start = self.starting_from
if not start :
return self.no_previous_page_link
start = max(0, start - self.page_size)
stop = start + self.page_size - 1
- url = self.build_url(**merge_dicts(params, {self.start_param : start,
- self.stop_param : stop,}))
- url = xml_escape(url)
+ url = xml_escape(self.page_url(path, params, start, stop))
return self.previous_page_link_templ % (url, title, content)
- def next_link(self, params, content='>>', title=_('next_results')):
+ def next_link(self, path, params, content='>>', title=_('next_results')):
start = self.starting_from + self.page_size
if start >= self.total:
return self.no_next_page_link
stop = start + self.page_size - 1
- url = self.build_url(**merge_dicts(params, {self.start_param : start,
- self.stop_param : stop,}))
- url = xml_escape(url)
+ url = xml_escape(self.page_url(path, params, start, stop))
return self.next_page_link_templ % (url, title, content)
--- a/web/data/cubicweb.ajax.js Fri Aug 21 16:26:20 2009 +0200
+++ b/web/data/cubicweb.ajax.js Wed Aug 26 14:45:56 2009 +0200
@@ -66,7 +66,16 @@
jQuery(CubicWeb).trigger('ajax-loaded');
}
-// cubicweb loadxhtml plugin to make jquery handle xhtml response
+/* cubicweb loadxhtml plugin to make jquery handle xhtml response
+ *
+ * fetches `url` and replaces this's content with the result
+ *
+ * @param mode how the replacement should be done (default is 'replace')
+ * Possible values are :
+ * - 'replace' to replace the node's content with the generated HTML
+ * - 'swap' to replace the node itself with the generated HTML
+ * - 'append' to append the generated HTML to the node's content
+ */
jQuery.fn.loadxhtml = function(url, data, reqtype, mode) {
var ajax = null;
if (reqtype == 'post') {
@@ -323,7 +332,7 @@
}
}
-/*
+/* XXX deprecates?
* fetches `url` and replaces `nodeid`'s content with the result
* @param replacemode how the replacement should be done (default is 'replace')
* Possible values are :
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/cubicweb.facets.js Wed Aug 26 14:45:56 2009 +0200
@@ -0,0 +1,227 @@
+/*
+ * :organization: Logilab
+ * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+ */
+
+CubicWeb.require('htmlhelpers.js');
+CubicWeb.require('ajax.js');
+
+//============= filter form functions ========================================//
+function copyParam(origparams, newparams, param) {
+ var index = findValue(origparams[0], param);
+ if (index > -1) {
+ newparams[param] = origparams[1][index];
+ }
+}
+
+function facetFormContent(form) {
+ var names = [];
+ var values = [];
+ jQuery(form).find('.facet').each(function () {
+ var facetName = jQuery(this).find('.facetTitle').attr('cubicweb:facetName');
+ var facetValues = jQuery(this).find('.facetValueSelected').each(function(x) {
+ names.push(facetName);
+ values.push(this.getAttribute('cubicweb:value'));
+ });
+ });
+ jQuery(form).find('input').each(function () {
+ names.push(this.name);
+ values.push(this.value);
+ });
+ jQuery(form).find('select option[selected]').each(function () {
+ names.push(this.parentNode.name);
+ values.push(this.value);
+ });
+ return [names, values];
+}
+
+function buildRQL(divid, vid, paginate, vidargs) {
+ jQuery(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
+ var form = getNode(divid+'Form');
+ var zipped = facetFormContent(form);
+ zipped[0].push('facetargs');
+ zipped[1].push(vidargs);
+ var d = asyncRemoteExec('filter_build_rql', zipped[0], zipped[1]);
+ d.addCallback(function(result) {
+ var rql = result[0];
+ var $bkLink = jQuery('#facetBkLink');
+ if ($bkLink.length) {
+ var bkUrl = $bkLink.attr('cubicweb:target') + '&path=view?rql=' + rql;
+ if (vid) {
+ bkUrl += '&vid=' + vid;
+ }
+ $bkLink.attr('href', bkUrl);
+ }
+ var toupdate = result[1];
+ var extraparams = vidargs;
+ var displayactions = jQuery('#' + divid).attr('cubicweb:displayactions');
+ if (displayactions) { extraparams['displayactions'] = displayactions; }
+ if (paginate) { extraparams['paginate'] = '1'; }
+ // copy some parameters
+ // XXX cleanup vid/divid mess
+ // if vid argument is specified , the one specified in form params will
+ // be overriden by replacePageChunk
+ copyParam(zipped, extraparams, 'vid');
+ extraparams['divid'] = divid;
+ copyParam(zipped, extraparams, 'divid');
+ copyParam(zipped, extraparams, 'subvid');
+ // paginate used to know if the filter box is acting, in which case we
+ // want to reload action box to match current selection (we don't want
+ // this from a table filter)
+ replacePageChunk(divid, rql, vid, extraparams, true, function() {
+ jQuery(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
+ });
+ if (paginate) {
+ // FIXME the edit box might not be displayed in which case we don't
+ // know where to put the potential new one, just skip this case
+ // for now
+ if (jQuery('#edit_box').length) {
+ reloadComponent('edit_box', rql, 'boxes', 'edit_box');
+ }
+ if (jQuery('#breadcrumbs').length) {
+ reloadComponent('breadcrumbs', rql, 'components', 'breadcrumbs');
+ }
+ }
+ var d = asyncRemoteExec('filter_select_content', toupdate, rql);
+ d.addCallback(function(updateMap) {
+ for (facetId in updateMap) {
+ var values = updateMap[facetId];
+ jqNode(facetId).find('.facetCheckBox').each(function () {
+ var value = this.getAttribute('cubicweb:value');
+ if (!values.contains(value)) {
+ if (!jQuery(this).hasClass('facetValueDisabled')) {
+ jQuery(this).addClass('facetValueDisabled');
+ }
+ } else {
+ if (jQuery(this).hasClass('facetValueDisabled')) {
+ jQuery(this).removeClass('facetValueDisabled');
+ }
+ }
+ });
+ }
+ });
+ });
+}
+
+
+var SELECTED_IMG = baseuri()+"data/black-check.png";
+var UNSELECTED_IMG = baseuri()+"data/no-check-no-border.png";
+var UNSELECTED_BORDER_IMG = baseuri()+"data/black-uncheck.png";
+
+function initFacetBoxEvents(root) {
+ // facetargs : (divid, vid, paginate, extraargs)
+ root = root || document;
+ jQuery(root).find('form').each(function () {
+ var form = jQuery(this);
+ // NOTE: don't evaluate facetargs here but in callbacks since its value
+ // may changes and we must send its value when the callback is
+ // called, not when the page is initialized
+ var facetargs = form.attr('cubicweb:facetargs');
+ if (facetargs !== undefined) {
+ form.submit(function() {
+ buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
+ return false;
+ });
+ form.find('div.facet').each(function() {
+ var facet = jQuery(this);
+ facet.find('div.facetCheckBox').each(function (i) {
+ this.setAttribute('cubicweb:idx', i);
+ });
+ facet.find('div.facetCheckBox').click(function () {
+ var $this = jQuery(this);
+ if ($this.hasClass('facetValueDisabled')){
+ return
+ }
+ if ($this.hasClass('facetValueSelected')) {
+ $this.removeClass('facetValueSelected');
+ $this.find('img').each(function (i){
+ if (this.getAttribute('cubicweb:unselimg')){
+ this.setAttribute('src', UNSELECTED_BORDER_IMG);
+ this.setAttribute('alt', (_('not selected')));
+ }
+ else{
+ this.setAttribute('src', UNSELECTED_IMG);
+ this.setAttribute('alt', (_('not selected')));
+ }
+ });
+ var index = parseInt($this.attr('cubicweb:idx'));
+ // we dont need to move the element when cubicweb:idx == 0
+ if (index > 0){
+ var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) {
+ var nindex = parseInt(n.getAttribute('cubicweb:idx'));
+ return nindex > index;
+ }).length;
+ index += shift;
+ var parent = this.parentNode;
+ var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
+ if ( ! ($insertAfter.length == 1 && shift == 0) ) {
+ // only rearrange element if necessary
+ $insertAfter.after(this);
+ }
+ }
+ } else {
+ var lastSelected = facet.find('.facetValueSelected:last');
+ if (lastSelected.length) {
+ lastSelected.after(this);
+ } else {
+ var parent = this.parentNode;
+ jQuery(parent).prepend(this);
+ }
+ jQuery(this).addClass('facetValueSelected');
+ var $img = jQuery(this).find('img');
+ $img.attr('src', SELECTED_IMG).attr('alt', (_('selected')));
+ }
+ buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
+ facet.find('.facetBody').animate({scrollTop: 0}, '');
+ });
+ facet.find('select.facetOperator').change(function() {
+ var nbselected = facet.find('div.facetValueSelected').length;
+ if (nbselected >= 2) {
+ buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
+ }
+ });
+ facet.find('div.facetTitle').click(function() {
+ facet.find('div.facetBody').toggleClass('hidden').toggleClass('opened');
+ jQuery(this).toggleClass('opened');
+ });
+
+ });
+ }
+ });
+}
+
+// trigger this function on document ready event if you provide some kind of
+// persistent search (eg crih)
+function reorderFacetsItems(root){
+ root = root || document;
+ jQuery(root).find('form').each(function () {
+ var form = jQuery(this);
+ if (form.attr('cubicweb:facetargs')) {
+ form.find('div.facet').each(function() {
+ var facet = jQuery(this);
+ var lastSelected = null;
+ facet.find('div.facetCheckBox').each(function (i) {
+ var $this = jQuery(this);
+ if ($this.hasClass('facetValueSelected')) {
+ if (lastSelected) {
+ lastSelected.after(this);
+ } else {
+ var parent = this.parentNode;
+ jQuery(parent).prepend(this);
+ }
+ lastSelected = $this;
+ }
+ });
+ });
+ }
+ });
+}
+
+// we need to differenciate cases where initFacetBoxEvents is called
+// with one argument or without any argument. If we use `initFacetBoxEvents`
+// as the direct callback on the jQuery.ready event, jQuery will pass some argument
+// of his, so we use this small anonymous function instead.
+jQuery(document).ready(function() {initFacetBoxEvents();});
+
+CubicWeb.provide('facets.js');
--- a/web/data/cubicweb.form.css Fri Aug 21 16:26:20 2009 +0200
+++ b/web/data/cubicweb.form.css Wed Aug 26 14:45:56 2009 +0200
@@ -78,7 +78,7 @@
table.attributeForm th,
table.attributeForm td {
- padding : .7em 2px;
+ padding : .2em 2px;
}
table.attributeForm th {
--- a/web/data/cubicweb.formfilter.js Fri Aug 21 16:26:20 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-/*
- * :organization: Logilab
- * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
- * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
- */
-
-CubicWeb.require('htmlhelpers.js');
-CubicWeb.require('ajax.js');
-
-//============= filter form functions ========================================//
-function copyParam(origparams, newparams, param) {
- var index = findValue(origparams[0], param);
- if (index > -1) {
- newparams[param] = origparams[1][index];
- }
-}
-
-function facetFormContent(form) {
- var names = [];
- var values = [];
- jQuery(form).find('.facet').each(function () {
- var facetName = jQuery(this).find('.facetTitle').attr('cubicweb:facetName');
- var facetValues = jQuery(this).find('.facetValueSelected').each(function(x) {
- names.push(facetName);
- values.push(this.getAttribute('cubicweb:value'));
- });
- });
- jQuery(form).find('input').each(function () {
- names.push(this.name);
- values.push(this.value);
- });
- jQuery(form).find('select option[selected]').each(function () {
- names.push(this.parentNode.name);
- values.push(this.value);
- });
- return [names, values];
-}
-
-function buildRQL(divid, vid, paginate, vidargs) {
- jQuery(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
- var form = getNode(divid+'Form');
- var zipped = facetFormContent(form);
- zipped[0].push('facetargs');
- zipped[1].push(vidargs);
- var d = asyncRemoteExec('filter_build_rql', zipped[0], zipped[1]);
- d.addCallback(function(result) {
- var rql = result[0];
- var $bkLink = jQuery('#facetBkLink');
- if ($bkLink.length) {
- var bkUrl = $bkLink.attr('cubicweb:target') + '&path=view?rql=' + rql;
- if (vid) {
- bkUrl += '&vid=' + vid;
- }
- $bkLink.attr('href', bkUrl);
- }
- var toupdate = result[1];
- var extraparams = vidargs;
- var displayactions = jQuery('#' + divid).attr('cubicweb:displayactions');
- if (displayactions) { extraparams['displayactions'] = displayactions; }
- if (paginate) { extraparams['paginate'] = '1'; }
- // copy some parameters
- // XXX cleanup vid/divid mess
- // if vid argument is specified , the one specified in form params will
- // be overriden by replacePageChunk
- copyParam(zipped, extraparams, 'vid');
- extraparams['divid'] = divid;
- copyParam(zipped, extraparams, 'divid');
- copyParam(zipped, extraparams, 'subvid');
- // paginate used to know if the filter box is acting, in which case we
- // want to reload action box to match current selection
- replacePageChunk(divid, rql, vid, extraparams, true, function() {
- jQuery(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
- });
- if (paginate) {
- // FIXME the edit box might not be displayed in which case we don't
- // know where to put the potential new one, just skip this case
- // for now
- if (jQuery('#edit_box').length) {
- reloadComponent('edit_box', rql, 'boxes', 'edit_box');
- }
- }
- var d = asyncRemoteExec('filter_select_content', toupdate, rql);
- d.addCallback(function(updateMap) {
- for (facetId in updateMap) {
- var values = updateMap[facetId];
- jqNode(facetId).find('.facetCheckBox').each(function () {
- var value = this.getAttribute('cubicweb:value');
- if (!values.contains(value)) {
- if (!jQuery(this).hasClass('facetValueDisabled')) {
- jQuery(this).addClass('facetValueDisabled');
- }
- } else {
- if (jQuery(this).hasClass('facetValueDisabled')) {
- jQuery(this).removeClass('facetValueDisabled');
- }
- }
- });
- }
- });
- });
-}
-
-
-var SELECTED_IMG = baseuri()+"data/black-check.png";
-var UNSELECTED_IMG = baseuri()+"data/no-check-no-border.png";
-var UNSELECTED_BORDER_IMG = baseuri()+"data/black-uncheck.png";
-
-function initFacetBoxEvents(root) {
- // facetargs : (divid, vid, paginate, extraargs)
- root = root || document;
- jQuery(root).find('form').each(function () {
- var form = jQuery(this);
- // NOTE: don't evaluate facetargs here but in callbacks since its value
- // may changes and we must send its value when the callback is
- // called, not when the page is initialized
- var facetargs = form.attr('cubicweb:facetargs');
- if (facetargs !== undefined) {
- form.submit(function() {
- buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
- return false;
- });
- form.find('div.facet').each(function() {
- var facet = jQuery(this);
- facet.find('div.facetCheckBox').each(function (i) {
- this.setAttribute('cubicweb:idx', i);
- });
- facet.find('div.facetCheckBox').click(function () {
- var $this = jQuery(this);
- if ($this.hasClass('facetValueDisabled')){
- return
- }
- if ($this.hasClass('facetValueSelected')) {
- $this.removeClass('facetValueSelected');
- $this.find('img').each(function (i){
- if (this.getAttribute('cubicweb:unselimg')){
- this.setAttribute('src', UNSELECTED_BORDER_IMG);
- this.setAttribute('alt', (_('not selected')));
- }
- else{
- this.setAttribute('src', UNSELECTED_IMG);
- this.setAttribute('alt', (_('not selected')));
- }
- });
- var index = parseInt($this.attr('cubicweb:idx'));
- // we dont need to move the element when cubicweb:idx == 0
- if (index > 0){
- var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) {
- var nindex = parseInt(n.getAttribute('cubicweb:idx'));
- return nindex > index;
- }).length;
- index += shift;
- var parent = this.parentNode;
- var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
- if ( ! ($insertAfter.length == 1 && shift == 0) ) {
- // only rearrange element if necessary
- $insertAfter.after(this);
- }
- }
- } else {
- var lastSelected = facet.find('.facetValueSelected:last');
- if (lastSelected.length) {
- lastSelected.after(this);
- } else {
- var parent = this.parentNode;
- jQuery(parent).prepend(this);
- }
- jQuery(this).addClass('facetValueSelected');
- var $img = jQuery(this).find('img');
- $img.attr('src', SELECTED_IMG).attr('alt', (_('selected')));
- }
- buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
- facet.find('.facetBody').animate({scrollTop: 0}, '');
- });
- facet.find('select.facetOperator').change(function() {
- var nbselected = facet.find('div.facetValueSelected').length;
- if (nbselected >= 2) {
- buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
- }
- });
- facet.find('div.facetTitle').click(function() {
- facet.find('div.facetBody').toggleClass('hidden').toggleClass('opened');
- jQuery(this).toggleClass('opened');
- });
-
- });
- }
- });
-}
-
-// trigger this function on document ready event if you provide some kind of
-// persistent search (eg crih)
-function reorderFacetsItems(root){
- root = root || document;
- jQuery(root).find('form').each(function () {
- var form = jQuery(this);
- if (form.attr('cubicweb:facetargs')) {
- form.find('div.facet').each(function() {
- var facet = jQuery(this);
- var lastSelected = null;
- facet.find('div.facetCheckBox').each(function (i) {
- var $this = jQuery(this);
- if ($this.hasClass('facetValueSelected')) {
- if (lastSelected) {
- lastSelected.after(this);
- } else {
- var parent = this.parentNode;
- jQuery(parent).prepend(this);
- }
- lastSelected = $this;
- }
- });
- });
- }
- });
-}
-
-// we need to differenciate cases where initFacetBoxEvents is called
-// with one argument or without any argument. If we use `initFacetBoxEvents`
-// as the direct callback on the jQuery.ready event, jQuery will pass some argument
-// of his, so we use this small anonymous function instead.
-jQuery(document).ready(function() {initFacetBoxEvents();});
-
-CubicWeb.provide('formfilter.js');
--- a/web/facet.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/facet.py Wed Aug 26 14:45:56 2009 +0200
@@ -718,7 +718,7 @@
imgalt = self.req._('not selected')
self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
% (cssclass, xml_escape(unicode(self.value))))
- self.w(u'<img src="%s" alt="%s"/> ' % (imgsrc, imgalt))
+ self.w(u'<img src="%s" alt="%s"/> ' % (imgsrc, imgalt))
self.w(u'<a href="javascript: {}">%s</a>' % xml_escape(self.label))
self.w(u'</div>')
@@ -747,7 +747,7 @@
self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
% (cssclass, xml_escape(unicode(self.value))))
self.w(u'<div class="facetCheckBoxWidget">')
- self.w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" /> ' % (imgsrc, imgalt))
+ self.w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" /> ' % (imgsrc, imgalt))
self.w(u'<label class="facetTitle" cubicweb:facetName="%s"><a href="javascript: {}">%s</a></label>' % (facetid, title))
self.w(u'</div>\n')
self.w(u'</div>\n')
@@ -755,7 +755,7 @@
class FacetSeparator(HTMLWidget):
def __init__(self, label=None):
- self.label = label or u' '
+ self.label = label or u' '
def _render(self):
pass
--- a/web/form.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/form.py Wed Aug 26 14:45:56 2009 +0200
@@ -162,7 +162,7 @@
if len(errors) > 1:
templstr = '<li>%s</li>\n'
else:
- templstr = ' %s\n'
+ templstr = ' %s\n'
for field, err in errors:
if field is None:
errormsg += templstr % err
--- a/web/formwidgets.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/formwidgets.py Wed Aug 26 14:45:56 2009 +0200
@@ -110,7 +110,7 @@
'<br/>',
tags.input(name=confirmname, value=values[0], type=self.type,
**attrs),
- ' ', tags.span(form.req._('confirm password'),
+ ' ', tags.span(form.req._('confirm password'),
**{'class': 'emphasis'})]
return u'\n'.join(inputs)
@@ -437,7 +437,7 @@
return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
<div id="newvalue">
<input type="text" id="newopt" />
- <a href="javascript:noop()" id="add_newopt"> </a></div>
+ <a href="javascript:noop()" id="add_newopt"> </a></div>
'''
# buttons ######################################################################
--- a/web/htmlwidgets.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/htmlwidgets.py Wed Aug 26 14:45:56 2009 +0200
@@ -75,7 +75,7 @@
self.w(u'</ul>\n')
self.w(u'</div>\n')
if self.shadow:
- self.w(u'<div class="shadow"> </div>')
+ self.w(u'<div class="shadow"> </div>')
def _render(self):
if self.id:
@@ -191,7 +191,7 @@
self.value = value
def _render(self):
- self.w(u'<li><div><span class="label">%s</span> '
+ self.w(u'<li><div><span class="label">%s</span> '
u'<span class="value">%s</span></div></li>'
% (self.label, self.value))
--- a/web/test/unittest_form.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/test/unittest_form.py Wed Aug 26 14:45:56 2009 +0200
@@ -219,7 +219,7 @@
'''<input id="upassword:%(eid)s" name="upassword:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
<br/>
<input name="upassword-confirm:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
-
+ 
<span class="emphasis">confirm password</span>''' % {'eid': self.entity.eid})
--- a/web/views/basecomponents.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/basecomponents.py Wed Aug 26 14:45:56 2009 +0200
@@ -74,7 +74,7 @@
id = 'help'
cw_property_defs = VISIBLE_PROP_DEF
def call(self):
- self.w(u'<a href="%s" class="help" title="%s"> </a>'
+ self.w(u'<a href="%s" class="help" title="%s"> </a>'
% (self.build_url(_restpath='doc/main'),
self.req._(u'help'),))
@@ -110,11 +110,11 @@
def anon_user_link(self):
if self.config['auth-mode'] == 'cookie':
self.w(self.req._('anonymous'))
- self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
+ self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
% (self.req._('i18n_login_popup')))
else:
self.w(self.req._('anonymous'))
- self.w(u' [<a class="logout" href="%s">%s</a>]'
+ self.w(u' [<a class="logout" href="%s">%s</a>]'
% (self.build_url('login'), self.req._('login')))
@@ -212,7 +212,7 @@
url, _('Any')))
else:
html.insert(0, u'<span class="selected">%s</span>' % _('Any'))
- self.w(u' | '.join(html))
+ self.w(u' | '.join(html))
self.w(u'</div>')
class PdfViewComponent(component.Component):
--- a/web/views/basetemplates.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/basetemplates.py Wed Aug 26 14:45:56 2009 +0200
@@ -429,7 +429,7 @@
req._(ChangeLogView.title).lower()))
self.w(u'<a href="%s">%s</a> | ' % (req.build_url('doc/about'),
req._('about this site')))
- self.w(u'© 2001-2009 <a href="http://www.logilab.fr">Logilab S.A.</a>')
+ self.w(u'<a href="http://www.cubicweb.org">%s</a>' % req._('powered by CubicWeb'))
self.w(u'</div>')
@@ -478,7 +478,7 @@
self.w(u'<div id="%s" class="%s">' % (id, klass))
if title:
self.w(u'<div id="loginTitle">%s</div>'
- % (self.req.property_value('ui.site-title') or u' '))
+ % (self.req.property_value('ui.site-title') or u' '))
self.w(u'<div id="loginContent">\n')
if message:
@@ -509,7 +509,7 @@
self.w(u'<td><label for="__password" >%s</label></td>' % _('password'))
self.w(u'<td><input name="__password" id="__password" class="data" type="password" /></td>\n')
self.w(u'</tr><tr>\n')
- self.w(u'<td> </td><td><input type="submit" class="loginButton right" value="%s" />\n</td>' % _('log in'))
+ self.w(u'<td> </td><td><input type="submit" class="loginButton right" value="%s" />\n</td>' % _('log in'))
self.w(u'</tr>\n')
self.w(u'</table>\n')
self.w(u'</form>\n')
--- a/web/views/baseviews.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/baseviews.py Wed Aug 26 14:45:56 2009 +0200
@@ -51,13 +51,13 @@
"""
id = 'final'
# record generated i18n catalog messages
- _('%d years')
- _('%d months')
- _('%d weeks')
- _('%d days')
- _('%d hours')
- _('%d minutes')
- _('%d seconds')
+ _('%d years')
+ _('%d months')
+ _('%d weeks')
+ _('%d days')
+ _('%d hours')
+ _('%d minutes')
+ _('%d seconds')
_('%d years')
_('%d months')
_('%d weeks')
@@ -80,7 +80,7 @@
# value is DateTimeDelta but we have no idea about what is the
# reference date here, so we can only approximate years and months
if format == 'text/html':
- space = ' '
+ space = ' '
else:
space = ' '
if value.days > 730: # 2 years
@@ -111,7 +111,7 @@
secondary = icon + view(oneline)
"""
entity = self.rset.get_entity(row, col)
- self.w(u' ')
+ self.w(u' ')
self.wview('oneline', self.rset, row=row, col=col)
--- a/web/views/bookmark.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/bookmark.py Wed Aug 26 14:45:56 2009 +0200
@@ -33,7 +33,7 @@
def cell_call(self, row, col):
"""the primary view for bookmark entity"""
entity = self.complete_entity(row, col)
- self.w(u' ')
+ self.w(u' ')
self.w(u"<span class='title'><b>")
self.w(u"%s : %s" % (self.req._('Bookmark'), xml_escape(entity.title)))
self.w(u"</b></span>")
--- a/web/views/boxes.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/boxes.py Wed Aug 26 14:45:56 2009 +0200
@@ -65,17 +65,8 @@
not self.schema[self.rset.description[0][0]].is_final() and \
searchstate == 'normal':
entity = self.rset.get_entity(0, 0)
- #entity.complete()
- if add_menu.items:
- self.info('explicit actions defined, ignoring potential rtags for %s',
- entity.e_schema)
- else:
- # some addrelated actions may be specified but no one is selectable
- # in which case we should not fallback to schema_actions. The proper
- # way to avoid this is to override add_related_schemas() on the
- # entity class to return an empty list
- for action in self.schema_actions(entity):
- add_menu.append(action)
+ for action in self.schema_actions(entity):
+ add_menu.append(action)
self.workflow_actions(entity, box)
if box.is_empty() and not other_menu.is_empty():
box.items = other_menu.items
@@ -113,9 +104,9 @@
"""this is actually used ui method to generate 'addrelated' actions from
the schema.
- If you're using explicit 'addrelated' actions for an entity types, you
- should probably overrides this method to return an empty list else you
- may get some unexpected actions.
+ If you don't want any auto-generated actions, you should overrides this
+ method to return an empty list. If you only want some, you can configure
+ them by using uicfg.actionbox_appearsin_addmenu
"""
req = self.req
eschema = entity.e_schema
--- a/web/views/calendar.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/calendar.py Wed Aug 26 14:45:56 2009 +0200
@@ -293,7 +293,7 @@
__redirectvid=self.id
)
self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self.req._(u'add')))
- self.w(u' ')
+ self.w(u' ')
self.w(u'</div>')
self.w(u'<div class="cellContent">')
for task_descr in rows:
@@ -312,7 +312,7 @@
self.w(u'</div>')
else:
self.w(u'<div class="task">')
- self.w(u" ")
+ self.w(u" ")
self.w(u'</div>')
self.w(u'</div>')
self.w(u'</td>')
@@ -443,7 +443,7 @@
self.w(u'</tr>')
self.w(u'</table></div>')
self.w(u'<div id="coord"></div>')
- self.w(u'<div id="debug"> </div>')
+ self.w(u'<div id="debug"> </div>')
def _build_calendar_cell(self, date, task_descrs):
inday_tasks = [t for t in task_descrs if t.is_one_day_task() and t.in_working_hours()]
--- a/web/views/cwuser.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/cwuser.py Wed Aug 26 14:45:56 2009 +0200
@@ -11,9 +11,10 @@
from cubicweb.selectors import one_line_rset, implements, match_user_groups
from cubicweb.view import EntityView
-from cubicweb.web import action
+from cubicweb.web import action, uicfg
from cubicweb.web.views import primary
+uicfg.primaryview_section.tag_attribute(('CWUser', 'login'), 'hidden')
class UserPreferencesEntityAction(action.Action):
id = 'prefs'
--- a/web/views/editcontroller.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/editcontroller.py Wed Aug 26 14:45:56 2009 +0200
@@ -157,6 +157,7 @@
errorurl = self.req.form.get('__errorurl')
if errorurl:
self.req.cancel_edition(errorurl)
+ self.req.message = self.req._('edit canceled')
return self.reset()
def _action_delete(self):
--- a/web/views/editviews.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/editviews.py Wed Aug 26 14:45:56 2009 +0200
@@ -63,7 +63,7 @@
entity = self.rset.get_entity(row, col)
erset = entity.as_rset()
if self.req.match_search_state(erset):
- self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
+ self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
xml_escape(linksearch_select_url(self.req, erset)),
self.req._('select this entity'),
xml_escape(entity.view('textoutofcontext')),
--- a/web/views/emailaddress.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/emailaddress.py Wed Aug 26 14:45:56 2009 +0200
@@ -27,7 +27,7 @@
if not entity.canonical:
canonemailaddr = entity.canonical_form()
if canonemailaddr:
- self.w(u' (<i>%s</i>)' % canonemailaddr.view('oneline'))
+ self.w(u' (<i>%s</i>)' % canonemailaddr.view('oneline'))
self.w(u'</h3>')
elif entity.identical_to:
self.w(u'</h3>')
--- a/web/views/facets.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/facets.py Wed Aug 26 14:45:56 2009 +0200
@@ -40,7 +40,7 @@
roundcorners = True
needs_css = 'cubicweb.facets.css'
- needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js')
+ needs_js = ('cubicweb.ajax.js', 'cubicweb.facets.js')
bk_linkbox_template = u'<div class="facetTitle">%s</div>'
--- a/web/views/formrenderers.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/formrenderers.py Wed Aug 26 14:45:56 2009 +0200
@@ -109,7 +109,7 @@
if example:
help.append('<div class="helper">(%s: %s)</div>'
% (self.req._('sample format'), example))
- return u' '.join(help)
+ return u' '.join(help)
# specific methods (mostly to ease overriding) #############################
@@ -130,7 +130,7 @@
if len(errors) > 1:
templstr = '<li>%s</li>\n'
else:
- templstr = ' %s\n'
+ templstr = ' %s\n'
for field, err in errors:
if field is None:
errormsg += templstr % err
@@ -284,7 +284,7 @@
if self.display_help:
w(self.render_help(form, field))
# empty slot for buttons
- w(u'<th class="labelCol"> </th>')
+ w(u'<th class="labelCol"> </th>')
w(u'</tr>')
w(u'<tr>')
for field in fields:
@@ -441,7 +441,7 @@
w(u'</tr>')
pendings = list(form.restore_pending_inserts())
if not pendings:
- w(u'<tr><th> </th><td> </td></tr>')
+ w(u'<tr><th> </th><td> </td></tr>')
else:
for row in pendings:
# soon to be linked to entities
@@ -519,7 +519,7 @@
w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
% (rschema, entity.eid, js, __('add a %s' % targettype)))
w(u'</div>')
- w(u'<div class="trame_grise"> </div>')
+ w(u'<div class="trame_grise"> </div>')
w(u'</div>')
--- a/web/views/ibreadcrumbs.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/ibreadcrumbs.py Wed Aug 26 14:45:56 2009 +0200
@@ -10,50 +10,55 @@
from logilab.mtconverter import xml_escape
+from cubicweb.interfaces import IBreadCrumbs
+from cubicweb.selectors import (one_line_rset, implements, one_etype_rset,
+ two_lines_rset, any_rset)
+from cubicweb.view import EntityView, Component
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
-from cubicweb.interfaces import IBreadCrumbs
-from cubicweb.selectors import match_context_prop, one_line_rset, implements
from cubicweb.entity import Entity
-from cubicweb.view import EntityView
from cubicweb.common.uilib import cut
-from cubicweb.web.component import EntityVComponent
def bc_title(entity):
textsize = entity.req.property_value('navigation.short-line-size')
return xml_escape(cut(entity.dc_title(), textsize))
+# XXX only provides the component version
-class BreadCrumbEntityVComponent(EntityVComponent):
+class BreadCrumbEntityVComponent(Component):
id = 'breadcrumbs'
- # register msg not generated since no entity implements IPrevNext in cubicweb itself
+ __select__ = one_line_rset() & implements(IBreadCrumbs)
+
+ property_defs = {
+ _('visible'): dict(type='Boolean', default=True,
+ help=_('display the component or not')),
+ }
title = _('contentnavigation_breadcrumbs')
help = _('contentnavigation_breadcrumbs_description')
- __select__ = (one_line_rset() & match_context_prop() & implements(IBreadCrumbs))
- context = 'navtop'
- order = 5
- visible = False
- separator = u' > '
+ separator = u' > '
def call(self, view=None, first_separator=True):
entity = self.rset.get_entity(0,0)
path = entity.breadcrumbs(view)
if path:
- self.w(u'<span class="pathbar">')
+ self.w(u'<span id="breadcrumbs" class="pathbar">')
if first_separator:
self.w(self.separator)
- root = path.pop(0)
- if isinstance(root, Entity):
- self.w(u'<a href="%s">%s</a>' % (self.req.build_url(root.id),
- root.dc_type('plural')))
- self.w(self.separator)
- self.wpath_part(root, entity, not path)
- for i, parent in enumerate(path):
- self.w(self.separator)
- self.w(u"\n")
- self.wpath_part(parent, entity, i == len(path) - 1)
+ self.render_breadcrumbs(entity, path)
self.w(u'</span>')
+ def render_breadcrumbs(self, contextentity, path):
+ root = path.pop(0)
+ if isinstance(root, Entity):
+ self.w(u'<a href="%s">%s</a>' % (self.req.build_url(root.id),
+ root.dc_type('plural')))
+ self.w(self.separator)
+ self.wpath_part(root, contextentity, not path)
+ for i, parent in enumerate(path):
+ self.w(self.separator)
+ self.w(u"\n")
+ self.wpath_part(parent, contextentity, i == len(path) - 1)
+
def wpath_part(self, part, contextentity, last=False):
if isinstance(part, Entity):
if last and part.eid == contextentity.eid:
@@ -70,10 +75,28 @@
self.w(cut(unicode(part), textsize))
-class BreadCrumbComponent(BreadCrumbEntityVComponent):
- __registry__ = 'components'
- __select__ = (one_line_rset() & implements(IBreadCrumbs))
- visible = True
+class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent):
+ __select__ = two_lines_rset() & one_etype_rset() & implements(IBreadCrumbs)
+
+ def render_breadcrumbs(self, contextentity, path):
+ # XXX hack: only display etype name or first non entity path part
+ root = path.pop(0)
+ if isinstance(root, Entity):
+ self.w(u'<a href="%s">%s</a>' % (self.req.build_url(root.id),
+ root.dc_type('plural')))
+ else:
+ self.wpath_part(root, entity, not path)
+
+
+class BreadCrumbAnyRSetVComponent(BreadCrumbEntityVComponent):
+ __select__ = any_rset()
+
+ def call(self, view=None, first_separator=True):
+ self.w(u'<span id="breadcrumbs" class="pathbar">')
+ if first_separator:
+ self.w(self.separator)
+ self.w(self.req._('search'))
+ self.w(u'</span>')
class BreadCrumbView(EntityView):
--- a/web/views/iprogress.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/iprogress.py Wed Aug 26 14:45:56 2009 +0200
@@ -167,10 +167,10 @@
"""
id = 'ic_progress_table_view'
- def call(self):
+ def call(self, columns=None):
view = self.vreg['views'].select('progress_table_view', self.req,
rset=self.rset)
- columns = list(view.columns)
+ columns = list(columns or view.columns)
try:
columns.remove('project')
except ValueError:
--- a/web/views/management.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/management.py Wed Aug 26 14:45:56 2009 +0200
@@ -139,7 +139,7 @@
# don't give __delete value to build_url else it will be urlquoted
# and this will replace %s by %25s
delurl += '&__delete=%s:require_permission:%%s' % entity.eid
- dellinktempl = u'[<a href="%s" title="%s">-</a>] ' % (
+ dellinktempl = u'[<a href="%s" title="%s">-</a>] ' % (
xml_escape(delurl), _('delete this permission'))
else:
dellinktempl = None
--- a/web/views/navigation.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/navigation.py Wed Aug 26 14:45:56 2009 +0200
@@ -40,9 +40,9 @@
self.index_display(start, stop)))
start = stop + 1
w(u'<div class="pagination">')
- w(u'%s ' % self.previous_link(params))
- w(u'[ %s ]' % u' | '.join(blocklist))
- w(u' %s' % self.next_link(params))
+ w(u'%s ' % self.previous_link(basepath, params))
+ w(u'[ %s ]' % u' | '.join(blocklist))
+ w(u' %s' % self.next_link(basepath, params))
w(u'</div>')
def index_display(self, start, stop):
@@ -131,18 +131,18 @@
cell = self.format_link_content(index_display(start), index_display(stop))
blocklist.append(self.page_link(basepath, params, start, stop, cell))
start = stop + 1
- self.write_links(params, blocklist)
+ self.write_links(basepath, params, blocklist)
def format_link_content(self, startstr, stopstr):
text = u'%s - %s' % (startstr.lower()[:self.nb_chars],
stopstr.lower()[:self.nb_chars])
return xml_escape(text)
- def write_links(self, params, blocklist):
+ def write_links(self, basepath, params, blocklist):
self.w(u'<div class="pagination">')
- self.w(u'%s ' % self.previous_link(params))
- self.w(u'[ %s ]' % u' | '.join(blocklist))
- self.w(u' %s' % self.next_link(params))
+ self.w(u'%s ' % self.previous_link(basepath, params))
+ self.w(u'[ %s ]' % u' | '.join(blocklist))
+ self.w(u' %s' % self.next_link(basepath, params))
self.w(u'</div>')
@@ -160,7 +160,7 @@
# make a link to see them all
if show_all_option:
url = xml_escape(self.build_url(__force_display=1, **params))
- w(u'<p><a href="%s">%s</a></p>\n'
+ w(u'<span><a href="%s">%s</a></span>\n'
% (url, req._('show %s results') % len(rset)))
rset.limit(offset=start, limit=stop-start, inplace=True)
--- a/web/views/old_calendar.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/old_calendar.py Wed Aug 26 14:45:56 2009 +0200
@@ -30,8 +30,8 @@
# Navigation building methods / views ####################################
- PREV = u'<a href="%s"><<</a> <a href="%s"><</a>'
- NEXT = u'<a href="%s">></a> <a href="%s">>></a>'
+ PREV = u'<a href="%s"><<</a>  <a href="%s"><</a>'
+ NEXT = u'<a href="%s">></a>  <a href="%s">>></a>'
NAV_HEADER = u"""<table class="calendarPageHeader">
<tr><td class="prev">%s</td><td class="next">%s</td></tr>
</table>
@@ -200,7 +200,7 @@
self.w(u'<tr>')
rql = self.rset.printable_rql()
for cur_month in date_range(begin, end, incmonth=1):
- umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
+ umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
url = self.build_url(rql=rql, vid=self.id,
year=cur_month.year, month=cur_month.month)
self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (xml_escape(url),
@@ -215,7 +215,7 @@
else:
day = date(cur_month.year, cur_month.month, day_num+1)
events = schedule.get(day)
- self.w(u'<td>%s %s</td>\n' % (_(WEEKDAYS[day.weekday()])[0].upper(), day_num+1))
+ self.w(u'<td>%s %s</td>\n' % (_(WEEKDAYS[day.weekday()])[0].upper(), day_num+1))
self.format_day_events(day, events)
self.w(u'</tr>')
@@ -345,8 +345,8 @@
am_row = [am for day, am, pm in row]
pm_row = [pm for day, am, pm in row]
formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
- formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'% '\n'.join(am_row))
- formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'% '\n'.join(pm_row))
+ formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'% '\n'.join(am_row))
+ formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'% '\n'.join(pm_row))
# tigh everything together
url = self.build_url(rql=rql, vid='ampmcalendarmonth',
year=first_day.year, month=first_day.month)
@@ -364,7 +364,7 @@
self.w(u'<tr>')
rql = self.rset.printable_rql()
for cur_month in date_range(begin, end, incmonth=1):
- umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
+ umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
url = self.build_url(rql=rql, vid=self.id,
year=cur_month.year, month=cur_month.month)
self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (xml_escape(url),
@@ -379,7 +379,7 @@
else:
day = date(cur_month.year, cur_month.month, day_num+1)
events = schedule.get(day)
- self.w(u'<td>%s %s</td>\n' % (_(WEEKDAYS[day.weekday()])[0].upper(),
+ self.w(u'<td>%s %s</td>\n' % (_(WEEKDAYS[day.weekday()])[0].upper(),
day_num+1))
self.format_day_events(day, events)
self.w(u'</tr>')
@@ -437,8 +437,8 @@
am_row = [am for day, am, pm in row]
pm_row = [pm for day, am, pm in row]
formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
- formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'% '\n'.join(am_row))
- formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'% '\n'.join(pm_row))
+ formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'% '\n'.join(am_row))
+ formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'% '\n'.join(pm_row))
# tigh everything together
url = self.build_url(rql=rql, vid='ampmcalendarmonth',
year=first_day.year, month=first_day.month)
@@ -464,7 +464,7 @@
monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
w(u'<tr>%s</tr>' % (
WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink)))
- w(u'<tr><th>%s</th><th> </th></tr>'% _(u'Date'))
+ w(u'<tr><th>%s</th><th> </th></tr>'% _(u'Date'))
for day in date_range(monday, sunday):
events = schedule.get(day)
style = day.weekday() % 2 and "even" or "odd"
@@ -534,9 +534,9 @@
AMPM_CONTENT = u'<td class="%s"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
WEEK_TITLE = u'<th class="weekTitle" colspan="2">%s %s (%s)</th>'
-WEEK_EMPTY_CELL = u'<td class="weekEmptyCell"> </td>'
+WEEK_EMPTY_CELL = u'<td class="weekEmptyCell"> </td>'
WEEK_CELL = u'<td class="weekCell"><div class="cellContent">%s</div></td>'
-AMPM_DAYWEEK_EMPTY = u'<td>%s %s</td>'
-AMPM_DAYWEEK = u'<td rowspan="%d">%s %s</td>'
+AMPM_DAYWEEK_EMPTY = u'<td>%s %s</td>'
+AMPM_DAYWEEK = u'<td rowspan="%d">%s %s</td>'
AMPM_WEEK_CELL = u'<td class="ampmWeekCell"><div class="cellContent">%02d:%02d - %s</div></td>'
--- a/web/views/startup.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/startup.py Wed Aug 26 14:45:56 2009 +0200
@@ -52,10 +52,10 @@
self.wview('inlined', rset, row=0)
else:
self.entities()
- self.w(u'<div class="hr"> </div>')
+ self.w(u'<div class="hr"> </div>')
self.startup_views()
if manager and 'Card' in self.schema:
- self.w(u'<div class="hr"> </div>')
+ self.w(u'<div class="hr"> </div>')
if rset:
href = rset.get_entity(0, 0).absolute_url(vid='edition')
label = self.req._('edit the index page')
@@ -104,7 +104,7 @@
key=lambda (l,a,e):unormalize(l))
q, r = divmod(len(infos), 2)
if r:
- infos.append( (None, ' ', ' ') )
+ infos.append( (None, ' ', ' ') )
infos = zip(infos[:q+r], infos[q+r:])
for (_, etypelink, addlink), (_, etypelink2, addlink2) in infos:
self.w(u'<tr>\n')
@@ -126,7 +126,7 @@
label = display_name(req, etype, 'plural')
nb = req.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
url = self.build_url(etype)
- etypelink = u' <a href="%s">%s</a> (%d)' % (
+ etypelink = u' <a href="%s">%s</a> (%d)' % (
xml_escape(url), label, nb)
yield (label, etypelink, self.add_entity_link(eschema, req))
--- a/web/views/tableview.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/tableview.py Wed Aug 26 14:45:56 2009 +0200
@@ -51,7 +51,7 @@
"""display a form to filter table's content. This should only
occurs when a context eid is given
"""
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.formfilter.js'))
+ self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
# drop False / None values from vidargs
vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
self.w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
@@ -129,8 +129,7 @@
# replace the inner div, so don't regenerate everything under the if
# below
if not fromformfilter:
- div_class = 'section'
- self.w(u'<div class="%s">' % div_class)
+ self.w(u'<div class="section">')
if not title and 'title' in req.form:
title = req.form['title']
if title:
--- a/web/views/timetable.py Fri Aug 21 16:26:20 2009 +0200
+++ b/web/views/timetable.py Wed Aug 26 14:45:56 2009 +0200
@@ -133,7 +133,7 @@
""" render column headers """
self.w(u'<tr class="header">\n')
- self.w(u'<th class="ttdate"> </th>\n')
+ self.w(u'<th class="ttdate"> </th>\n')
columns = []
for user, width in zip(users, widths):
self.w(u'<th colspan="%s">' % max(MIN_COLS, width))
@@ -191,13 +191,13 @@
task_descr, first_row = value
if first_row:
url = xml_escape(task_descr.task.absolute_url(vid="edition"))
- self.w(u'<td rowspan="%d" class="%s %s" onclick="document.location=\'%s\'"> <div>' % (
+ self.w(u'<td rowspan="%d" class="%s %s" onclick="document.location=\'%s\'"> <div>' % (
task_descr.lines, task_descr.color, filled_klasses[kj], url))
task_descr.task.view('tooltip', w=self.w)
self.w(u'</div></td>')
else:
if empty_line:
- self.w(u'<td class="ttempty"> </td>')
+ self.w(u'<td class="ttempty"> </td>')
else:
- self.w(u'<td class="%s"> </td>' % empty_klasses[kj] )
+ self.w(u'<td class="%s"> </td>' % empty_klasses[kj] )
self.w(u'</tr>\n')
--- a/web/wdoc/ChangeLog_en Fri Aug 21 16:26:20 2009 +0200
+++ b/web/wdoc/ChangeLog_en Wed Aug 26 14:45:56 2009 +0200
@@ -1,27 +1,34 @@
.. -*- coding: utf-8 -*-
-.. _`user preferences`: myprefs#fieldset_ui
-
-2008-09-25 -- 2.50.0
- * jQuery replaces MochiKit
- * schema inheritance support
+.. _`user preferences`: myprefs
+.. _here: sparql
+.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
+.. _schema: schema
+.. _OWL: http://www.w3.org/TR/owl-features/
-2008-05-13 -- 2.48.0
- * web pages are now served with the ``xhtml+xml`` content type
+2009-08-07 -- 3.4.0
+
+ * support for SPARQL_. Click here_ to test it.
-2008-03-27 -- 2.47.0
- * fckeditor is now integrated to edit rich text fields. If you don't see it,
- check your `user preferences`_.
+ * and another step toward the semantic web: new `ExternalUri` entity type
+ with its associated `same_as` relation. See
+ http://www.w3.org/TR/owl-ref/#sameAs-def for more information and check
+ this instance schema_ to see on which entity types it may be applied
-2008-03-13 -- 2.46.0
- * new calendar and timetable views.
-
- * click-and-edit functionalities : if you see the text edit cursor when
- you're over a fied, try to double-click!
-
- * automatic facets oriented search : a filter box should appear when you're
- looking for something and more than one entity are displayed.
+ * new "view workflow" and "view history" items in the workflow
+ sub-menu of the actions box
+
+ * you can now edit comments of workflow transition afterward (if authorized,
+ of course)
+
+ * modification date of an entity is updated when its state is changed
+
-2008-02-15 -- 2.44.0
- * new internationalized online help system. Click the question mark on the
- right top corner! Hopefuly some new documentation will appear as time is
- going.
+2009-02-26 -- 3.1.0
+
+ * schema may be exported as OWL_
+
+ * new ajax interface for site configuration / `user preferences`_
+
+
+2008-10-24 -- 2.99.0
+ * cubicweb is now open source !
--- a/web/wdoc/ChangeLog_fr Fri Aug 21 16:26:20 2009 +0200
+++ b/web/wdoc/ChangeLog_fr Wed Aug 26 14:45:56 2009 +0200
@@ -1,31 +1,34 @@
.. -*- coding: utf-8 -*-
.. _`préférences utilisateurs`: myprefs#fieldset_ui
-
-2008-09-25 -- 2.50.0
- * jQuery remplace MochiKit
- * support de l'héritage de schéma
+.. _ici: sparql
+.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
+.. _schema: schema
+.. _OWL: http://www.w3.org/TR/owl-features/
-2008-05-13 -- 2.48.0
- * les pages sont servies en tant que ``xhtml+xml`` pour certains navigateurs
+2009-08-07 -- 3.4.0
-2008-03-27 -- 2.47.0
- * fckeditor est enfin intégré pour éditer les champs de type texte riche. Si
- vous ne le voyez pas apparaître, vérifiez vos `préférences utilisateurs`_.
+ * support de SPARQL_. Cliquez ici_ pour le tester.
-2008-03-13 -- 2.46.0
- * nouvelle vues calendrier et emploi du temps
-
- * fonctionalité "click-et-édite" : si vous voyez apparaitre le curseur
- d'édition de texte en survolant un champ, essayez de double-cliquer !
-
- * recherche par facettes : une boîte de filtrage devrait apparaitre
- automatiquement lorsque vous effectuez une recherche qui ramène plus d'une
- entité
+ * et encore un pas vers le web sémantique : un nouveau type d'entité
+ `ExternalUri` et la relation associée `same_as`. Voir
+ http://www.w3.org/TR/owl-ref/#sameAs-def pour plus d'information, ainsi
+ que le schema_ de cette instance pour voir à quels types d'entités cela
+ s'applique.
-2008-02-15 -- 2.44.0
- * nouveau système d'aide internationalisé. Cliquez sur le point
- d'interrogation en haut à droite. Reste à enrichir le contenu de cette
- documentation, mais cela devrait arriver avec le temps.
+ * nouveau liens "voir les états possibles" et "voir l'historique" dans le sous-menu
+ workflow de la boite actions
+
+ * vous pouvez dorénavant éditer les commentaires des passages de transition
+ depuis l'historique, pour peu que vous ayez les droits nécessaire bien sûr
+
+ * la date de modification d'une entité est mise à jour lorsque son état est changé
-
+2009-02-26 -- 3.1.0
+
+ * le schéma peut être exporté en OWL_
+
+ * nouvelle interface ajax pour la configuration du site et les `préférences utilisateurs`_
+
+
+