# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1241707354 -7200 # Node ID cba9f175da2d640858a7e05ec6e7d0d454885c55 # Parent a721966779bec15b493b66daa409968569a34101# Parent d817f23439bae6e85dcedaf8ba936f0363e212f3 merge diff -r a721966779be -r cba9f175da2d appobject.py --- a/appobject.py Thu May 07 16:33:22 2009 +0200 +++ b/appobject.py Thu May 07 16:42:34 2009 +0200 @@ -8,8 +8,6 @@ from datetime import datetime, timedelta -from simplejson import dumps - from logilab.common.decorators import classproperty from logilab.common.deprecation import obsolete @@ -23,21 +21,21 @@ ONESECOND = timedelta(0, 1, 0) -class Cache(dict): +class Cache(dict): def __init__(self): super(Cache, self).__init__() self.cache_creation_date = None self.latest_cache_lookup = datetime.now() - + CACHE_REGISTRY = {} class AppRsetObject(VObject): """This is the base class for CubicWeb application objects which are selected according to a request and result set. - + Classes are kept in the vregistry and instantiation is done at selection time. - + At registration time, the following attributes are set on the class: :vreg: the application's registry @@ -62,11 +60,11 @@ cls.config = vreg.config cls.register_properties() return cls - + @classmethod def vreg_initialization_completed(cls): pass - + @classmethod def selected(cls, *args, **kwargs): """by default web app objects are usually instantiated on @@ -85,9 +83,9 @@ # notice that when it exists multiple objects with the same id (adaptation, # overriding) only the first encountered definition is considered, so those # objects can't try to have different default values for instance. - + property_defs = {} - + @classmethod def register_properties(cls): for propid, pdef in cls.property_defs.items(): @@ -95,7 +93,7 @@ pdef['default'] = getattr(cls, propid, pdef['default']) pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide')) cls.vreg.register_property(cls.propkey(propid), **pdef) - + @classmethod def propkey(cls, propid): return '%s.%s.%s' % (cls.__registry__, cls.id, propid) @@ -109,7 +107,7 @@ if not isinstance(selector, tuple): selector = (selector,) return selector - + def __init__(self, req=None, rset=None, row=None, col=None, **extra): super(AppRsetObject, self).__init__() self.req = req @@ -117,12 +115,12 @@ self.row = row self.col = col self.extra_kwargs = extra - + def get_cache(self, cachename): """ NOTE: cachename should be dotted names as in : - cubicweb.mycache - - cubes.blog.mycache + - cubes.blog.mycache - etc. """ if cachename in CACHE_REGISTRY: @@ -132,7 +130,7 @@ CACHE_REGISTRY[cachename] = cache _now = datetime.now() if _now > cache.latest_cache_lookup + ONESECOND: - ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', + ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', {'name':cachename}).get_entity(0,0) cache.latest_cache_lookup = _now if not ecache.valid(cache.cache_creation_date): @@ -143,7 +141,7 @@ def propval(self, propid): assert self.req return self.req.property_value(self.propkey(propid)) - + def limited_rql(self): """return a printable rql for the result set associated to the object, with limit/offset correctly set according to maximum page size and @@ -165,7 +163,7 @@ else: rql = self.rset.printable_rql() return rql - + def _limit_offset_rql(self, limit, offset): rqlst = self.rset.syntax_tree() if len(rqlst.children) == 1: @@ -187,7 +185,7 @@ rql = rqlst.as_string(kwargs=self.rset.args) rqlst.parent = None return rql - + def view(self, __vid, rset=None, __fallback_vid=None, **kwargs): """shortcut to self.vreg.render method avoiding to pass self.req""" try: @@ -197,11 +195,11 @@ raise view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs) return view.dispatch(**kwargs) - + # url generation methods ################################################## - + controller = 'view' - + def build_url(self, method=None, **kwargs): """return an absolute URL using params dictionary key/values as URL parameters. Values are automatically URL quoted, and the @@ -217,13 +215,13 @@ return self.req.build_url(method, **kwargs) # various resources accessors ############################################# - + def entity(self, row, col=0): """short cut to get an entity instance for a particular row/column (col default to 0) """ return self.rset.get_entity(row, col) - + def complete_entity(self, row, col=0, skip_bytes=True): """short cut to get an completed entity instance for a particular row (all instance's attributes have been fetched) @@ -239,14 +237,15 @@ def rqlexec(req, rql, args=None, key=None): req.execute(rql, args, key) return self.user_callback(rqlexec, args, msg) - + def user_callback(self, cb, args, msg=None, nonify=False): """register the given user callback and return an url to call it ready to be inserted in html """ + from simplejson import dumps self.req.add_js('cubicweb.ajax.js') cbname = self.req.register_onetime_callback(cb, *args) - msg = dumps(msg or '') + msg = dumps(msg or '') return "javascript:userCallbackThenReloadPage('%s', %s)" % ( cbname, msg) @@ -256,7 +255,7 @@ """render a precompiled page template with variables in the given dictionary as context """ - from cubicweb.common.tal import CubicWebContext + from cubicweb.ext.tal import CubicWebContext context = CubicWebContext() context.update({'self': self, 'rset': self.rset, '_' : self.req._, 'req': self.req, 'user': self.req.user}) @@ -293,23 +292,23 @@ if num: return self.req.property_value('ui.float-format') % num return u'' - + # security related methods ################################################ - + def ensure_ro_rql(self, rql): """raise an exception if the given rql is not a select query""" first = rql.split(' ', 1)[0].lower() if first in ('insert', 'set', 'delete'): raise Unauthorized(self.req._('only select queries are authorized')) - + class AppObject(AppRsetObject): """base class for application objects which are not selected according to a result set, only by their identifier. - + Those objects may not have req, rset and cursor set. """ - + @classmethod def selected(cls, *args, **kwargs): """by default web app objects are usually instantiated on diff -r a721966779be -r cba9f175da2d common/mttransforms.py --- a/common/mttransforms.py Thu May 07 16:33:22 2009 +0200 +++ b/common/mttransforms.py Thu May 07 16:42:34 2009 +0200 @@ -11,7 +11,7 @@ from logilab.mtconverter.engine import TransformEngine from logilab.mtconverter.transform import Transform from logilab.mtconverter import (register_base_transforms, - register_pil_transforms, + register_pil_transforms, register_pygments_transforms) from cubicweb.common.uilib import rest_publish, html_publish @@ -31,7 +31,7 @@ output = 'text/html' def _convert(self, trdata): return html_publish(trdata.appobject, trdata.data) - + # Instantiate and configure the transformation engine @@ -47,10 +47,10 @@ HAS_TAL = False from cubicweb.schema import FormatConstraint FormatConstraint.need_perm_formats.remove('text/cubicweb-page-template') - + else: HAS_TAL = True - + class ept_to_html(Transform): inputs = ('text/cubicweb-page-template',) output = 'text/html' @@ -65,7 +65,7 @@ HAS_PIL_TRANSFORMS = True else: HAS_PIL_TRANSFORMS = False - + try: from logilab.mtconverter.transforms import pygmentstransforms for mt in ('text/plain',) + HTML_MIMETYPES: @@ -84,9 +84,9 @@ return origconvert(self, trdata) cls._convert = _convert patch_convert(pygmentstransforms.PygmentsHTMLTransform) - + HAS_PYGMENTS_TRANSFORMS = True except ImportError: HAS_PYGMENTS_TRANSFORMS = False - + register_base_transforms(ENGINE, verb=False) diff -r a721966779be -r cba9f175da2d common/tags.py --- a/common/tags.py Thu May 07 16:33:22 2009 +0200 +++ b/common/tags.py Thu May 07 16:42:34 2009 +0200 @@ -9,17 +9,19 @@ from cubicweb.common.uilib import simple_sgml_tag class tag(object): - def __init__(self, name): + def __init__(self, name, escapecontent=True): self.name = name - + self.escapecontent = escapecontent + def __call__(self, __content=None, **attrs): + attrs.setdefault('escapecontent', self.escapecontent) return simple_sgml_tag(self.name, __content, **attrs) input = tag('input') textarea = tag('textarea') a = tag('a') span = tag('span') -div = tag('div') +div = tag('div', False) img = tag('img') label = tag('label') option = tag('option') @@ -34,8 +36,9 @@ attrs['multiple'] = 'multiple' if id: attrs['id'] = id - html = [u'' % ' '.join('%s="%s"' % kv + for kv in sorted(attrs.items()))] html += options html.append(u'') return u'\n'.join(html) diff -r a721966779be -r cba9f175da2d common/test/unittest_rest.py --- a/common/test/unittest_rest.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -from logilab.common.testlib import unittest_main -from cubicweb.devtools.apptest import EnvBasedTC - -from cubicweb.common.rest import rest_publish - -class RestTC(EnvBasedTC): - def context(self): - return self.execute('CWUser X WHERE X login "admin"').get_entity(0, 0) - - def test_eid_role(self): - context = self.context() - self.assertEquals(rest_publish(context, ':eid:`%s`' % context.eid), - '

#%s

\n' % context.eid) - self.assertEquals(rest_publish(context, ':eid:`%s:some text`' % context.eid), - '

some text

\n') - - def test_card_role_create(self): - self.assertEquals(rest_publish(self.context(), ':card:`index`'), - '

index

\n') - - def test_card_role_link(self): - self.add_entity('Card', wikiid=u'index', title=u'Site index page', synopsis=u'yo') - self.assertEquals(rest_publish(self.context(), ':card:`index`'), - '

index

\n') - - def test_bad_rest_no_crash(self): - data = rest_publish(self.context(), ''' -| card | implication | --------------------------- -| 1-1 | N1 = N2 | -| 1-? | N1 <= N2 | -| 1-+ | N1 >= N2 | -| 1-* | N1>0 => N2>0 | --------------------------- -| ?-? | N1 # N2 | -| ?-+ | N1 >= N2 | -| ?-* | N1 # N2 | --------------------------- -| +-+ | N1>0 => N2>0 et | -| | N2>0 => N1>0 | -| +-* | N1>+ => N2>0 | --------------------------- -| *-* | N1#N2 | --------------------------- - -''') - -if __name__ == '__main__': - unittest_main() diff -r a721966779be -r cba9f175da2d common/test/unittest_uilib.py --- a/common/test/unittest_uilib.py Thu May 07 16:33:22 2009 +0200 +++ b/common/test/unittest_uilib.py Thu May 07 16:42:34 2009 +0200 @@ -21,7 +21,7 @@ for text, expected in data: got = uilib.remove_html_tags(text) self.assertEquals(got, expected) - + def test_fallback_safe_cut(self): self.assertEquals(uilib.fallback_safe_cut(u'ab cd', 4), u'ab c...') self.assertEquals(uilib.fallback_safe_cut(u'ab cd', 5), u'ab cd') @@ -29,7 +29,7 @@ self.assertEquals(uilib.fallback_safe_cut(u'ab &d ef', 5), u'ab &d...') self.assertEquals(uilib.fallback_safe_cut(u'ab ìd', 4), u'ab ì...') self.assertEquals(uilib.fallback_safe_cut(u'& &d ef', 4), u'& &d...') - + def test_lxml_safe_cut(self): self.assertEquals(uilib.safe_cut(u'aaa
aaad
ef', 4), u'

aaa

a...
') self.assertEquals(uilib.safe_cut(u'aaa
aaad
ef', 7), u'

aaa

aaad
...') @@ -75,18 +75,6 @@ got = uilib.text_cut(text, 30) self.assertEquals(got, expected) - def test_ajax_replace_url(self): - # NOTE: for the simplest use cases, we could use doctest - arurl = uilib.ajax_replace_url - self.assertEquals(arurl('foo', 'Person P'), - "javascript: replacePageChunk('foo', 'Person%20P');") - self.assertEquals(arurl('foo', 'Person P', 'oneline'), - "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');") - self.assertEquals(arurl('foo', 'Person P', 'oneline', name='bar', age=12), - 'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'oneline\', {"age": 12, "name": "bar"});') - self.assertEquals(arurl('foo', 'Person P', name='bar', age=12), - 'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'null\', {"age": 12, "name": "bar"});') - tree = ('root', ( ('child_1_1', ( ('child_2_1', ()), ('child_2_2', ( @@ -116,18 +104,18 @@ for child in tuple[1]: n.append(make_tree(child)) return n - + class UIlibHTMLGenerationTC(TestCase): """ a basic tree node, caracterised by an id""" def setUp(self): - """ called before each test from this class """ + """ called before each test from this class """ self.o = make_tree(tree) def test_generated_html(self): s = uilib.render_HTML_tree(self.o, selected_node="child_2_2") self.assertTextEqual(s, generated_html) - - + + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d common/uilib.py --- a/common/uilib.py Thu May 07 16:33:22 2009 +0200 +++ b/common/uilib.py Thu May 07 16:42:34 2009 +0200 @@ -10,11 +10,9 @@ __docformat__ = "restructuredtext en" import csv -import decimal import re -from datetime import datetime, date, timedelta from urllib import quote as urlquote -from cStringIO import StringIO +from StringIO import StringIO from logilab.mtconverter import html_escape, html_unescape @@ -41,7 +39,7 @@ # don't translate empty value if you don't want strange results if props is not None and value and props.get('internationalizable'): return req._(value) - + return value if attrtype == 'Date': return ustrftime(value, req.property_value('ui.date-format')) @@ -63,12 +61,12 @@ # text publishing ############################################################# try: - from cubicweb.common.rest import rest_publish # pylint: disable-msg=W0611 + from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611 except ImportError: def rest_publish(entity, data): """default behaviour if docutils was not found""" - return data - + return html_escape(data) + TAG_PROG = re.compile(r'', re.U) def remove_html_tags(text): """Removes HTML tags from text @@ -201,10 +199,10 @@ return text[:length] + u'...' - + # HTML generation helper functions ############################################ -def simple_sgml_tag(tag, content=None, **attrs): +def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs): """generation of a simple sgml tag (eg without children tags) easier content and attributes will be escaped @@ -216,10 +214,12 @@ except KeyError: pass value += u' ' + u' '.join(u'%s="%s"' % (attr, html_escape(unicode(value))) - for attr, value in attrs.items() + for attr, value in sorted(attrs.items()) if value is not None) if content: - value += u'>%s' % (html_escape(unicode(content)), tag) + if escapecontent: + content = html_escape(unicode(content)) + value += u'>%s' % (content, tag) else: value += u'/>' return value @@ -237,31 +237,6 @@ """builds a HTML link that uses the js toggleVisibility function""" return u'%s' % (toggle_action(nodeid), label) -def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams): - """builds a replacePageChunk-like url - >>> ajax_replace_url('foo', 'Person P') - "javascript: replacePageChunk('foo', 'Person%20P');" - >>> ajax_replace_url('foo', 'Person P', 'oneline') - "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');" - >>> ajax_replace_url('foo', 'Person P', 'oneline', name='bar', age=12) - "javascript: replacePageChunk('foo', 'Person%20P', 'oneline', {'age':12, 'name':'bar'});" - >>> ajax_replace_url('foo', 'Person P', name='bar', age=12) - "javascript: replacePageChunk('foo', 'Person%20P', 'null', {'age':12, 'name':'bar'});" - """ - params = [repr(nodeid), repr(urlquote(rql))] - if extraparams and not vid: - params.append("'null'") - elif vid: - params.append(repr(vid)) - if extraparams: - import simplejson - params.append(simplejson.dumps(extraparams)) - if swap: - params.append('true') - return "javascript: replacePageChunk(%s);" % ', '.join(params) - - -from StringIO import StringIO def ureport_as_html(layout): from logilab.common.ureports import HTMLWriter @@ -315,7 +290,7 @@ else: for child in path[-1].children: build_matrix(path[:] + [child], matrix) - + matrix = [] build_matrix([tree], matrix) @@ -347,9 +322,9 @@ if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3): link_type = 2 links[-1].append(link_type) - + - # We can now generate the HTML code for the + # We can now generate the HTML code for the
s = u'
\n' if caption: s += '\n' % caption @@ -369,7 +344,7 @@ s += '' s += '' % link_cell s += '' % link_cell - + cell = line[-1] if cell: if cell.id == selected_node: @@ -459,7 +434,7 @@ (boxid, ''.join(html_info))) tcbk = tcbk.tb_next except Exception: - pass # doesn't really matter if we have no context info + pass # doesn't really matter if we have no context info strings.append(u'') return '\n'.join(strings) @@ -467,7 +442,7 @@ class UnicodeCSVWriter: """proxies calls to csv.writer.writerow to be able to deal with unicode""" - + def __init__(self, wfunc, encoding, **kwargs): self.writer = csv.writer(self, **kwargs) self.wfunc = wfunc @@ -505,23 +480,6 @@ return newfunc -def jsonize(function): - import simplejson - def newfunc(*args, **kwargs): - ret = function(*args, **kwargs) - if isinstance(ret, decimal.Decimal): - ret = float(ret) - elif isinstance(ret, (date, datetime)): - ret = ret.strftime('%Y-%m-%d %H:%M') - elif isinstance(ret, timedelta): - ret = (ret.days * 24*60*60) + ret.seconds - try: - return simplejson.dumps(ret) - except TypeError: - return simplejson.dumps(repr(ret)) - return newfunc - - def htmlescape(function): def newfunc(*args, **kwargs): ret = function(*args, **kwargs) diff -r a721966779be -r cba9f175da2d cwconfig.py --- a/cwconfig.py Thu May 07 16:33:22 2009 +0200 +++ b/cwconfig.py Thu May 07 16:42:34 2009 +0200 @@ -189,6 +189,14 @@ 'help': 'web server root url', 'group': 'main', 'inputlevel': 1, }), + ('use-request-subdomain', + {'type' : 'yn', + 'default': None, + 'help': ('if set, base-url subdomain is replaced by the request\'s ' + 'host, to help managing sites with several subdomains in a ' + 'single cubicweb instance'), + 'group': 'main', 'inputlevel': 1, + }), ('mangle-emails', {'type' : 'yn', 'default': False, @@ -669,7 +677,7 @@ return join(self.instance_data_dir(), self.appid) def init_cubes(self, cubes): - assert self._cubes is None + assert self._cubes is None, self._cubes self._cubes = self.reorder_cubes(cubes) # load cubes'__init__.py file first for cube in cubes: diff -r a721966779be -r cba9f175da2d cwvreg.py --- a/cwvreg.py Thu May 07 16:33:22 2009 +0200 +++ b/cwvreg.py Thu May 07 16:42:34 2009 +0200 @@ -113,19 +113,29 @@ def register_objects(self, path, force_reload=None): """overriden to remove objects requiring a missing interface""" if super(CubicWebRegistry, self).register_objects(path, force_reload): - # clear etype cache if you don't want to run into deep weirdness - clear_cache(self, 'etype_class') - # we may want to keep interface dependent objects (e.g.for i18n - # catalog generation) - if not self.config.cleanup_interface_sobjects: - return + self.initialization_completed() + # print registry content + for registry, objects in self.items(): + self.debug('available in registry %s: %s', registry, + sorted(objects)) + for appobjects in objects.itervalues(): + for appobject in appobjects: + appobject.vreg_initialization_completed() + + def initialization_completed(self): + # clear etype cache if you don't want to run into deep weirdness + clear_cache(self, 'etype_class') + # we may want to keep interface dependent objects (e.g.for i18n + # catalog generation) + if self.config.cleanup_interface_sobjects: # remove vobjects that don't support any available interface implemented_interfaces = set() - for etype in self.schema.entities(): - cls = self.etype_class(etype) - for iface in cls.__implements__: - implemented_interfaces.update(iface.__mro__) - implemented_interfaces.update(cls.__mro__) + if 'Any' in self.get('etypes', ()): + for etype in self.schema.entities(): + cls = self.etype_class(etype) + for iface in cls.__implements__: + implemented_interfaces.update(iface.__mro__) + implemented_interfaces.update(cls.__mro__) for obj, ifaces in self._needs_iface.items(): ifaces = frozenset(isinstance(iface, basestring) and iface in self.schema @@ -133,19 +143,12 @@ or iface for iface in ifaces) if not ('Any' in ifaces or ifaces & implemented_interfaces): - self.debug('kicking vobject %s (no implemented interface ' - 'among %s)', obj, ifaces) + self.debug('kicking vobject %s (no implemented ' + 'interface among %s)', obj, ifaces) self.unregister(obj) - # clear needs_iface so we don't try to remove some not-anymore-in - # objects on automatic reloading - self._needs_iface.clear() - # print registry content - for registry, objects in self.items(): - self.debug('available in registry %s: %s', registry, - sorted(objects)) - for appobjects in objects.itervalues(): - for appobject in appobjects: - appobject.vreg_initialization_completed() + # clear needs_iface so we don't try to remove some not-anymore-in + # objects on automatic reloading + self._needs_iface.clear() @cached def etype_class(self, etype): diff -r a721966779be -r cba9f175da2d dbapi.py --- a/dbapi.py Thu May 07 16:33:22 2009 +0200 +++ b/dbapi.py Thu May 07 16:42:34 2009 +0200 @@ -16,7 +16,7 @@ from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry from cubicweb.cwconfig import CubicWebNoAppConfiguration - + _MARKER = object() class ConnectionProperties(object): @@ -29,7 +29,7 @@ def get_repository(method, database=None, config=None, vreg=None): """get a proxy object to the CubicWeb repository, using a specific RPC method. - + Only 'in-memory' and 'pyro' are supported for now. Either vreg or config argument should be given """ @@ -60,10 +60,10 @@ 'you may have to restart your server-side ' 'application' % nsid) return core.getProxyForURI(uri) - + def repo_connect(repo, user, password, cnxprops=None): """Constructor to create a new connection to the CubicWeb repository. - + Returns a Connection instance. """ cnxprops = cnxprops or ConnectionProperties('inmemory') @@ -72,7 +72,7 @@ if cnxprops.cnxtype == 'inmemory': cnx.vreg = repo.vreg return cnx - + def connect(database=None, user=None, password=None, host=None, group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True, initlog=True): @@ -110,7 +110,7 @@ def in_memory_cnx(config, user, password): """usefull method for testing and scripting to get a dbapi.Connection - object connected to an in-memory repository instance + object connected to an in-memory repository instance """ if isinstance(config, CubicWebRegistry): vreg = config @@ -126,7 +126,7 @@ class DBAPIRequest(RequestSessionMixIn): - + def __init__(self, vreg, cnx=None): super(DBAPIRequest, self).__init__(vreg) try: @@ -146,10 +146,10 @@ def base_url(self): return self.vreg.config['base-url'] - + def from_controller(self): return 'view' - + def set_connection(self, cnx, user=None): """method called by the session handler when the user is authenticated or an anonymous connection is open @@ -157,7 +157,7 @@ self.cnx = cnx self.cursor = cnx.cursor(self) self.set_user(user) - + def set_default_language(self, vreg): try: self.lang = vreg.property_value('ui.language') @@ -175,26 +175,26 @@ rset.vreg = self.vreg rset.req = self return rset - + def describe(self, eid): """return a tuple (type, sourceuri, extid) for the entity with id """ return self.cnx.describe(eid) - + def source_defs(self): """return the definition of sources used by the repository.""" return self.cnx.source_defs() - + # entities cache management ############################################### - + def entity_cache(self, eid): return self._eid_cache[eid] - + def set_entity_cache(self, entity): self._eid_cache[entity.eid] = entity def cached_entities(self): return self._eid_cache.values() - + def drop_entity_cache(self, eid=None): if eid is None: self._eid_cache = {} @@ -210,11 +210,11 @@ def get_session_data(self, key, default=None, pop=False): """return value associated to `key` in session data""" return self.cnx.get_session_data(key, default, pop) - + def set_session_data(self, key, value): """set value associated to `key` in session data""" return self.cnx.set_session_data(key, value) - + def del_session_data(self, key): """remove value associated to `key` in session data""" return self.cnx.del_session_data(key) @@ -222,7 +222,7 @@ def get_shared_data(self, key, default=None, pop=False): """return value associated to `key` in shared data""" return self.cnx.get_shared_data(key, default, pop) - + def set_shared_data(self, key, value, querydata=False): """set value associated to `key` in shared data @@ -245,14 +245,14 @@ self._user = user if user: self.set_entity_cache(user) - + def execute(self, *args, **kwargs): """Session interface compatibility""" return self.cursor.execute(*args, **kwargs) set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi')) - - + + # exceptions ################################################################## class ProgrammingError(Exception): #DatabaseError): @@ -288,15 +288,15 @@ """String constant stating the type of parameter marker formatting expected by the interface. Possible values are : - 'qmark' Question mark style, + 'qmark' Question mark style, e.g. '...WHERE name=?' - 'numeric' Numeric, positional style, + 'numeric' Numeric, positional style, e.g. '...WHERE name=:1' - 'named' Named style, + 'named' Named style, e.g. '...WHERE name=:name' - 'format' ANSI C printf format codes, + 'format' ANSI C printf format codes, e.g. '...WHERE name=%s' - 'pyformat' Python extended format codes, + 'pyformat' Python extended format codes, e.g. '...WHERE name=%(name)s' """ paramstyle = 'pyformat' @@ -333,28 +333,28 @@ def request(self): return DBAPIRequest(self.vreg, self) - + def session_data(self): """return a dictionnary containing session data""" return self.data - + def get_session_data(self, key, default=None, pop=False): """return value associated to `key` in session data""" if pop: return self.data.pop(key, default) else: return self.data.get(key, default) - + def set_session_data(self, key, value): """set value associated to `key` in session data""" self.data[key] = value - + def del_session_data(self, key): """remove value associated to `key` in session data""" try: del self.data[key] except KeyError: - pass + pass def check(self): """raise `BadSessionId` if the connection is no more valid""" @@ -363,7 +363,7 @@ def get_shared_data(self, key, default=None, pop=False): """return value associated to `key` in shared data""" return self._repo.get_shared_data(self.sessionid, key, default, pop) - + def set_shared_data(self, key, value, querydata=False): """set value associated to `key` in shared data @@ -373,10 +373,10 @@ repository side. """ return self._repo.set_shared_data(self.sessionid, key, value, querydata) - + def get_schema(self): """Return the schema currently used by the repository. - + This is NOT part of the DB-API. """ if self._closed is not None: @@ -414,10 +414,10 @@ # application specific hooks if self._repo.config.application_hooks: hm.register_hooks(config.load_hooks(self.vreg)) - + def source_defs(self): """Return the definition of sources used by the repository. - + This is NOT part of the DB-API. """ if self._closed is not None: @@ -443,13 +443,13 @@ self.close() except: pass - + def describe(self, eid): return self._repo.describe(self.sessionid, eid) - + def close(self): """Close the connection now (rather than whenever __del__ is called). - + The connection will be unusable from this point forward; an Error (or subclass) exception will be raised if any operation is attempted with the connection. The same applies to all cursor objects trying to use the @@ -465,7 +465,7 @@ """Commit any pending transaction to the database. Note that if the database supports an auto-commit feature, this must be initially off. An interface method may be provided to turn it back on. - + Database modules that do not support transactions should implement this method with void functionality. """ @@ -476,7 +476,7 @@ def rollback(self): """This method is optional since not all databases provide transaction support. - + In case a database does provide transactions this method causes the the database to roll back to the start of any pending transaction. Closing a connection without committing the changes first will cause an implicit @@ -510,7 +510,7 @@ support is implemented (see also the connection's rollback() and commit() methods.) """ - + def __init__(self, connection, repo, req=None): """This read-only attribute return a reference to the Connection object on which the cursor was created. @@ -522,7 +522,7 @@ """This read/write attribute specifies the number of rows to fetch at a time with fetchmany(). It defaults to 1 meaning to fetch a single row at a time. - + Implementations must observe this value with respect to the fetchmany() method, but are free to interact with the database a single row at a time. It may also be used in the implementation of executemany(). @@ -535,7 +535,7 @@ self._closed = None self._index = 0 - + def close(self): """Close the cursor now (rather than whenever __del__ is called). The cursor will be unusable from this point forward; an Error (or subclass) @@ -543,30 +543,30 @@ """ self._closed = True - + def execute(self, operation, parameters=None, eid_key=None, build_descr=True): """Prepare and execute a database operation (query or command). Parameters may be provided as sequence or mapping and will be bound to variables in the operation. Variables are specified in a database-specific notation (see the module's paramstyle attribute for details). - + A reference to the operation will be retained by the cursor. If the same operation object is passed in again, then the cursor can optimize its behavior. This is most effective for algorithms where the same operation is used, but different parameters are bound to it (many times). - + For maximum efficiency when reusing an operation, it is best to use the setinputsizes() method to specify the parameter types and sizes ahead of time. It is legal for a parameter to not match the predefined information; the implementation should compensate, possibly with a loss of efficiency. - + The parameters may also be specified as list of tuples to e.g. insert multiple rows in a single operation, but this kind of usage is depreciated: executemany() should be used instead. - + Return values are not defined by the DB-API, but this here it returns a ResultSet object. """ @@ -575,25 +575,25 @@ self.req.decorate_rset(res) self._index = 0 return res - + def executemany(self, operation, seq_of_parameters): """Prepare a database operation (query or command) and then execute it against all parameter sequences or mappings found in the sequence seq_of_parameters. - + Modules are free to implement this method using multiple calls to the execute() method or by using array operations to have the database process the sequence as a whole in one call. - + Use of this method for an operation which produces one or more result sets constitutes undefined behavior, and the implementation is permitted (but not required) to raise an exception when it detects that a result set has been created by an invocation of the operation. - + The same comments as for execute() also apply accordingly to this method. - + Return values are not defined. """ for parameters in seq_of_parameters: @@ -606,7 +606,7 @@ def fetchone(self): """Fetch the next row of a query result set, returning a single sequence, or None when no more data is available. - + An Error (or subclass) exception is raised if the previous call to execute*() did not produce any result set or no call was issued yet. """ @@ -616,21 +616,21 @@ self._index += 1 return row - + def fetchmany(self, size=None): """Fetch the next set of rows of a query result, returning a sequence of sequences (e.g. a list of tuples). An empty sequence is returned when no more rows are available. - + The number of rows to fetch per call is specified by the parameter. If it is not given, the cursor's arraysize determines the number of rows to be fetched. The method should try to fetch as many rows as indicated by the size parameter. If this is not possible due to the specified number of rows not being available, fewer rows may be returned. - + An Error (or subclass) exception is raised if the previous call to execute*() did not produce any result set or no call was issued yet. - + Note there are performance considerations involved with the size parameter. For optimal performance, it is usually best to use the arraysize attribute. If the size parameter is used, then it is best @@ -644,12 +644,12 @@ self._index += size return rows - + def fetchall(self): """Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples). Note that the cursor's arraysize attribute can affect the performance of this operation. - + An Error (or subclass) exception is raised if the previous call to execute*() did not produce any result set or no call was issued yet. """ @@ -665,39 +665,39 @@ def setinputsizes(self, sizes): """This can be used before a call to execute*() to predefine memory areas for the operation's parameters. - + sizes is specified as a sequence -- one item for each input parameter. The item should be a Type Object that corresponds to the input that will be used, or it should be an integer specifying the maximum length of a string parameter. If the item is None, then no predefined memory area will be reserved for that column (this is useful to avoid predefined areas for large inputs). - + This method would be used before the execute*() method is invoked. - + Implementations are free to have this method do nothing and users are free to not use it. """ pass - + def setoutputsize(self, size, column=None): """Set a column buffer size for fetches of large columns (e.g. LONGs, BLOBs, etc.). The column is specified as an index into the result sequence. Not specifying the column will set the default size for all large columns in the cursor. - + This method would be used before the execute*() method is invoked. - + Implementations are free to have this method do nothing and users are free to not use it. - """ + """ pass - + class LogCursor(Cursor): """override the standard cursor to log executed queries""" - + def execute(self, operation, parameters=None, eid_key=None, build_descr=True): """override the standard cursor to log executed queries""" tstart, cstart = time(), clock() diff -r a721966779be -r cba9f175da2d debian/control --- a/debian/control Thu May 07 16:33:22 2009 +0200 +++ b/debian/control Thu May 07 16:42:34 2009 +0200 @@ -60,8 +60,8 @@ Package: cubicweb-web Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-docutils, python-vobject, python-elementtree -Recommends: fckeditor +Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree +Recommends: python-docutils, python-vobject, fckeditor Description: web interface library for the CubicWeb framework CubicWeb is a semantic web application framework. . @@ -75,8 +75,8 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, python-logilab-mtconverter (>= 0.6.0), python-simpletal (>= 4.0), graphviz, gettext, python-lxml, python-logilab-common (>= 0.39.0), python-yams (>= 0.21.0), python-rql (>= 0.22.0), python-simplejson (>= 1.3) -Recommends: python-psyco +Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.39.0), python-yams (>= 0.22.0), python-rql (>= 0.22.0) +Recommends: python-simpletal (>= 4.0), python-lxml Conflicts: cubicweb-core Replaces: cubicweb-core Description: common library for the CubicWeb framework diff -r a721966779be -r cba9f175da2d devtools/apptest.py --- a/devtools/apptest.py Thu May 07 16:33:22 2009 +0200 +++ b/devtools/apptest.py Thu May 07 16:42:34 2009 +0200 @@ -239,7 +239,7 @@ """remote call simulation""" dump = simplejson.dumps args = [dump(arg) for arg in args] - req = self.request(mode='remote', fname=fname, pageid='123', arg=args) + req = self.request(fname=fname, pageid='123', arg=args) ctrl = self.env.app.select_controller('json', req) return ctrl.publish(), req diff -r a721966779be -r cba9f175da2d devtools/devctl.py --- a/devtools/devctl.py Thu May 07 16:33:22 2009 +0200 +++ b/devtools/devctl.py Thu May 07 16:42:34 2009 +0200 @@ -91,11 +91,10 @@ cleanup_sys_modules(libconfig) if cubedir: config = DevCubeConfiguration(cube) - schema = config.load_schema() else: - schema = config.load_schema() config = libconfig libconfig = None + schema = config.load_schema(remove_unused_rtypes=False) vreg = CubicWebRegistry(config) # set_schema triggers objects registrations vreg.set_schema(schema) @@ -110,7 +109,7 @@ w('# singular and plural forms for each entity type\n') w('\n') if libconfig is not None: - libschema = libconfig.load_schema() + libschema = libconfig.load_schema(remove_unused_rtypes=False) entities = [e for e in schema.entities() if not e in libschema] else: entities = schema.entities() @@ -130,7 +129,7 @@ w('# subject and object forms for each relation type\n') w('# (no object form for final relation types)\n') w('\n') - if libschema is not None: + if libconfig is not None: relations = [r for r in schema.relations() if not r in libschema] else: relations = schema.relations() @@ -154,10 +153,16 @@ for rschema in rschemas: if rschema.is_final(): continue + if libconfig is not None: + librschema = libschema.get(rschema) for teschema in rschema.targets(eschema, role): - if defined_in_library(libschema, eschema, rschema, - teschema, role): - continue + if libconfig is not None and librschema is not None: + if role == 'subject': + subjtype, objtype = eschema, teschema + else: + subjtype, objtype = teschema, eschema + if librschema.has_rdef(subjtype, objtype): + continue if actionbox.relation_mode(rschema, eschema, teschema, role) == 'create': if role == 'subject': label = 'add %s %s %s %s' % (eschema, rschema, @@ -179,7 +184,6 @@ libvreg.set_schema(libschema) # trigger objects registration # prefill done set list(_iter_vreg_objids(libvreg, done)) - print 'done', done for objid in _iter_vreg_objids(vreg, done): add_msg(w, '%s_description' % objid) add_msg(w, objid) @@ -197,7 +201,7 @@ break -def defined_in_library(libschema, etype, rtype, tetype, role): +def defined_in_library(etype, rtype, tetype, role): """return true if the given relation definition exists in cubicweb's library """ if libschema is None: @@ -271,8 +275,12 @@ cmd = 'xgettext --no-location --omit-header -k_ -o %s %s' if lang is not None: cmd += ' -L %s' % lang - potfiles.append(join(tempdir, '%s.pot' % id)) - execute(cmd % (potfiles[-1], ' '.join(files))) + potfile = join(tempdir, '%s.pot' % id) + execute(cmd % (potfile, ' '.join(files))) + if exists(potfile): + potfiles.append(potfile) + else: + print 'WARNING: %s file not generated' % potfile print '******** merging .pot files' cubicwebpot = join(tempdir, 'cubicweb.pot') execute('msgcat %s > %s' % (' '.join(potfiles), cubicwebpot)) diff -r a721966779be -r cba9f175da2d devtools/testlib.py --- a/devtools/testlib.py Thu May 07 16:33:22 2009 +0200 +++ b/devtools/testlib.py Thu May 07 16:42:34 2009 +0200 @@ -110,16 +110,16 @@ # maps vid : validator name (override content_type_validators) vid_validators = dict((vid, VALMAP[valkey]) for vid, valkey in VIEW_VALIDATORS.iteritems()) - + no_auto_populate = () - ignored_relations = () - + ignored_relations = () + def custom_populate(self, how_many, cursor): pass - + def post_populate(self, cursor): pass - + @nocoverage def auto_populate(self, how_many): """this method populates the database with `how_many` entities @@ -185,7 +185,7 @@ # if rset: # print rset, len(rset), id(rset) # else: - # print + # print req.form['vid'] = vid view = self.vreg.select_view(vid, req, rset, **kwargs) # set explicit test description @@ -243,7 +243,7 @@ def to_test_etypes(self): return unprotected_entities(self.schema, strict=True) - + def iter_automatic_rsets(self, limit=10): """generates basic resultsets for each entity type""" etypes = self.to_test_etypes() @@ -260,7 +260,7 @@ for rql in self.application_rql: yield self.execute(rql) - + def list_views_for(self, rset): """returns the list of views that can be applied on `rset`""" req = rset.req @@ -297,7 +297,7 @@ req = rset.req for box in self.vreg.possible_objects('boxes', req, rset): yield box - + def list_startup_views(self): """returns the list of startup views""" req = self.request() @@ -306,7 +306,7 @@ yield view.id else: not_selected(self.vreg, view) - + def _test_everything_for(self, rset): """this method tries to find everything that can be tested for `rset` and yields a callable test (as needed in generative tests) @@ -332,7 +332,7 @@ @staticmethod def _testname(rset, objid, objtype): return '%s_%s_%s' % ('_'.join(rset.column_types(0)), objid, objtype) - + class AutomaticWebTest(WebTest): """import this if you wan automatic tests to be ran""" @@ -349,7 +349,7 @@ for rset in self.iter_automatic_rsets(limit=10): for testargs in self._test_everything_for(rset): yield testargs - + ## startup views def test_startup_views(self): for vid in self.list_startup_views(): @@ -374,7 +374,7 @@ vreg._selected[vobject.__class__] -= 1 except (KeyError, AttributeError): pass - + def vreg_instrumentize(testclass): from cubicweb.devtools.apptest import TestEnvironment env = testclass._env = TestEnvironment('data', configcls=testclass.configcls, diff -r a721966779be -r cba9f175da2d doc/book/en/B0015-define-permissions.en.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/B0015-define-permissions.en.txt Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,188 @@ +.. -*- coding: utf-8 -*- + +The security model +------------------ + +The security model of `cubicWeb` is based on `Access Control List`. +The main principles are: + +* users and groups of users +* a user belongs to at least one group of user +* permissions (read, update, create, delete) +* permissions are assigned to groups (and not to users) + +For `CubicWeb` in particular: + +* we associate rights at the enttities/relations schema level +* for each entity, we distinguish four kind of permissions: read, + add, update and delete +* for each relation, we distinguish three king of permissions: read, + add and delete (we can not modify a relation) +* the basic groups are: Administrators, Users and Guests +* by default, users belongs to the group Users +* there is a virtual group called `Owners users` to which we + can associate only deletion and update permissions +* we can not add users to the `Owners users` group, they are + implicetely added to it according to the context of the objects + they own +* the permissions of this group are only be checked on update/deletion + actions if all the other groups the user belongs does not provide + those permissions + + +Permissions definition +`````````````````````` + +Setting permissions is done with the attribute `permissions` of entities and +relation types. It defines a dictionary where the keys are the access types +(action), and the values are the authorized groups or expressions. + +For an entity type, the possible actions are `read`, `add`, `update` and +`delete`. + +For a relation type, the possible actions are `read`, `add`, and `delete`. + +For each access type, a tuple indicates the name of the authorized groups and/or +one or multiple RQL expressions to satisfy to grant access. The access is +provided once the user is in the listed groups or one of the RQL condition is +satisfied. + +The standard groups are : + +* `guests` + +* `users` + +* `managers` + +* `owners` : virtual group corresponding to the entity's owner. + This can only be used for the actions `update` and `delete` of an entity + type. + +It is also possible to use specific groups if they are defined in the precreate +of the cube (``migration/precreate.py``). + + +Use of RQL expression for writing rights +```````````````````````````````````````` + +It is possible to define RQL expression to provide update permission +(`add`, `delete` and `update`) on relation and entity types. + +RQL expression for entity type permission : + +* you have to use the class `ERQLExpression` + +* the used expression corresponds to the WHERE statement of an RQL query + +* in this expression, the variables X and U are pre-defined references + respectively on the current entity (on which the action is verified) and + on the user who send the request + +* it is possible to use, in this expression, a special relation + "has__permission" where the subject is the user and the + object is a any variable, meaning that the user needs to have + permission to execute the action on the entities related + to this variable + +For RQL expressions on a relation type, the principles are the same except +for the following : + +* you have to use the class `RQLExpression` in the case of a non-final relation + +* in the expression, the variables S, O and U are pre-defined references + to respectively the subject and the object of the current relation (on + which the action is being verified) and the user who executed the query + +* we can also defined rights on attributes of an entity (non-final relation), + knowing that : + + - to defines RQL expression, we have to use the class `ERQLExpression` + in which X represents the entity the attribute belongs to + + - the permissions `add` and `delete` are equivalent. Only `add`/`read` + are actually taken in consideration. + +In addition to that the entity type `EPermission` from the standard library +allow to build very complex and dynamic security architecture. The schema of +this entity type is as follow : :: + + class EPermission(MetaEntityType): + """entity type that may be used to construct some advanced security configuration + """ + name = String(required=True, indexed=True, internationalizable=True, maxsize=100) + require_group = SubjectRelation('EGroup', cardinality='+*', + description=_('groups to which the permission is granted')) + require_state = SubjectRelation('State', + description=_("entity'state in which the permission is applyable")) + # can be used on any entity + require_permission = ObjectRelation('**', cardinality='*1', composite='subject', + description=_("link a permission to the entity. This " + "permission should be used in the security " + "definition of the entity's type to be useful.")) + + +Example of configuration :: + + + ... + + class Version(EntityType): + """a version is defining the content of a particular project's release""" + + permissions = {'read': ('managers', 'users', 'guests',), + 'update': ('managers', 'logilab', 'owners',), + 'delete': ('managers', ), + 'add': ('managers', 'logilab', + ERQLExpression('X version_of PROJ, U in_group G,' + 'PROJ require_permission P, P name "add_version",' + 'P require_group G'),)} + + ... + + class version_of(RelationType): + """link a version to its project. A version is necessarily linked to one and only one project. + """ + permissions = {'read': ('managers', 'users', 'guests',), + 'delete': ('managers', ), + 'add': ('managers', 'logilab', + RRQLExpression('O require_permission P, P name "add_version",' + 'U in_group G, P require_group G'),) + } + inlined = True + +This configuration indicates that an entity `EPermission` named +"add_version" can be associated to a project and provides rights to create +new versions on this project to specific groups. It is important to notice that : + +* in such case, we have to protect both the entity type "Version" and the relation + associating a version to a project ("version_of") + +* because of the genricity of the entity type `EPermission`, we have to execute + a unification with the groups and/or the states if necessary in the expression + ("U in_group G, P require_group G" in the above example) + +Use of RQL expression for reading rights +```````````````````````````````````````` + +The principles are the same but with the following restrictions : + +* we can not use `RRQLExpression` on relation types for reading + +* special relations "has__permission" can not be used + + +Note on the use of RQL expression for `add` permission +`````````````````````````````````````````````````````` +Potentially, the use of an RQL expression to add an entity or a relation +can cause problems for the user interface, because if the expression uses +the entity or the relation to create, then we are not able to verify the +permissions before we actually add the entity (please note that this is +not a problem for the RQL server at all, because the permissions checks are +done after the creation). In such case, the permission check methods +(check_perm, has_perm) can indicate that the user is not allowed to create +this entity but can obtain the permission. +To compensate this problem, it is usually necessary, for such case, +to use an action that reflects the schema permissions but which enables +to check properly the permissions so that it would show up if necessary. + diff -r a721966779be -r cba9f175da2d doc/book/en/annexes/faq.rst --- a/doc/book/en/annexes/faq.rst Thu May 07 16:33:22 2009 +0200 +++ b/doc/book/en/annexes/faq.rst Thu May 07 16:42:34 2009 +0200 @@ -11,7 +11,8 @@ http://groups.google.com/group/google-appengine/browse_frm/thread/f48cf6099973aef5/c28cd6934dd72457 ] -* Why does not CubicWeb have a template language ? +Why does not CubicWeb have a template language ? +------------------------------------------------ There are enough template languages out there. You can use your preferred template language if you want. [explain how to use a @@ -28,7 +29,8 @@ The reason template languages are not used in this book is that experience has proved us that using pure python was less cumbersome. -* Why do you think using pure python is better than using a template language ? +Why do you think using pure python is better than using a template language ? +----------------------------------------------------------------------------- Python is an Object Oriented Programming language and as such it already provides a consistent and strong architecture and syntax @@ -43,7 +45,8 @@ we use standard OOP techniques and this is a key factor in a robust application. -* Why do you use the GPL license to prevent me from doing X ? +Why do you use the GPL license to prevent me from doing X ? +----------------------------------------------------------- GPL means that *if* you redistribute your application, you need to redistribute it *and* the changes you made *and* the code _linked_ @@ -58,14 +61,16 @@ clients. -* CubicWeb looks pretty recent. Is it stable ? +CubicWeb looks pretty recent. Is it stable ? +-------------------------------------------- It is constantly evolving, piece by piece. The framework has evolved over the past seven years and data has been migrated from one schema to the other ever since. There is a well-defined way to handle data and schema migration. -* Why is the RQL query language looking similar to X ? +Why is the RQL query language looking similar to X ? +----------------------------------------------------- It may remind you of SQL but it is higher level than SQL, more like SPARQL. Except that SPARQL did not exist when we started the project. @@ -89,12 +94,12 @@ [copy answer from forum, explain why similar to sparql and why better than django and SQL] -* which ajax library - +which ajax library +------------------ [we use jquery and things on top of that] -* `Error while publishing rest text ...` - +`Error while publishing rest text ...` +-------------------------------------- While modifying the description of an entity, you get an error message in the application `Error while publishing ...` for Rest text and plain text. The server returns a traceback like as follows :: @@ -110,7 +115,8 @@ This can be fixed by applying the patch described in : http://code.google.com/p/googleappengine/issues/detail?id=48 -* What are hooks used for? +What are hooks used for? +------------------------ Hooks are executed around (actually before or after) events. The most common events are data creation, update and deletion. They @@ -123,19 +129,22 @@ Other kinds of hooks, called Operations, are available for execution just before commit. -* When should you define an HTML template rather than define a graphical component? +When should you define an HTML template rather than define a graphical component? +--------------------------------------------------------------------------------- An HTML template cannot contain code, hence it is only about static content. A component is made of code and operations that apply on a well defined context (request, result set). It enables much more dynamic views. -* What is the difference between `AppRsetObject` and `AppObject` ? +What is the difference between `AppRsetObject` and `AppObject` ? +---------------------------------------------------------------- `AppRsetObject` instances are selected on a request and a result set. `AppObject` instances are directly selected by id. -* How to update a database after a schema modification? +How to update a database after a schema modification? +----------------------------------------------------- It depends on what has been modified in the schema. @@ -150,7 +159,8 @@ * Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``. -* How to create an anonymous user? +How to create an anonymous user? +-------------------------------- This allows to bypass authentication for your site. In the ``all-in-one.conf`` file of your instance, define the anonymous user @@ -175,7 +185,8 @@ decribed above. -* How to change the application logo? +How to change the application logo? +----------------------------------- There are two ways of changing the logo. @@ -193,7 +204,8 @@ where DATADIR is ``mycubes/data``. -* How to configure LDAP source? +How to configure LDAP source? +------------------------------- Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``. Configuring an LDAP source is about declaring that source in your @@ -219,7 +231,8 @@ Any change applied to configuration file requires to restart your application. -* I get NoSelectableObject exceptions: how do I debug selectors ? +I get NoSelectableObject exceptions: how do I debug selectors ? +--------------------------------------------------------------- You just need to put the appropriate context manager around view/component selection: :: @@ -233,7 +246,8 @@ 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for -* How to format an entity date attribute? +How to format an entity date attribute? +--------------------------------------- If your schema has an attribute of type Date or Datetime, you might want to format it. First, you should define your preferred format using @@ -242,3 +256,82 @@ Then in the view code, use:: self.format_date(entity.date_attribute) + +Can PostgreSQL and CubicWeb authentication work with kerberos ? +---------------------------------------------------------------- + + If you have postgresql set up to accept kerberos authentication, you can set + the db-host, db-name and db-user parameters in the `sources` configuration + file while leaving the password blank. It should be enough for your instance + to connect to postgresql with a kerberos ticket. + + +How to load data from a script? +------------------------------- + + The following script aims at loading data within a script assuming pyro-nsd is + running and your application is configured with ``pyro-server=yes``, otherwise + you would not be able to use dbapi. :: + + from cubicweb import dbapi + + cnx = dbapi.connection(database='instance-id', user='admin', password='admin') + cur = cnx.cursor() + for name in ('Personal', 'Professional', 'Computers'): + cur.execute('INSERT Blog B: B name %s', name) + cnx.commit() + +What is the CubicWeb datatype corresponding to GAE datastore's UserProperty? +---------------------------------------------------------------------------- + + If you take a look at your application schema and + click on "display detailed view of metadata" you will see that there + is a Euser entity in there. That's the one that is modeling users. The + thing that corresponds to a UserProperty is a relationship between + your entity and the Euser entity. As in :: + + class TodoItem(EntityType): + text = String() + todo_by = SubjectRelation('Euser') + + [XXX check that cw handle users better by + mapping Google Accounts to local Euser entities automatically] + + +How to implement security? +-------------------------- + + This is an example of how it works in our framework:: + + class Version(EntityType): + """a version is defining the content of a particular project's + release""" + # definition of attributes is voluntarily missing + permissions = {'read': ('managers', 'users', 'guests',), + 'update': ('managers', 'logilab', 'owners',), + 'delete': ('managers', ), + 'add': ('managers', 'logilab', + ERQLExpression('X version_of PROJ, U in_group G, PROJ + require_permission P, P name "add_version", P require_group G'),)} + + The above means that permission to read a Version is granted to any + user that is part of one of the groups 'managers', 'users', 'guests'. + The 'add' permission is granted to users in group 'managers' or + 'logilab' and to users in group G, if G is linked by a permission + entity named "add_version" to the version's project. + :: + + class version_of(RelationType): + """link a version to its project. A version is necessarily linked + to one and only one project. """ + # some lines voluntarily missing + permissions = {'read': ('managers', 'users', 'guests',), + 'delete': ('managers', ), + 'add': ('managers', 'logilab', + RRQLExpression('O require_permission P, P name "add_version", + 'U in_group G, P require_group G'),) } + + You can find additional information in the section :ref:`security`. + + [XXX what does the second example means in addition to the first one?] + diff -r a721966779be -r cba9f175da2d doc/book/en/development/datamodel/index.rst --- a/doc/book/en/development/datamodel/index.rst Thu May 07 16:33:22 2009 +0200 +++ b/doc/book/en/development/datamodel/index.rst Thu May 07 16:42:34 2009 +0200 @@ -10,4 +10,4 @@ metadata baseschema -.. comment: define-workflows +.. define-workflows diff -r a721966779be -r cba9f175da2d doc/book/en/intro/index.rst --- a/doc/book/en/intro/index.rst Thu May 07 16:33:22 2009 +0200 +++ b/doc/book/en/intro/index.rst Thu May 07 16:42:34 2009 +0200 @@ -6,8 +6,8 @@ Part I - Introduction to `CubicWeb` ----------------------------------- -This first part of the book will offer different reading path to -present you with the `CubicWeb` framework, provide a tutorial to get a quick +This first part of the book will present different reading path to +discover the `CubicWeb` framework, provide a tutorial to get a quick overview of its features and list its key concepts. diff -r a721966779be -r cba9f175da2d doc/book/en/intro/tutorial/blog-less-ten-minutes.rst --- a/doc/book/en/intro/tutorial/blog-less-ten-minutes.rst Thu May 07 16:33:22 2009 +0200 +++ b/doc/book/en/intro/tutorial/blog-less-ten-minutes.rst Thu May 07 16:42:34 2009 +0200 @@ -17,7 +17,7 @@ cubicweb-ctl start -D myblog -This is it. Your blog is ready to you. Go to http://localhost:8080 and enjoy!! +This is it. Your blog is ready to you. Go to http://localhost:8080 and enjoy! As a developper, you'll want to know more about how to develop new cubes and cutomize the look of your application and this is what we diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/ARPA.py --- a/embedded/mx/DateTime/ARPA.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,227 +0,0 @@ -""" This module provides a set of constructors and routines to convert - between DateTime[Delta] instances and ARPA representations of date - and time. The format is specified by RFC822 + RFC1123. - - Note: Timezones are only interpreted by ParseDateTimeGMT(). All - other constructors silently ignore the time zone information. - - Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com - Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com - See the documentation for further information on copyrights, - or contact the author. All Rights Reserved. - -""" -import DateTime,Timezone -import re,string - -# Grammar: RFC822 + RFC1123 + depreciated RFC850 -_litday = '(?PMon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*' -_litmonth = '(?PJan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'\ - '[a-z]*' -_date = ('(?:(?P\d?\d)(?: +' + _litmonth + - ' +|-(?P\d?\d)-)(?P(?:\d\d)?\d\d))') -_zone = Timezone.zone -_time = ('(?:(?P\d\d):(?P\d\d)' - '(?::(?P\d\d))?(?: +'+_zone+')?)') -# Timezone information is made optional because some mail apps -# forget to add it (most of these seem to be spamming engines, btw). -# It defaults to UTC. - -_arpadate = '(?:'+ _litday + ',? )? *' + _date -_arpadatetime = '(?:'+ _litday + ',? )? *' + _date + ' +' + _time - -# We are not strict about the extra characters: some applications -# add extra information to the date header field. Additional spaces -# between the fields and extra characters in the literal day -# and month fields are also silently ignored. - -arpadateRE = re.compile(_arpadate) -arpadatetimeRE = re.compile(_arpadatetime) - -# Translation tables -litdaytable = {'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6 } -litmonthtable = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, - 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 } -_days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] -_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] - -def ParseDate(arpastring,parse_arpadate=arpadateRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof, - lower=string.lower): - - """ParseDate(arpastring) - - Returns a DateTime instance reflecting the given ARPA - date. Only the date part is parsed, any time part will be - ignored. The instance's time is set to 0:00:00. - - """ - s = strip(arpastring) - date = parse_arpadate(s) - if not date: - raise ValueError,'wrong format' - litday,day,litmonth,month,year = date.groups() - if len(year) == 2: - year = DateTime.add_century(atoi(year)) - else: - year = atoi(year) - if litmonth: - litmonth = lower(litmonth) - try: - month = litmonthtable[litmonth] - except KeyError: - raise ValueError,'wrong month format' - else: - month = atoi(month) - day = atoi(day) - # litday and timezone are ignored - return DateTime.DateTime(year,month,day) - -def ParseDateTime(arpastring,parse_arpadatetime=arpadatetimeRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof, - lower=string.lower): - - """ParseDateTime(arpastring) - - Returns a DateTime instance reflecting the given ARPA date assuming - it is local time (timezones are silently ignored). - """ - s = strip(arpastring) - date = parse_arpadatetime(s) - if not date: - raise ValueError,'wrong format or unknown time zone' - litday,day,litmonth,month,year,hour,minute,second,zone = date.groups() - if len(year) == 2: - year = DateTime.add_century(atoi(year)) - else: - year = atoi(year) - if litmonth: - litmonth = lower(litmonth) - try: - month = litmonthtable[litmonth] - except KeyError: - raise ValueError,'wrong month format' - else: - month = atoi(month) - day = atoi(day) - hour = atoi(hour) - minute = atoi(minute) - if second is None: - second = 0.0 - else: - second = atof(second) - # litday and timezone are ignored - return DateTime.DateTime(year,month,day,hour,minute,second) - -def ParseDateTimeGMT(arpastring,parse_arpadatetime=arpadatetimeRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof, - lower=string.lower): - - """ParseDateTimeGMT(arpastring) - - Returns a DateTime instance reflecting the given ARPA date converting - it to UTC (timezones are honored). - """ - s = strip(arpastring) - date = parse_arpadatetime(s) - if not date: - raise ValueError,'wrong format or unknown time zone' - litday,day,litmonth,month,year,hour,minute,second,zone = date.groups() - if len(year) == 2: - year = DateTime.add_century(atoi(year)) - else: - year = atoi(year) - if litmonth: - litmonth = lower(litmonth) - try: - month = litmonthtable[litmonth] - except KeyError: - raise ValueError,'wrong month format' - else: - month = atoi(month) - day = atoi(day) - hour = atoi(hour) - minute = atoi(minute) - if second is None: - second = 0.0 - else: - second = atof(second) - offset = Timezone.utc_offset(zone) - # litday is ignored - return DateTime.DateTime(year,month,day,hour,minute,second) - offset - -# Alias -ParseDateTimeUTC = ParseDateTimeGMT - -def str(datetime,tz=None): - - """str(datetime,tz=DateTime.tz_offset(datetime)) - - Returns the datetime instance as ARPA date string. tz can be given - as DateTimeDelta instance providing the time zone difference from - datetime's zone to UTC. It defaults to - DateTime.tz_offset(datetime) which assumes local time. """ - - if tz is None: - tz = datetime.gmtoffset() - return '%s, %02i %s %04i %02i:%02i:%02i %+03i%02i' % ( - _days[datetime.day_of_week], datetime.day, - _months[datetime.month], datetime.year, - datetime.hour, datetime.minute, datetime.second, - tz.hour,tz.minute) - -def strGMT(datetime): - - """ strGMT(datetime) - - Returns the datetime instance as ARPA date string assuming it - is given in GMT. """ - - return '%s, %02i %s %04i %02i:%02i:%02i GMT' % ( - _days[datetime.day_of_week], datetime.day, - _months[datetime.month], datetime.year, - datetime.hour, datetime.minute, datetime.second) - -def strUTC(datetime): - - """ strUTC(datetime) - - Returns the datetime instance as ARPA date string assuming it - is given in UTC. """ - - return '%s, %02i %s %04i %02i:%02i:%02i UTC' % ( - _days[datetime.day_of_week], datetime.day, - _months[datetime.month], datetime.year, - datetime.hour, datetime.minute, datetime.second) - -def _test(): - import sys, os, rfc822 - file = os.path.join(os.environ['HOME'], 'nsmail/Inbox') - f = open(file, 'r') - while 1: - m = rfc822.Message(f) - if not m: - break - print 'From:', m.getaddr('from') - print 'To:', m.getaddrlist('to') - print 'Subject:', m.getheader('subject') - raw = m.getheader('date') - try: - date = ParseDateTimeUTC(raw) - print 'Date:',strUTC(date) - except ValueError,why: - print 'PROBLEMS:',repr(raw),'-->',why - raw_input('...hit return to continue') - print - # Netscape mail file - while 1: - line = f.readline() - if line[:6] == 'From -': - break - -if __name__ == '__main__': - _test() diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/DateTime.py --- a/embedded/mx/DateTime/DateTime.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1054 +0,0 @@ -""" Python part of the low-level DateTime[Delta] type implementation. - - Copyright (c) 1998-2001, Marc-Andre Lemburg; mailto:mal@lemburg.com - Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com - See the documentation for further information on copyrights, - or contact the author. All Rights Reserved. -""" -# Import the python implementation module -from mxDateTime_python import * -from mxDateTime_python import __version__ - -# Singletons -oneSecond = DateTimeDelta(0,0,0,1) -oneMinute = DateTimeDelta(0,0,1) -oneHour = DateTimeDelta(0,1) -oneDay = DateTimeDelta(1) -oneWeek = DateTimeDelta(7) -Epoch = DateTimeFromAbsDateTime(1,0) - -# Shortcuts for pickle; for backward compatibility only (they are now -# defined in __init__.py to further reduce the pickles length) -def _DT(absdate,abstime): - return DateTimeFromAbsDateTime(absdate,abstime) -def _DTD(seconds): - return DateTimeDeltaFromSeconds(seconds) - -# Module init -class modinit: - - global _time,_string,_math,_types - import time,string,math,types - _time = time - _string = string - _math = math - _types = types - -del modinit - -### Helpers - -def _isstring(arg, - - isinstance=isinstance, types=_types): - - if isinstance(arg, types.StringType): - return 1 - try: - if isinstance(arg, types.UnicodeType): - return 1 - except AttributeError: - pass - return 0 - -### Compatibility APIs - -# Aliases and functions to make 'from mx.DateTime import *' work much -# like 'from time import *' - -def localtime(ticks=None, - # Locals: - time=_time.time,float=float,localtime=_time.localtime, - round=round,int=int,DateTime=DateTime,floor=_math.floor): - - """localtime(ticks=None) - - Construct a DateTime instance using local time from ticks. If - ticks are not given, it defaults to the current time. The - result is similar to time.localtime(). Fractions of a second - are rounded to the nearest micro-second. - - """ - if ticks is None: - ticks = time() - else: - ticks = float(ticks) - ticks = round(ticks, 6) - fticks = floor(ticks) - Y,M,D,h,m,s = localtime(fticks)[:6] - s = s + (ticks - fticks) - return DateTime(Y,M,D,h,m,s) - -def gmtime(ticks=None, - # Locals: - time=_time.time,float=float,gmtime=_time.gmtime, - round=round,int=int,DateTime=DateTime,floor=_math.floor): - - """gmtime(ticks=None) - - Construct a DateTime instance using UTC time from ticks. If - ticks are not given, it defaults to the current time. The - result is similar to time.gmtime(). Fractions of a second are - rounded to the nearest micro-second. - - """ - if ticks is None: - ticks = time() - else: - ticks = float(ticks) - ticks = round(ticks, 6) - fticks = floor(ticks) - Y,M,D,h,m,s = gmtime(ticks)[:6] - s = s + (ticks - fticks) - return DateTime(Y,M,D,h,m,s) - -def mktime((year,month,day,hour,minute,second,dow,doy,dst), - # Locals: - DateTime=DateTime): - - """mktime((year,month,day,hour,minute,second,dow,doy,dst)) - - Same as the DateTime() constructor accept that the interface - used is compatible to the similar time.mktime() API. - - Note that the tuple elements dow, doy and dst are not used in - any way. - - """ - return DateTime(year,month,day,hour,minute,second) - -def ctime(datetime): - - """ctime(datetime) - - Returns a string representation of the given DateTime instance - using the current locale's default settings. - - """ - return datetime.strftime('%c') - -def today(hour=0,minute=0,second=0.0, - # Locals: - localtime=_time.localtime,time=_time.time,DateTime=DateTime): - - """today(hour=0,minute=0,second=0.0) - - Returns a DateTime instance for today (in local time) at the - given time (defaults to midnight). - - """ - Y,M,D = localtime(time())[:3] - return DateTime(Y,M,D,hour,minute,second) - -def TimeDelta(hours=0.0,minutes=0.0,seconds=0.0, - # Locals: - DateTimeDelta=DateTimeDelta): - - """TimeDelta(hours=0.0,minutes=0.0,seconds=0.0) - - Returns a DateTimeDelta-object reflecting the given time - delta. Seconds can be given as float to indicate fractions. - - """ - return DateTimeDelta(0,hours,minutes,seconds) - -def gm2local(datetime): - - """ gm2local(datetime) - - Convert a DateTime instance holding UTC time to a DateTime - instance using local time. - - """ - return localtime(datetime.gmticks()) - -def local2gm(datetime): - - """ local2gm(datetime) - - Convert a DateTime instance holding local time to a DateTime - instance using UTC time. - - """ - return gmtime(datetime.ticks()) - -# Alias -gmt = utc - -# Default value for DateTimeFromTJD's tjd_myriad parameter -current_myriad = localtime().tjd_myriad - -def DateTimeFromTJD(tjd,tjd_myriad=current_myriad): - - """ DateTimeFromTJD(tjd[,myriad]) - - Return a DateTime instance for the given Truncated Julian Day. - myriad defaults to the TJD myriad current at package import - time. - - Note that this version of Truncated Julian Day number does - real truncation of important information. It's use is - discouraged and unsupported. - - """ - return DateTimeFromAbsDays(tjd + tjd_myriad * 10000.0 - 1721425.0) - -def DateTimeFromJDN(jdn): - - """ DateTimeFromJDN(jdn) - - Return a DateTime instance for the given Julian Day Number. - - References: - ----------- - Gregorian 2000-01-01 12:00:00 corresponds to JDN 2451545.0. - Gregorian 1858-11-17 00:00:00.00 corresponds to JDN 2400000.5; MJD 0.0. - Julian -4712-01-01 12:00:00.00 corresponds to JDN 0.0. - Gregorian -4713-11-24 12:00:00.00 corresponds to JDN 0.0. - - """ - return DateTimeFromAbsDays(jdn - 1721425.5) - -def DateTimeFromMJD(mjd): - - """ DateTimeFromMJD(mjd) - - Return a DateTime instance for the given Modified Julian Day - (MJD). The MJD is calculated the same way as the JDN except - that 1858-11-17 00:00:00.00 is taken as origin of the scale. - - """ - return DateTimeFromAbsDays(mjd + 678575.0) - -def DateTimeFrom(*args, **kws): - - """ DateTimeFrom(*args, **kws) - - Generic DateTime instance constructor. Can handle parsing - strings, numbers and keywords. - - XXX Add support for Unicode. - - """ - if len(args) == 1: - # Single argument - arg = args[0] - argtype = type(arg) - if _isstring(arg): - import Parser - return apply(Parser.DateTimeFromString, args, kws) - elif argtype is DateTimeType: - return arg - elif argtype is DateTimeDeltaType: - raise TypeError,'cannot convert DateTimeDelta to DateTime' - else: - try: - value = float(arg) - except (TypeError, ValueError): - value = int(arg) - assert not kws - return DateTimeFromTicks(value) - - elif len(args) > 1: - # More than one argument - if len(args) == 2 and _isstring(args[0]) and _isstring(args[1]): - # interpret as date and time string - import Parser - return apply(Parser.DateTimeFromString, - (args[0] + ' ' + args[1],), - kws) - - # Assume the arguments are the same as for DateTime() - return apply(DateTime, args, kws) - - elif len(kws) > 0: - # Keyword arguments; add defaults... today at 0:00:00 - hour = kws.get('hour',0) - minute = kws.get('minute',0) - second = kws.get('second',0) - today = now() - day = kws.get('day',today.day) - month = kws.get('month',today.month) - year = kws.get('year',today.year) - return DateTime(year,month,day,hour,minute,second) - - else: - raise TypeError,'cannot convert arguments to DateTime' - -def DateTimeDeltaFrom(*args, **kws): - - """ DateTimeDeltaFrom(*args, **kws) - - Generic DateTimeDelta instance constructor. Can handle parsing - strings, numbers and keywords. - - XXX Add support for Unicode. - - """ - if len(args) == 1: - # Single argument - arg = args[0] - if _isstring(arg): - import Parser - return apply(Parser.DateTimeDeltaFromString, args, kws) - elif type(arg) is DateTimeDeltaType: - return arg - elif type(arg) is DateTimeType: - raise TypeError,'cannot convert DateTime to DateTimeDelta' - else: - try: - value = float(arg) - except TypeError: - value = int(arg) - assert not kws - return DateTimeDeltaFromSeconds(value) - - elif len(args) > 1: - # Assume the arguments are the same as for DateTimeDelta() - return apply(DateTimeDelta, args, kws) - - elif len(kws) > 0: - # Keyword arguments; default: 00:00:00:00.00 - hours = kws.get('hours',0) - minutes = kws.get('minutes',0) - seconds = kws.get('seconds',0.0) - days = kws.get('days',0) - return DateTimeDelta(days,hours,minutes,seconds) - - else: - raise TypeError,'cannot convert arguments to DateTimeDelta' - -def TimeDeltaFrom(*args, **kws): - - """ TimeDeltaFrom(*args, **kws) - - Generic TimeDelta instance constructor. Can handle parsing - strings, numbers and keywords. - - XXX Add support for Unicode. - - """ - if len(args) > 1: - # Assume the arguments are the same as for TimeDelta(): without - # days part ! - return apply(DateTimeDelta, (0,)+args, kws) - else: - # Otherwise treat the arguments just like for DateTimeDelta - # instances. - return apply(DateTimeDeltaFrom, args, kws) - -def DateFromTicks(ticks, - # Locals: - DateTime=DateTime,localtime=_time.localtime): - - """ DateFromTicks(ticks) - - Constructs a DateTime instance pointing to the local time date - at 00:00:00.00 (midnight) indicated by the given ticks value. - The time part is ignored. - - """ - return apply(DateTime, localtime(ticks)[:3]) - -def TimestampFromTicks(ticks, - # Locals: - DateTime=DateTime,localtime=_time.localtime): - - """ TimestampFromTicks(ticks) - - Constructs a DateTime instance pointing to the local date and - time indicated by the given ticks value. - - """ - return apply(DateTime, localtime(ticks)[:6]) - -def TimeFromTicks(ticks, - # Locals: - DateTimeDelta=DateTimeDelta,localtime=_time.localtime): - - """ TimeFromTicks(ticks) - - Constructs a DateTimeDelta instance pointing to the local time - indicated by the given ticks value. The date part is ignored. - - """ - return apply(DateTimeDelta, (0,) + localtime(ticks)[3:6]) - -# Aliases -utctime = gmtime -utc2local = gm2local -local2utc = local2gm -DateTimeFromTicks = localtime -Date = DateTime -Time = TimeDelta -Timestamp = DateTime -DateFrom = DateTimeFrom # XXX should only parse the date part ! -TimeFrom = TimeDeltaFrom -TimestampFrom = DateTimeFrom -GregorianDateTime = DateTime -GregorianDate = Date -JulianDate = JulianDateTime - - -### For backward compatibility (these are depreciated): - -def gmticks(datetime): - - """gmticks(datetime) - - [DEPRECIATED: use the .gmticks() method] - - Returns a ticks value based on the values stored in - datetime under the assumption that they are given in UTC, - rather than local time. - - """ - return datetime.gmticks() - -# Alias -utcticks = gmticks - -def tz_offset(datetime, - # Locals: - oneSecond=oneSecond): - - """tz_offset(datetime) - - [DEPRECIATED: use the .gmtoffset() method] - - Returns a DateTimeDelta instance representing the UTC - offset for datetime assuming that the stored values refer - to local time. If you subtract this value from datetime, - you'll get UTC time. - - """ - return datetime.gmtoffset() - -### Constants (only English; see Locale.py for other languages) - -# Weekdays -Monday = 0 -Tuesday = 1 -Wednesday = 2 -Thursday = 3 -Friday = 4 -Saturday = 5 -Sunday = 6 -# as mapping -Weekday = {'Saturday': 5, 6: 'Sunday', 'Sunday': 6, 'Thursday': 3, - 'Wednesday': 2, 'Friday': 4, 'Tuesday': 1, 'Monday': 0, - 5: 'Saturday', 4: 'Friday', 3: 'Thursday', 2: 'Wednesday', - 1: 'Tuesday', 0: 'Monday'} - -# Months -January = 1 -February = 2 -March = 3 -April = 4 -May = 5 -June = 6 -July = 7 -August = 8 -September = 9 -October = 10 -November = 11 -December = 12 -# as mapping -Month = {2: 'February', 3: 'March', None: 0, 'July': 7, 11: 'November', - 'December': 12, 'June': 6, 'January': 1, 'September': 9, 'August': - 8, 'March': 3, 'November': 11, 'April': 4, 12: 'December', 'May': - 5, 10: 'October', 9: 'September', 8: 'August', 7: 'July', 6: - 'June', 5: 'May', 4: 'April', 'October': 10, 'February': 2, 1: - 'January', 0: None} - -# Limits (see also the range checks in mxDateTime.c) -MaxDateTime = DateTime(5867440,12,31) -MinDateTime = DateTime(-5851455,1,1) -MaxDateTimeDelta = DateTimeDeltaFromSeconds(2147483647 * 86400.0) -MinDateTimeDelta = -MaxDateTimeDelta - -### - -class RelativeDateTime: - - """RelativeDateTime(years=0,months=0,days=0, - hours=0,minutes=0,seconds=0, - year=0,month=0,day=0, - hour=None,minute=None,second=None, - weekday=None,weeks=None) - - Returns a RelativeDateTime instance for the specified relative - time. The constructor handles keywords, so you'll only have to - give those parameters which should be changed when you add the - relative to an absolute DateTime instance. - - Adding RelativeDateTime instances is supported with the - following rules: deltas will be added together, right side - absolute values override left side ones. - - Adding RelativeDateTime instances to DateTime instances will - return DateTime instances with the appropriate calculations - applied, e.g. to get a DateTime instance for the first of next - month, you'd call now() + RelativeDateTime(months=+1,day=1). - - """ - years = 0 - months = 0 - days = 0 - year = None - month = 0 - day = 0 - hours = 0 - minutes = 0 - seconds = 0 - hour = None - minute = None - second = None - weekday = None - - # cached hash value - _hash = None - - # For Zope security: - __roles__ = None - __allow_access_to_unprotected_subobjects__ = 1 - - def __init__(self, - years=0,months=0,days=0, - hours=0,minutes=0,seconds=0, - year=None,month=None,day=None, - hour=None,minute=None,second=None, - weekday=None,weeks=0): - - self.years = years - self.months = months - self.days = days + weeks*7 - self.year = year - self.month = month - self.day = day - self.hours = hours - self.minutes = minutes - self.seconds = seconds - self.hour = hour - self.minute = minute - self.second = second - if weekday is not None: - # Make sure we've got a 2-tuple - assert len(weekday) == 2 - self.weekday = weekday - - def __add__(self,other, - # Locals: - isinstance=isinstance): - - if isinstance(other,RelativeDateTime): - # RelativeDateTime (self) + RelativeDateTime (other) - - r = RelativeDateTime() - # date deltas - r.years = self.years + other.years - r.months = self.months + other.months - r.days = self.days + other.days - # absolute entries of other override those in self, if given - r.year = other.year or self.year - r.month = other.month or self.month - r.day = other.day or self.day - r.weekday = other.weekday or self.weekday - # time deltas - r.hours = self.hours + other.hours - r.minutes = self.minutes + other.minutes - r.seconds = self.seconds + other.seconds - # absolute entries of other override those in self, if given - r.hour = other.hour or self.hour - r.minute = other.minute or self.minute - r.second = other.second or self.second - return r - - else: - raise TypeError,"can't add the two types" - - def __radd__(self,other, - # Locals: - isinstance=isinstance,DateTimeType=DateTimeType, - DateTime=DateTime,DateTimeDelta=DateTimeDelta): - - if isinstance(other,DateTimeType): - # DateTime (other) + RelativeDateTime (self) - - # date - if self.year is None: - year = other.year + self.years - else: - year = self.year + self.years - if self.month is None: - month = other.month + self.months - else: - month = self.month + self.months - if self.day is None: - day = other.day - else: - day = self.day - if day < 0: - # fix negative day values - month = month + 1 - day = day + 1 - day = day + self.days - # time - if self.hour is None: - hour = other.hour + self.hours - else: - hour = self.hour + self.hours - if self.minute is None: - minute = other.minute + self.minutes - else: - minute = self.minute + self.minutes - if self.second is None: - second = other.second + self.seconds - else: - second = self.second + self.seconds - - # Refit into proper ranges: - if month < 1 or month > 12: - month = month - 1 - yeardelta, monthdelta = divmod(month, 12) - year = year + yeardelta - month = monthdelta + 1 - - # Make sure we have integers - year = int(year) - month = int(month) - day = int(day) - - if self.weekday is None: - return DateTime(year, month, 1) + \ - DateTimeDelta(day-1,hour,minute,second) - - # Adjust to the correct weekday - day_of_week,index = self.weekday - d = DateTime(year, month, 1) + \ - DateTimeDelta(day-1,hour,minute,second) - if index == 0: - # 0 index: next weekday if no match - return d + (day_of_week - d.day_of_week) - elif index > 0: - # positive index (1 == first weekday of month) - first = d - (d.day - 1) - diff = day_of_week - first.day_of_week - if diff >= 0: - return first + (diff + (index-1) * 7) - else: - return first + (diff + index * 7) - else: - # negative index (-1 == last weekday of month) - last = d + (d.days_in_month - d.day) - diff = day_of_week - last.day_of_week - if diff <= 0: - return last + (diff + (index+1) * 7) - else: - return last + (diff + index * 7) - - else: - raise TypeError,"can't add the two types" - - def __sub__(self,other): - - if isinstance(other,RelativeDateTime): - # RelativeDateTime (self) - RelativeDateTime (other) - - r = RelativeDateTime() - # date deltas - r.years = self.years - other.years - r.months = self.months - other.months - r.days = self.days - other.days - # absolute entries of other override those in self, if given - r.year = other.year or self.year - r.month = other.month or self.month - r.day = other.day or self.day - r.weekday = other.weekday or self.weekday - # time deltas - r.hours = self.hours - other.hours - r.minutes = self.minutes - other.minutes - r.seconds = self.seconds - other.seconds - # absolute entries of other override those in self, if given - r.hour = other.hour or self.hour - r.minute = other.minute or self.minute - r.second = other.second or self.second - - return r - - else: - raise TypeError,"can't subtract the two types" - - def __rsub__(self,other, - # Locals: - isinstance=isinstance,DateTimeType=DateTimeType): - - if isinstance(other,DateTimeType): - # DateTime (other) - RelativeDateTime (self) - return other + self.__neg__() - - else: - raise TypeError,"can't subtract the two types" - - def __neg__(self): - - # - RelativeDateTime(self) - - r = RelativeDateTime() - # negate date deltas - r.years = - self.years - r.months = - self.months - r.days = - self.days - # absolute entries don't change - r.year = self.year - r.month = self.month - r.day = self.day - r.weekday = self.weekday - # negate time deltas - r.hours = - self.hours - r.minutes = - self.minutes - r.seconds = - self.seconds - # absolute entries don't change - r.hour = self.hour - r.minute = self.minute - r.second = self.second - - return r - - def __nonzero__(self): - - # RelativeDateTime instances are considered false in case - # they do not define any alterations - if (self.year is None and - self.years == 0 and - self.month is None and - self.months == 0 and - self.day is None and - self.weekday is None and - self.days == 0 and - self.hour is None and - self.hours == 0 and - self.minute is None and - self.minutes == 0 and - self.second is None and - self.seconds == 0): - return 0 - else: - return 1 - - def __mul__(self,other): - - # RelativeDateTime (self) * Number (other) - factor = float(other) - - r = RelativeDateTime() - # date deltas - r.years = factor * self.years - r.months = factor * self.months - r.days = factor * self.days - # time deltas - r.hours = factor * self.hours - r.minutes = factor * self.minutes - r.seconds = factor * self.seconds - return r - - __rmul__ = __mul__ - - def __div__(self,other): - - # RelativeDateTime (self) / Number (other) - return self.__mul__(1/float(other)) - - def __eq__(self, other): - - if isinstance(self, RelativeDateTime) and \ - isinstance(other, RelativeDateTime): - # RelativeDateTime (self) == RelativeDateTime (other) - if (self.years == other.years and - self.months == other.months and - self.days == other.days and - self.year == other.year and - self.day == other.day and - self.hours == other.hours and - self.minutes == other.minutes and - self.seconds == other.seconds and - self.hour == other.hour and - self.minute == other.minute and - self.second == other.second and - self.weekday == other.weekday): - return 1 - else: - return 0 - else: - raise TypeError,"can't compare the two types" - - def __hash__(self): - - if self._hash is not None: - return self._hash - x = 1234 - for value in (self.years, self.months, self.days, - self.year, self.day, - self.hours, self.minutes, self.seconds, - self.hour, self.minute, self.second, - self.weekday): - if value is None: - x = 135051820 ^ x - else: - x = hash(value) ^ x - self._hash = x - return x - - def __str__(self, - - join=_string.join): - - l = [] - append = l.append - - # Format date part - if self.year is not None: - append('%04i-' % self.year) - elif self.years: - append('(%0+5i)-' % self.years) - else: - append('YYYY-') - if self.month is not None: - append('%02i-' % self.month) - elif self.months: - append('(%0+3i)-' % self.months) - else: - append('MM-') - if self.day is not None: - append('%02i' % self.day) - elif self.days: - append('(%0+3i)' % self.days) - else: - append('DD') - if self.weekday: - append(' %s:%i' % (Weekday[self.weekday[0]][:3],self.weekday[1])) - append(' ') - - # Normalize relative time values to avoid fractions - hours = self.hours - minutes = self.minutes - seconds = self.seconds - hours_fraction = hours - int(hours) - minutes = minutes + hours_fraction * 60.0 - minutes_fraction = minutes - int(minutes) - seconds = seconds + minutes_fraction * 6.0 - seconds_fraction = seconds - int(seconds) - - if 0: - # Normalize to standard time ranges - if seconds > 60.0: - extra_minutes, seconds = divmod(seconds, 60.0) - minutes = minutes + extra_minutes - elif seconds < -60.0: - extra_minutes, seconds = divmod(seconds, -60.0) - minutes = minutes - extra_minutes - if minutes >= 60.0: - extra_hours, minutes = divmod(minutes, 60.0) - hours = hours + extra_hours - elif minutes <= -60.0: - extra_hours, minutes = divmod(minutes, -60.0) - hours = hours - extra_hours - - # Format time part - if self.hour is not None: - append('%02i:' % self.hour) - elif hours: - append('(%0+3i):' % hours) - else: - append('HH:') - if self.minute is not None: - append('%02i:' % self.minute) - elif minutes: - append('(%0+3i):' % minutes) - else: - append('MM:') - if self.second is not None: - append('%02i' % self.second) - elif seconds: - append('(%0+3i)' % seconds) - else: - append('SS') - - return join(l,'') - - def __repr__(self): - - return "<%s instance for '%s' at 0x%x>" % ( - self.__class__.__name__, - self.__str__(), - id(self)) - -# Alias -RelativeDate = RelativeDateTime - -def RelativeDateTimeFrom(*args, **kws): - - """ RelativeDateTimeFrom(*args, **kws) - - Generic RelativeDateTime instance constructor. Can handle - parsing strings and keywords. - - """ - if len(args) == 1: - # Single argument - arg = args[0] - if _isstring(arg): - import Parser - return apply(Parser.RelativeDateTimeFromString, args, kws) - elif isinstance(arg, RelativeDateTime): - return arg - else: - raise TypeError,\ - 'cannot convert argument to RelativeDateTime' - - else: - return apply(RelativeDateTime,args,kws) - -def RelativeDateTimeDiff(date1,date2, - - floor=_math.floor,int=int,divmod=divmod, - RelativeDateTime=RelativeDateTime): - - """ RelativeDateTimeDiff(date1,date2) - - Returns a RelativeDateTime instance representing the difference - between date1 and date2 in relative terms. - - The following should hold: - - date2 + RelativeDateDiff(date1,date2) == date1 - - for all dates date1 and date2. - - Note that due to the algorithm used by this function, not the - whole range of DateTime instances is supported; there could - also be a loss of precision. - - XXX There are still some problems left (thanks to Carel - Fellinger for pointing these out): - - 29 1 1901 -> 1 3 1901 = 1 month - 29 1 1901 -> 1 3 1900 = -10 month and -28 days, but - 29 1 1901 -> 28 2 1900 = -11 month and -1 day - - and even worse: - - >>> print RelativeDateDiff(Date(1900,3,1),Date(1901,2,1)) - YYYY-(-11)-DD HH:MM:SS - - with: - - >>> print Date(1901,1,29) + RelativeDateTime(months=-11) - 1900-03-01 00:00:00.00 - >>> print Date(1901,2,1) + RelativeDateTime(months=-11) - 1900-03-01 00:00:00.00 - - """ - diff = date1 - date2 - if diff.days == 0: - return RelativeDateTime() - date1months = date1.year * 12 + (date1.month - 1) - date2months = date2.year * 12 + (date2.month - 1) - #print 'months',date1months,date2months - - # Calculate the months difference - diffmonths = date1months - date2months - #print 'diffmonths',diffmonths - if diff.days > 0: - years,months = divmod(diffmonths,12) - else: - years,months = divmod(diffmonths,-12) - years = -years - date3 = date2 + RelativeDateTime(years=years,months=months) - diff3 = date1 - date3 - days = date1.absdays - date3.absdays - #print 'date3',date3,'diff3',diff3,'days',days - - # Correction to ensure that all relative parts have the same sign - while days * diff.days < 0: - if diff.days > 0: - diffmonths = diffmonths - 1 - years,months = divmod(diffmonths,12) - else: - diffmonths = diffmonths + 1 - years,months = divmod(diffmonths,-12) - years = -years - #print 'diffmonths',diffmonths - date3 = date2 + RelativeDateTime(years=years,months=months) - diff3 = date1 - date3 - days = date1.absdays - date3.absdays - #print 'date3',date3,'diff3',diff3,'days',days - - # Drop the fraction part of days - if days > 0: - days = int(floor(days)) - else: - days = int(-floor(-days)) - - return RelativeDateTime(years=years, - months=months, - days=days, - hours=diff3.hour, - minutes=diff3.minute, - seconds=diff3.second) - -# Aliases -RelativeDateDiff = RelativeDateTimeDiff -Age = RelativeDateTimeDiff - -### - -_current_year = now().year -_current_century, _current_year_in_century = divmod(_current_year, 100) -_current_century = _current_century * 100 - -def add_century(year, - - current_year=_current_year, - current_century=_current_century): - - """ Sliding window approach to the Y2K problem: adds a suitable - century to the given year and returns it as integer. - - The window used depends on the current year (at import time). - If adding the current century to the given year gives a year - within the range current_year-70...current_year+30 [both - inclusive], then the current century is added. Otherwise the - century (current + 1 or - 1) producing the least difference is - chosen. - - """ - if year > 99: - # Take it as-is - return year - year = year + current_century - diff = year - current_year - if diff >= -70 and diff <= 30: - return year - elif diff < -70: - return year + 100 - else: - return year - 100 - -# Reference formulas for JDN taken from the Calendar FAQ: - -def gregorian_jdn(year,month,day): - - # XXX These require proper integer division. - a = (14-month)/12 - y = year+4800-a - m = month + 12*a - 3 - return day + (306*m+5)/10 + y*365 + y/4 - y/100 + y/400 - 32045 - -def julian_jdn(year,month,day): - - # XXX These require proper integer division. - a = (14-month)/12 - y = year+4800-a - m = month + 12*a - 3 - return day + (306*m+5)/10 + y*365 + y/4 - 32083 diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/ISO.py --- a/embedded/mx/DateTime/ISO.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,366 +0,0 @@ -""" This module provides a set of constructors and routines to convert - between DateTime[Delta] instances and ISO representations of date - and time. - - Note: Timezones are only interpreted by ParseDateTimeGMT(). All - other constructors silently ignore the time zone information. - - Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com - Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com - See the documentation for further information on copyrights, - or contact the author. - -""" -import DateTime,Timezone -import re,string - -# Grammar: ISO 8601 (not all, but what we need from it) -_year = '(?P\d?\d\d\d)' -_month = '(?P\d?\d)' -_day = '(?P\d?\d)' -_hour = '(?P\d?\d)' -_minute = '(?P\d?\d)' -_second = '(?P\d?\d(?:\.\d+)?)' -_sign = '(?P[-+])' -_week = 'W(?P\d?\d)' -_zone = Timezone.isozone - -_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?' -_date = _year + '-?' + '(?:' + _month + '-?' + _day + '?)?' -_time = _hour + ':?' + _minute + ':?' + _second + '?(?:' + _zone + ')?' - -isodatetimeRE = re.compile(_date + '(?:[ T]' + _time + ')?$') -isodateRE = re.compile(_date + '$') -isotimeRE = re.compile(_time + '$') -isodeltaRE = re.compile(_sign + '?' + _time + '$') -isoweekRE = re.compile(_weekdate + '$') -isoweektimeRE = re.compile(_weekdate + '(?:[ T]' + _time + ')?$') - -def WeekTime(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0): - - """Week(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0) - - Returns a DateTime instance pointing to the given ISO week and - day. isoday defaults to 1, which corresponds to Monday in the - ISO numbering. The time part is set as given. - - """ - d = DateTime.DateTime(year,1,1,hour,minute,second) - if d.iso_week[0] == year: - # 1.1. belongs to year (backup to Monday) - return d + (-d.day_of_week + 7 * (isoweek-1) + isoday-1) - else: - # 1.1. belongs to year-1 (advance to next Monday) - return d + (7-d.day_of_week + 7 * (isoweek-1) + isoday-1) - -# Alias -Week = WeekTime - -# Aliases for the other constructors (they all happen to already use -# ISO format) -Date = DateTime.Date -Time = DateTime.Time -TimeDelta = DateTime.TimeDelta - -def ParseDateTime(isostring,parse_isodatetime=isodatetimeRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseDateTime(isostring) - - Returns a DateTime instance reflecting the given ISO date. A - time part is optional and must be delimited from the date by a - space or 'T'. - - Time zone information is parsed, but not evaluated. - - """ - s = strip(isostring) - date = parse_isodatetime(s) - if not date: - raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS' - year,month,day,hour,minute,second,zone = date.groups() - year = atoi(year) - if month is None: - month = 1 - else: - month = atoi(month) - if day is None: - day = 1 - else: - day = atoi(day) - if hour is None: - hour = 0 - else: - hour = atoi(hour) - if minute is None: - minute = 0 - else: - minute = atoi(minute) - if second is None: - second = 0.0 - else: - second = atof(second) - return DateTime.DateTime(year,month,day,hour,minute,second) - -def ParseDateTimeGMT(isostring,parse_isodatetime=isodatetimeRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseDateTimeGMT(isostring) - - Returns a DateTime instance in UTC reflecting the given ISO - date. A time part is optional and must be delimited from the - date by a space or 'T'. Timezones are honored. - - """ - s = strip(isostring) - date = parse_isodatetime(s) - if not date: - raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS' - year,month,day,hour,minute,second,zone = date.groups() - year = atoi(year) - if month is None: - month = 1 - else: - month = atoi(month) - if day is None: - day = 1 - else: - day = atoi(day) - if hour is None: - hour = 0 - else: - hour = atoi(hour) - if minute is None: - minute = 0 - else: - minute = atoi(minute) - if second is None: - second = 0.0 - else: - second = atof(second) - offset = Timezone.utc_offset(zone) - return DateTime.DateTime(year,month,day,hour,minute,second) - offset - -# Alias -ParseDateTimeUTC = ParseDateTimeGMT - -def ParseDate(isostring,parse_isodate=isodateRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseDate(isostring) - - Returns a DateTime instance reflecting the given ISO date. A - time part may not be included. - - """ - s = strip(isostring) - date = parse_isodate(s) - if not date: - raise ValueError,'wrong format, use YYYY-MM-DD' - year,month,day = date.groups() - year = atoi(year) - if month is None: - month = 1 - else: - month = atoi(month) - if day is None: - day = 1 - else: - day = atoi(day) - return DateTime.DateTime(year,month,day) - -def ParseWeek(isostring,parse_isoweek=isoweekRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseWeek(isostring) - - Returns a DateTime instance reflecting the given ISO date. A - time part may not be included. - - """ - s = strip(isostring) - date = parse_isoweek(s) - if not date: - raise ValueError,'wrong format, use yyyy-Www-d, e.g. 1998-W01-1' - year,week,day = date.groups() - year = atoi(year) - if week is None: - week = 1 - else: - week = atoi(week) - if day is None: - day = 1 - else: - day = atoi(day) - return Week(year,week,day) - -def ParseWeekTime(isostring,parse_isoweektime=isoweektimeRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseWeekTime(isostring) - - Returns a DateTime instance reflecting the given ISO date. A - time part is optional and must be delimited from the date by a - space or 'T'. - - """ - s = strip(isostring) - date = parse_isoweektime(s) - if not date: - raise ValueError,'wrong format, use e.g. "1998-W01-1 12:00:30"' - year,week,day,hour,minute,second,zone = date.groups() - year = atoi(year) - if week is None: - week = 1 - else: - week = atoi(week) - if day is None: - day = 1 - else: - day = atoi(day) - if hour is None: - hour = 0 - else: - hour = atoi(hour) - if minute is None: - minute = 0 - else: - minute = atoi(minute) - if second is None: - second = 0.0 - else: - second = atof(second) - return WeekTime(year,week,day,hour,minute,second) - -def ParseTime(isostring,parse_isotime=isotimeRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseTime(isostring) - - Returns a DateTimeDelta instance reflecting the given ISO time. - Hours and minutes must be given, seconds are - optional. Fractions of a second may also be used, - e.g. 12:23:12.34. - - """ - s = strip(isostring) - time = parse_isotime(s) - if not time: - raise ValueError,'wrong format, use HH:MM:SS' - hour,minute,second,zone = time.groups() - hour = atoi(hour) - minute = atoi(minute) - if second is not None: - second = atof(second) - else: - second = 0.0 - return DateTime.TimeDelta(hour,minute,second) - -def ParseTimeDelta(isostring,parse_isodelta=isodeltaRE.match, - - strip=string.strip,atoi=string.atoi,atof=string.atof): - - """ParseTimeDelta(isostring) - - Returns a DateTimeDelta instance reflecting the given ISO time - as delta. Hours and minutes must be given, seconds are - optional. Fractions of a second may also be used, - e.g. 12:23:12.34. In addition to the ISO standard a sign may be - prepended to the time, e.g. -12:34. - - """ - s = strip(isostring) - time = parse_isodelta(s) - if not time: - raise ValueError,'wrong format, use [-]HH:MM:SS' - sign,hour,minute,second,zone = time.groups() - hour = atoi(hour) - minute = atoi(minute) - if second is not None: - second = atof(second) - else: - second = 0.0 - if sign and sign == '-': - return -DateTime.TimeDelta(hour,minute,second) - else: - return DateTime.TimeDelta(hour,minute,second) - -def ParseAny(isostring): - - """ParseAny(isostring) - - Parses the given string and tries to convert it to a - DateTime[Delta] instance. - - """ - try: - return ParseDateTime(isostring) - except ValueError: - pass - try: - return ParseWeekTime(isostring) - except ValueError: - pass - try: - return ParseTimeDelta(isostring) - except ValueError: - raise ValueError,'unsupported format: "%s"' % isostring - -def str(datetime,tz=None): - - """str(datetime,tz=DateTime.tz_offset(datetime)) - - Returns the datetime instance as ISO date string. tz can be - given as DateTimeDelta instance providing the time zone - difference from datetime's zone to UTC. It defaults to - DateTime.tz_offset(datetime) which assumes local time. - - """ - if tz is None: - tz = datetime.gmtoffset() - return '%04i-%02i-%02i %02i:%02i:%02i%+03i%02i' % ( - datetime.year, datetime.month, datetime.day, - datetime.hour, datetime.minute, datetime.second, - tz.hour,tz.minute) - -def strGMT(datetime): - - """strGMT(datetime) - - Returns the datetime instance as ISO date string assuming it is - given in GMT. - - """ - return '%04i-%02i-%02i %02i:%02i:%02i+0000' % ( - datetime.year, datetime.month, datetime.day, - datetime.hour, datetime.minute, datetime.second) - -def strUTC(datetime): - - """strUTC(datetime) - - Returns the datetime instance as ISO date string assuming it is - given in UTC. - - """ - return '%04i-%02i-%02i %02i:%02i:%02i+0000' % ( - datetime.year, datetime.month, datetime.day, - datetime.hour, datetime.minute, datetime.second) - -# Testing -if __name__ == '__main__': - e = DateTime.Date(1900,1,1) - for i in range(100000): - d = e + i - year,week,day = d.iso_week - c = WeekTime(year,week,day) - if d != c: - print ' Check %s (given; %i) != %s (parsed)' % (d,d.day_of_week,c) - elif i % 1000 == 0: - print d,'ok' diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/Parser.py --- a/embedded/mx/DateTime/Parser.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1225 +0,0 @@ -# -*- coding: latin-1 -*- - -""" Date/Time string parsing module. - - Note about the Y2K problems: - - The parser can only handle years with at least 2 digits. 2 - digit year values get expanded by adding the century using - DateTime.add_century(), while 3 digit year get converted - literally. To have 2 digit years also be interpreted literally, - add leading zeros, e.g. year 99 must be written as 099 or 0099. - - Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com - Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com - See the documentation for further information on copyrights, - or contact the author. All Rights Reserved. - -""" -import types,re,string -import DateTime,ISO,ARPA,Timezone - -# Enable to produce debugging output -_debug = 0 - -# REs for matching date and time parts in a string; These REs -# parse a superset of ARPA, ISO, American and European style dates. -# Timezones are supported via the Timezone submodule. - -_year = '(?P-?\d+\d(?!:))' -_fullyear = '(?P-?\d+\d\d(?!:))' -_year_epoch = '(?:' + _year + '(?P *[ABCDE\.]+)?)' -_fullyear_epoch = '(?:' + _fullyear + '(?P *[ABCDE\.]+)?)' -_relyear = '(?:\((?P[-+]?\d+)\))' - -_month = '(?P\d?\d(?!:))' -_fullmonth = '(?P\d\d(?!:))' -_litmonth = ('(?P' - 'jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|' - 'mr|mae|mrz|mai|okt|dez|' - 'fev|avr|juin|juil|aou|ao|dc|' - 'ene|abr|ago|dic|' - 'out' - ')[a-z,\.;]*') -litmonthtable = { - # English - 'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, - 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12, - # German - 'mr':3, 'mae':3, 'mrz':3, 'mai':5, 'okt':10, 'dez':12, - # French - 'fev':2, 'avr':4, 'juin':6, 'juil':7, 'aou':8, 'ao':8, - 'dc':12, - # Spanish - 'ene':1, 'abr':4, 'ago':8, 'dic':12, - # Portuguese - 'out':10, - } -_relmonth = '(?:\((?P[-+]?\d+)\))' - -_day = '(?P\d?\d(?!:))' -_usday = '(?P\d?\d(?!:))(?:st|nd|rd|th|[,\.;])?' -_fullday = '(?P\d\d(?!:))' -_litday = ('(?P' - 'mon|tue|wed|thu|fri|sat|sun|' - 'die|mit|don|fre|sam|son|' - 'lun|mar|mer|jeu|ven|sam|dim|' - 'mie|jue|vie|sab|dom|' - 'pri|seg|ter|cua|qui' - ')[a-z]*') -litdaytable = { - # English - 'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6, - # German - 'die':1, 'mit':2, 'don':3, 'fre':4, 'sam':5, 'son':6, - # French - 'lun':0, 'mar':1, 'mer':2, 'jeu':3, 'ven':4, 'sam':5, 'dim':6, - # Spanish - 'mie':2, 'jue':3, 'vie':4, 'sab':5, 'dom':6, - # Portuguese - 'pri':0, 'seg':1, 'ter':2, 'cua':3, 'qui':4, - } -_relday = '(?:\((?P[-+]?\d+)\))' - -_hour = '(?P[012]?\d)' -_minute = '(?P[0-6]\d)' -_second = '(?P[0-6]\d(?:[.,]\d+)?)' - -_days = '(?P\d*\d(?:[.,]\d+)?)' -_hours = '(?P\d*\d(?:[.,]\d+)?)' -_minutes = '(?P\d*\d(?:[.,]\d+)?)' -_seconds = '(?P\d*\d(?:[.,]\d+)?)' - -_reldays = '(?:\((?P[-+]?\d+(?:[.,]\d+)?)\))' -_relhours = '(?:\((?P[-+]?\d+(?:[.,]\d+)?)\))' -_relminutes = '(?:\((?P[-+]?\d+(?:[.,]\d+)?)\))' -_relseconds = '(?:\((?P[-+]?\d+(?:[.,]\d+)?)\))' - -_sign = '(?:(?P[-+]) *)' -_week = 'W(?P\d?\d)' -_zone = Timezone.zone -_ampm = '(?P[ap][m.]+)' - -_time = (_hour + ':' + _minute + '(?::' + _second + '|[^:]|$) *' - + _ampm + '? *' + _zone + '?') -_isotime = _hour + ':?' + _minute + ':?' + _second + '? *' + _zone + '?' - -_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?' -_eurodate = _day + '\.' + _month + '\.' + _year_epoch + '?' -_usdate = _month + '/' + _day + '(?:/' + _year_epoch + '|[^/]|$)' -_altusdate = _month + '-' + _day + '-' + _fullyear_epoch -_isodate = _year + '-' + _month + '-?' + _day + '?(?!:)' -_altisodate = _year + _fullmonth + _fullday + '(?!:)' -_usisodate = _fullyear + '/' + _fullmonth + '/' + _fullday -_litdate = ('(?:'+ _litday + ',? )? *' + - _usday + ' *' + - '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ] *' + - _year_epoch + '?') -_altlitdate = ('(?:'+ _litday + ',? )? *' + - _litmonth + '[ ,.a-z]+' + - _usday + - '(?:[ a-z]+' + _year_epoch + ')?') -_eurlitdate = ('(?:'+ _litday + ',?[ a-z]+)? *' + - '(?:'+ _usday + '[ a-z]+)? *' + - _litmonth + - '(?:[ ,.a-z]+' + _year_epoch + ')?') - -_relany = '[*%?a-zA-Z]+' - -_relisodate = ('(?:(?:' + _relany + '|' + _year + '|' + _relyear + ')-' + - '(?:' + _relany + '|' + _month + '|' + _relmonth + ')-' + - '(?:' + _relany + '|' + _day + '|' + _relday + '))') - -_asctime = ('(?:'+ _litday + ',? )? *' + - _usday + ' *' + - '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ]' + - '(?:[0-9: ]+)' + - _year_epoch + '?') - -_relisotime = ('(?:(?:' + _relany + '|' + _hour + '|' + _relhours + '):' + - '(?:' + _relany + '|' + _minute + '|' + _relminutes + ')' + - '(?::(?:' + _relany + '|' + _second + '|' + _relseconds + '))?)') - -_isodelta1 = (_sign + '?' + - _days + ':' + _hours + ':' + _minutes + ':' + _seconds) -_isodelta2 = (_sign + '?' + - _hours + ':' + _minutes + ':' + _seconds) -_isodelta3 = (_sign + '?' + - _hours + ':' + _minutes) -_litdelta = (_sign + '?' + - '(?:' + _days + ' *d[a-z]*[,; ]*)?' + - '(?:' + _hours + ' *h[a-z]*[,; ]*)?' + - '(?:' + _minutes + ' *m[a-z]*[,; ]*)?' + - '(?:' + _seconds + ' *s[a-z]*[,; ]*)?') -_litdelta2 = (_sign + '?' + - '(?:' + _days + ' *d[a-z]*[,; ]*)?' + - _hours + ':' + _minutes + '(?::' + _seconds + ')?') - -_timeRE = re.compile(_time, re.I) -_isotimeRE = re.compile(_isotime, re.I) -_isodateRE = re.compile(_isodate, re.I) -_altisodateRE = re.compile(_altisodate, re.I) -_usisodateRE = re.compile(_usisodate, re.I) -_eurodateRE = re.compile(_eurodate, re.I) -_usdateRE = re.compile(_usdate, re.I) -_altusdateRE = re.compile(_altusdate, re.I) -_litdateRE = re.compile(_litdate, re.I) -_altlitdateRE = re.compile(_altlitdate, re.I) -_eurlitdateRE = re.compile(_eurlitdate, re.I) -_relisodateRE = re.compile(_relisodate, re.I) -_asctimeRE = re.compile(_asctime, re.I) -_isodelta1RE = re.compile(_isodelta1) -_isodelta2RE = re.compile(_isodelta2) -_isodelta3RE = re.compile(_isodelta3) -_litdeltaRE = re.compile(_litdelta) -_litdelta2RE = re.compile(_litdelta2) -_relisotimeRE = re.compile(_relisotime, re.I) - -# Available date parsers -_date_formats = ('euro', - 'usiso', 'us', 'altus', - 'iso', 'altiso', - 'lit', 'altlit', 'eurlit', - 'unknown') - -# Available time parsers -_time_formats = ('standard', - 'iso', - 'unknown') - -def _parse_date(text, formats=_date_formats, defaultdate=None, - - int=int,float=float,lower=string.lower, - add_century=DateTime.add_century, - now=DateTime.now,us_formats=('us', 'altus'), - iso_formats=('iso', 'altiso', 'usiso')): - - """ Parses the date part given in text and returns a tuple - (text,day,month,year,style) with the following - meanings: - - * text gives the original text without the date part - - * day,month,year give the parsed date - - * style gives information about which parser was successful: - 'euro' - the European date parser - 'us' - the US date parser - 'altus' - the alternative US date parser (with '-' instead of '/') - 'iso' - the ISO date parser - 'altiso' - the alternative ISO date parser (without '-') - 'usiso' - US style ISO date parser (yyyy/mm/dd) - 'lit' - the US literal date parser - 'altlit' - the alternative US literal date parser - 'eurlit' - the Eurpean literal date parser - 'unknown' - no date part was found, defaultdate was used - - formats may be set to a tuple of style strings specifying - which of the above parsers to use and in which order to try - them. Default is to try all of them in the above order. - - defaultdate provides the defaults to use in case no date part - is found. Most other parsers default to the current year - January 1 if some of these date parts are missing. - - If 'unknown' is not given in formats and the date cannot be - parsed, a ValueError is raised. - - """ - match = None - style = '' - - # Apply parsers in the order given in formats - for format in formats: - - if format == 'euro': - # European style date - match = _eurodateRE.search(text) - if match is not None: - day,month,year,epoch = match.groups() - if year: - if len(year) == 2: - # Y2K problem: - year = add_century(int(year)) - else: - year = int(year) - else: - if defaultdate is None: - defaultdate = now() - year = defaultdate.year - if epoch and 'B' in epoch: - year = -year + 1 - month = int(month) - day = int(day) - # Could have mistaken euro format for us style date - # which uses month, day order - if month > 12 or month == 0: - match = None - continue - break - - elif format in iso_formats: - # ISO style date - if format == 'iso': - match = _isodateRE.search(text) - elif format == 'altiso': - match = _altisodateRE.search(text) - # Avoid mistaking ISO time parts ('Thhmmss') for dates - if match is not None: - left, right = match.span() - if left > 0 and \ - text[left - 1:left] == 'T': - match = None - continue - else: - match = _usisodateRE.search(text) - if match is not None: - year,month,day = match.groups() - if len(year) == 2: - # Y2K problem: - year = add_century(int(year)) - else: - year = int(year) - # Default to January 1st - if not month: - month = 1 - else: - month = int(month) - if not day: - day = 1 - else: - day = int(day) - break - - elif format in us_formats: - # US style date - if format == 'us': - match = _usdateRE.search(text) - else: - match = _altusdateRE.search(text) - if match is not None: - month,day,year,epoch = match.groups() - if year: - if len(year) == 2: - # Y2K problem: - year = add_century(int(year)) - else: - year = int(year) - else: - if defaultdate is None: - defaultdate = now() - year = defaultdate.year - if epoch and 'B' in epoch: - year = -year + 1 - # Default to 1 if no day is given - if day: - day = int(day) - else: - day = 1 - month = int(month) - # Could have mistaken us format for euro style date - # which uses day, month order - if month > 12 or month == 0: - match = None - continue - break - - elif format == 'lit': - # US style literal date - match = _litdateRE.search(text) - if match is not None: - litday,day,litmonth,month,year,epoch = match.groups() - break - - elif format == 'altlit': - # Alternative US style literal date - match = _altlitdateRE.search(text) - if match is not None: - litday,litmonth,day,year,epoch = match.groups() - month = '' - break - - elif format == 'eurlit': - # European style literal date - match = _eurlitdateRE.search(text) - if match is not None: - litday,day,litmonth,year,epoch = match.groups() - month = '' - break - - elif format == 'unknown': - # No date part: use defaultdate - if defaultdate is None: - defaultdate = now() - year = defaultdate.year - month = defaultdate.month - day = defaultdate.day - style = format - break - - # Check success - if match is not None: - # Remove date from text - left, right = match.span() - if 0 and _debug: - print 'parsed date:',repr(text[left:right]),\ - 'giving:',year,month,day - text = text[:left] + text[right:] - style = format - - elif not style: - # Not recognized: raise an error - raise ValueError, 'unknown date format: "%s"' % text - - # Literal date post-processing - if style in ('lit', 'altlit', 'eurlit'): - if 0 and _debug: print match.groups() - # Default to current year, January 1st - if not year: - if defaultdate is None: - defaultdate = now() - year = defaultdate.year - else: - if len(year) == 2: - # Y2K problem: - year = add_century(int(year)) - else: - year = int(year) - if epoch and 'B' in epoch: - year = -year + 1 - if litmonth: - litmonth = lower(litmonth) - try: - month = litmonthtable[litmonth] - except KeyError: - raise ValueError,\ - 'wrong month name: "%s"' % litmonth - elif month: - month = int(month) - else: - month = 1 - if day: - day = int(day) - else: - day = 1 - - #print '_parse_date:',text,day,month,year,style - return text,day,month,year,style - -def _parse_time(text, formats=_time_formats, - - int=int,float=float,replace=string.replace): - - """ Parses a time part given in text and returns a tuple - (text,hour,minute,second,offset,style) with the following - meanings: - - * text gives the original text without the time part - * hour,minute,second give the parsed time - * offset gives the time zone UTC offset - * style gives information about which parser was successful: - 'standard' - the standard parser - 'iso' - the ISO time format parser - 'unknown' - no time part was found - - formats may be set to a tuple specifying the parsers to use: - 'standard' - standard time format with ':' delimiter - 'iso' - ISO time format (superset of 'standard') - 'unknown' - default to 0:00:00, 0 zone offset - - If 'unknown' is not given in formats and the time cannot be - parsed, a ValueError is raised. - - """ - match = None - style = '' - - # Apply parsers in the order given in formats - for format in formats: - - # Standard format - if format == 'standard': - match = _timeRE.search(text) - if match is not None: - hour,minute,second,ampm,zone = match.groups() - style = 'standard' - break - - # ISO format - if format == 'iso': - match = _isotimeRE.search(text) - if match is not None: - hour,minute,second,zone = match.groups() - ampm = None - style = 'iso' - break - - # Default handling - elif format == 'unknown': - hour,minute,second,offset = 0,0,0.0,0 - style = 'unknown' - break - - if not style: - # If no default handling should be applied, raise an error - raise ValueError, 'unknown time format: "%s"' % text - - # Post-processing - if match is not None: - if zone: - # Convert to UTC offset - offset = Timezone.utc_offset(zone) - else: - offset = 0 - hour = int(hour) - if ampm: - if ampm[0] in ('p', 'P'): - # 12pm = midday - if hour < 12: - hour = hour + 12 - else: - # 12am = midnight - if hour >= 12: - hour = hour - 12 - if minute: - minute = int(minute) - else: - minute = 0 - if not second: - second = 0.0 - else: - if ',' in second: - second = replace(second, ',', '.') - second = float(second) - - # Remove time from text - left,right = match.span() - if 0 and _debug: - print 'parsed time:',repr(text[left:right]),\ - 'giving:',hour,minute,second,offset - text = text[:left] + text[right:] - - #print '_parse_time:',text,hour,minute,second,offset,style - return text,hour,minute,second,offset,style - -### - -def DateTimeFromString(text, formats=_date_formats, defaultdate=None, - time_formats=_time_formats, - - DateTime=DateTime): - - """ DateTimeFromString(text, [formats, defaultdate]) - - Returns a DateTime instance reflecting the date and time given - in text. In case a timezone is given, the returned instance - will point to the corresponding UTC time value. Otherwise, the - value is set as given in the string. - - formats may be set to a tuple of strings specifying which of - the following parsers to use and in which order to try - them. Default is to try all of them in the order given below: - - 'euro' - the European date parser - 'us' - the US date parser - 'altus' - the alternative US date parser (with '-' instead of '/') - 'iso' - the ISO date parser - 'altiso' - the alternative ISO date parser (without '-') - 'usiso' - US style ISO date parser (yyyy/mm/dd) - 'lit' - the US literal date parser - 'altlit' - the alternative US literal date parser - 'eurlit' - the Eurpean literal date parser - 'unknown' - if no date part is found, use defaultdate - - defaultdate provides the defaults to use in case no date part - is found. Most of the parsers default to the current year - January 1 if some of these date parts are missing. - - If 'unknown' is not given in formats and the date cannot - be parsed, a ValueError is raised. - - time_formats may be set to a tuple of strings specifying which - of the following parsers to use and in which order to try - them. Default is to try all of them in the order given below: - - 'standard' - standard time format HH:MM:SS (with ':' delimiter) - 'iso' - ISO time format (superset of 'standard') - 'unknown' - default to 00:00:00 in case the time format - cannot be parsed - - Defaults to 00:00:00.00 for time parts that are not included - in the textual representation. - - If 'unknown' is not given in time_formats and the time cannot - be parsed, a ValueError is raised. - - """ - origtext = text - formats = tuple(formats) - - if formats is _date_formats or \ - 'iso' in formats or \ - 'altiso' in formats: - - # First try standard order (parse time, then date) - if formats[0] not in ('iso', 'altiso'): - text,hour,minute,second,offset,timestyle = _parse_time( - origtext, - time_formats) - text,day,month,year,datestyle = _parse_date( - text, - formats + ('unknown',), - defaultdate) - if 0 and _debug: - print 'tried time/date on %s, date=%s, time=%s' % (origtext, - datestyle, - timestyle) - else: - timestyle = 'iso' - - # If this fails, try the ISO order (date, then time) - if timestyle in ('iso', 'unknown'): - text,day,month,year,datestyle = _parse_date( - origtext, - formats, - defaultdate) - text,hour,minute,second,offset,timestyle = _parse_time( - text, - time_formats) - if 0 and _debug: - print 'tried ISO on %s, date=%s, time=%s' % (origtext, - datestyle, - timestyle) - else: - # Standard order: time part, then date part - text,hour,minute,second,offset,timestyle = _parse_time( - origtext, - time_formats) - text,day,month,year,datestyle = _parse_date( - text, - formats, - defaultdate) - - if (datestyle == 'unknown' and 'unknown' not in formats) or \ - (timestyle == 'unknown' and 'unknown' not in time_formats): - raise ValueError,\ - 'Failed to parse "%s": found "%s" date, "%s" time' % \ - (origtext, datestyle, timestyle) - - try: - return DateTime.DateTime(year,month,day,hour,minute,second) - offset - except DateTime.RangeError, why: - raise DateTime.RangeError,\ - 'Failed to parse "%s": %s' % (origtext, why) - -def DateFromString(text, formats=_date_formats, defaultdate=None, - - DateTime=DateTime): - - """ DateFromString(text, [formats, defaultdate]) - - Returns a DateTime instance reflecting the date given in - text. A possibly included time part is ignored. - - formats and defaultdate work just like for - DateTimeFromString(). - - """ - _text,day,month,year,datestyle = _parse_date(text, formats, defaultdate) - - if datestyle == 'unknown' and \ - 'unknown' not in formats: - raise ValueError,\ - 'Failed to parse "%s": found "%s" date' % \ - (origtext, datestyle) - - try: - return DateTime.DateTime(year,month,day) - except DateTime.RangeError, why: - raise DateTime.RangeError,\ - 'Failed to parse "%s": %s' % (text, why) - -def validateDateTimeString(text, formats=_date_formats): - - """ validateDateTimeString(text, [formats, defaultdate]) - - Validates the given text and returns 1/0 depending on whether - text includes parseable date and time values or not. - - formats works just like for DateTimeFromString() and defines - the order of date/time parsers to apply. It defaults to the - same list of parsers as for DateTimeFromString(). - - XXX Undocumented ! - - """ - formats = list(formats) - if 'unknown' in formats: - formats.remove('unknown') - try: - DateTimeFromString(text, formats) - except (DateTime.RangeError, ValueError), why: - return 0 - return 1 - -def validateDateString(text, formats=_date_formats): - - """ validateDateString(text, [formats, defaultdate]) - - Validates the given text and returns 1/0 depending on whether - text includes a parseable date value or not. - - formats works just like for DateTimeFromString() and defines - the order of date/time parsers to apply. It defaults to the - same list of parsers as for DateTimeFromString(). - - XXX Undocumented ! - - """ - formats = list(formats) - if 'unknown' in formats: - formats.remove('unknown') - try: - DateFromString(text, formats) - except (DateTime.RangeError, ValueError), why: - return 0 - return 1 - -def TimeFromString(text, formats=_time_formats, - - DateTime=DateTime): - - """ TimeFromString(text, [formats]) - - Returns a DateTimeDelta instance reflecting the time given in - text. A possibly included date part is ignored. - - formats may be set to a tuple of strings specifying which of - the following parsers to use and in which order to try - them. Default is to try all of them in the order given below: - - 'standard' - standard time format with ':' delimiter - 'iso' - ISO time format (superset of 'standard') - 'unknown' - default to 00:00:00 in case the time format - cannot be parsed - - Defaults to 00:00:00.00 for parts that are not included in the - textual representation. - - """ - _text,hour,minute,second,offset,timestyle = _parse_time( - text, - formats) - - if timestyle == 'unknown' and \ - 'unknown' not in formats: - raise ValueError,\ - 'Failed to parse "%s": found "%s" time' % \ - (text, timestyle) - - try: - dtd = DateTime.DateTimeDelta(0.0, hour, minute, second) - except DateTime.RangeError, why: - raise DateTime.RangeError,\ - 'Failed to parse "%s": %s' % (text, why) - else: - # XXX What to do with offset ? - return dtd - -# -# XXX Still missing: validateTimeString(), validateDateTimeDeltaString() -# and validateTimeDeltaString() -# - -def DateTimeDeltaFromString(text, - - float=float,DateTime=DateTime): - - """ DateTimeDeltaFromString(text) - - Returns a DateTimeDelta instance reflecting the delta given in - text. Defaults to 0:00:00:00.00 for parts that are not - included in the textual representation or cannot be parsed. - - """ - match = _isodelta1RE.search(text) - if match is not None: - sign, days, hours, minutes, seconds = match.groups() - else: - match = _litdelta2RE.search(text) - if match is not None: - sign, days, hours, minutes, seconds = match.groups() - else: - match = _isodelta2RE.search(text) - if match is not None: - sign, hours, minutes, seconds = match.groups() - days = None - else: - match = _isodelta3RE.search(text) - if match is not None: - sign, hours, minutes = match.groups() - days = None - seconds = None - else: - match = _litdeltaRE.search(text) - if match is not None: - sign, days, hours, minutes, seconds = match.groups() - - else: - # Not matched: - return DateTime.DateTimeDelta(0.0) - - # Conversions - if days: - days = float(days) - else: - days = 0.0 - if hours: - hours = float(hours) - else: - hours = 0.0 - if minutes: - minutes = float(minutes) - else: - minutes = 0.0 - if seconds: - seconds = float(seconds) - else: - seconds = 0.0 - if sign != '-': - sign = 1 - else: - sign = -1 - - try: - dtd = DateTime.DateTimeDelta(days,hours,minutes,seconds) - except DateTime.RangeError, why: - raise DateTime.RangeError,\ - 'Failed to parse "%s": %s' % (text, why) - else: - if sign < 0: - return -dtd - else: - return dtd - -# Aliases -TimeDeltaFromString = DateTimeDeltaFromString - -### - -def _parse_reldate(text, - - int=int,float=float): - - match = _relisodateRE.search(text) - if match is not None: - groups = match.groups() - if 0 and _debug: print groups - year,years,month,months,day,days = groups - if year: - year = int(year) - if years: - years = float(years) - else: - years = 0 - if month: - month = int(month) - if months: - months = float(months) - else: - months = 0 - if day: - day = int(day) - if days: - days = float(days) - else: - days = 0 - return year,years,month,months,day,days - else: - return None,0,None,0,None,0 - -def _parse_reltime(text, - - int=int,float=float): - - match = _relisotimeRE.search(text) - if match is not None: - groups = match.groups() - if 0 and _debug: print groups - hour,hours,minute,minutes,second,seconds = groups - if hour: - hour = int(hour) - if hours: - hours = float(hours) - else: - hours = 0 - if minute: - minute = int(minute) - if minutes: - minutes = float(minutes) - else: - minutes = 0 - if second: - second = int(second) - if seconds: - seconds = float(seconds) - else: - seconds = 0 - return hour,hours,minute,minutes,second,seconds - else: - return None,0,None,0,None,0 - -def RelativeDateTimeFromString(text, - - RelativeDateTime=DateTime.RelativeDateTime): - - """ RelativeDateTimeFromString(text) - - Returns a RelativeDateTime instance reflecting the relative - date and time given in text. - - Defaults to wildcards for parts or values which are not - included in the textual representation or cannot be parsed. - - The format used in text must adhere to the following syntax: - - [YYYY-MM-DD] [HH:MM[:SS]] - - with the usual meanings. Values which should not be altered - may be replaced with '*', '%', '?' or any combination of - letters, e.g. 'YYYY'. Relative settings must be enclosed in - parenthesis if given and should include a sign, e.g. '(+0001)' - for the year part. All other settings are interpreted as - absolute values. - - Date and time parts are both optional as a whole. Seconds in - the time part are optional too. Everything else (including the - hyphens and colons) is mandatory. - - """ - year,years,month,months,day,days = _parse_reldate(text) - hour,hours,minute,minutes,second,seconds = _parse_reltime(text) - return RelativeDateTime(year=year,years=years, - month=month,months=months, - day=day,days=days, - hour=hour,hours=hours, - minute=minute,minutes=minutes, - second=second,seconds=seconds) - -def RelativeDateFromString(text, - - RelativeDateTime=DateTime.RelativeDateTime): - - """ RelativeDateFromString(text) - - Same as RelativeDateTimeFromString(text) except that only the - date part of text is taken into account. - - """ - year,years,month,months,day,days = _parse_reldate(text) - return RelativeDateTime(year=year,years=years, - month=month,months=months, - day=day,days=days) - -def RelativeTimeFromString(text, - - RelativeDateTime=DateTime.RelativeDateTime): - - """ RelativeTimeFromString(text) - - Same as RelativeDateTimeFromString(text) except that only the - time part of text is taken into account. - - """ - hour,hours,minute,minutes,second,seconds = _parse_reltime(text) - return RelativeDateTime(hour=hour,hours=hours, - minute=minute,minutes=minutes, - second=second,seconds=seconds) - -### Tests - -def _test(): - - import sys - - t = DateTime.now() - - print 'Testing DateTime Parser...' - - l = [ - - # Literal formats - ('Sun Nov 6 08:49:37 1994', '1994-11-06 08:49:37.00'), - ('sun nov 6 08:49:37 1994', '1994-11-06 08:49:37.00'), - ('sUN NOV 6 08:49:37 1994', '1994-11-06 08:49:37.00'), - ('Sunday, 06-Nov-94 08:49:37 GMT', '1994-11-06 08:49:37.00'), - ('Sun, 06 Nov 1994 08:49:37 GMT', '1994-11-06 08:49:37.00'), - ('06-Nov-94 08:49:37', '1994-11-06 08:49:37.00'), - ('06-Nov-94', '1994-11-06 00:00:00.00'), - ('06-NOV-94', '1994-11-06 00:00:00.00'), - ('November 19 08:49:37', '%s-11-19 08:49:37.00' % t.year), - ('Nov. 9', '%s-11-09 00:00:00.00' % t.year), - ('Sonntag, der 6. November 1994, 08:49:37 GMT', '1994-11-06 08:49:37.00'), - ('6. November 2001, 08:49:37', '2001-11-06 08:49:37.00'), - ('sep 6', '%s-09-06 00:00:00.00' % t.year), - ('sep 6 2000', '2000-09-06 00:00:00.00'), - ('September 29', '%s-09-29 00:00:00.00' % t.year), - ('Sep. 29', '%s-09-29 00:00:00.00' % t.year), - ('6 sep', '%s-09-06 00:00:00.00' % t.year), - ('29 September', '%s-09-29 00:00:00.00' % t.year), - ('29 Sep.', '%s-09-29 00:00:00.00' % t.year), - ('sep 6 2001', '2001-09-06 00:00:00.00'), - ('Sep 6, 2001', '2001-09-06 00:00:00.00'), - ('September 6, 2001', '2001-09-06 00:00:00.00'), - ('sep 6 01', '2001-09-06 00:00:00.00'), - ('Sep 6, 01', '2001-09-06 00:00:00.00'), - ('September 6, 01', '2001-09-06 00:00:00.00'), - ('30 Apr 2006 20:19:00', '2006-04-30 20:19:00.00'), - - # ISO formats - ('1994-11-06 08:49:37', '1994-11-06 08:49:37.00'), - ('010203', '2001-02-03 00:00:00.00'), - ('2001-02-03 00:00:00.00', '2001-02-03 00:00:00.00'), - ('2001-02 00:00:00.00', '2001-02-01 00:00:00.00'), - ('2001-02-03', '2001-02-03 00:00:00.00'), - ('2001-02', '2001-02-01 00:00:00.00'), - ('20000824/2300', '2000-08-24 23:00:00.00'), - ('20000824/0102', '2000-08-24 01:02:00.00'), - ('20000824', '2000-08-24 00:00:00.00'), - ('20000824/020301', '2000-08-24 02:03:01.00'), - ('20000824 020301', '2000-08-24 02:03:01.00'), - ('-20000824 020301', '-2000-08-24 02:03:01.00'), - ('20000824T020301', '2000-08-24 02:03:01.00'), - ('20000824 020301', '2000-08-24 02:03:01.00'), - ('2000-08-24 02:03:01.00', '2000-08-24 02:03:01.00'), - ('T020311', '%s 02:03:11.00' % t.date), - ('2003-12-9', '2003-12-09 00:00:00.00'), - ('03-12-9', '2003-12-09 00:00:00.00'), - ('003-12-9', '0003-12-09 00:00:00.00'), - ('0003-12-9', '0003-12-09 00:00:00.00'), - ('2003-1-9', '2003-01-09 00:00:00.00'), - ('03-1-9', '2003-01-09 00:00:00.00'), - ('003-1-9', '0003-01-09 00:00:00.00'), - ('0003-1-9', '0003-01-09 00:00:00.00'), - - # US formats - ('06/11/94 08:49:37', '1994-06-11 08:49:37.00'), - ('11/06/94 08:49:37', '1994-11-06 08:49:37.00'), - ('9/23/2001', '2001-09-23 00:00:00.00'), - ('9-23-2001', '2001-09-23 00:00:00.00'), - ('9/6', '%s-09-06 00:00:00.00' % t.year), - ('09/6', '%s-09-06 00:00:00.00' % t.year), - ('9/06', '%s-09-06 00:00:00.00' % t.year), - ('09/06', '%s-09-06 00:00:00.00' % t.year), - ('9/6/2001', '2001-09-06 00:00:00.00'), - ('09/6/2001', '2001-09-06 00:00:00.00'), - ('9/06/2001', '2001-09-06 00:00:00.00'), - ('09/06/2001', '2001-09-06 00:00:00.00'), - ('9-6-2001', '2001-09-06 00:00:00.00'), - ('09-6-2001', '2001-09-06 00:00:00.00'), - ('9-06-2001', '2001-09-06 00:00:00.00'), - ('09-06-2001', '2001-09-06 00:00:00.00'), - ('2002/05/28 13:10:56.1147 GMT+2', '2002-05-28 13:10:56.11'), - ('1970/01/01', '1970-01-01 00:00:00.00'), - ('20021025 12:00 PM', '2002-10-25 12:00:00.00'), - ('20021025 12:30 PM', '2002-10-25 12:30:00.00'), - ('20021025 12:00 AM', '2002-10-25 00:00:00.00'), - ('20021025 12:30 AM', '2002-10-25 00:30:00.00'), - ('20021025 1:00 PM', '2002-10-25 13:00:00.00'), - ('20021025 2:00 AM', '2002-10-25 02:00:00.00'), - ('Thursday, February 06, 2003 12:40 PM', '2003-02-06 12:40:00.00'), - ('Mon, 18 Sep 2006 23:03:00', '2006-09-18 23:03:00.00'), - - # European formats - ('6.11.2001, 08:49:37', '2001-11-06 08:49:37.00'), - ('06.11.2001, 08:49:37', '2001-11-06 08:49:37.00'), - ('06.11. 08:49:37', '%s-11-06 08:49:37.00' % t.year), - #('21/12/2002', '2002-12-21 00:00:00.00'), - #('21/08/2002', '2002-08-21 00:00:00.00'), - #('21-08-2002', '2002-08-21 00:00:00.00'), - #('13/01/03', '2003-01-13 00:00:00.00'), - #('13/1/03', '2003-01-13 00:00:00.00'), - #('13/1/3', '2003-01-13 00:00:00.00'), - #('13/01/3', '2003-01-13 00:00:00.00'), - - # Time only formats - ('01:03', '%s 01:03:00.00' % t.date), - ('01:03:11', '%s 01:03:11.00' % t.date), - ('01:03:11.50', '%s 01:03:11.50' % t.date), - ('01:03:11.50 AM', '%s 01:03:11.50' % t.date), - ('01:03:11.50 PM', '%s 13:03:11.50' % t.date), - ('01:03:11.50 a.m.', '%s 01:03:11.50' % t.date), - ('01:03:11.50 p.m.', '%s 13:03:11.50' % t.date), - - # Invalid formats - ('6..2001, 08:49:37', '%s 08:49:37.00' % t.date), - ('9//2001', 'ignore'), - ('06--94 08:49:37', 'ignore'), - ('20000824020301', 'ignore'), - ('20-03 00:00:00.00', 'ignore'), - ('9/2001', 'ignore'), - ('9-6', 'ignore'), - ('09-6', 'ignore'), - ('9-06', 'ignore'), - ('09-06', 'ignore'), - ('20000824/23', 'ignore'), - ('November 1994 08:49:37', 'ignore'), - ('Nov. 94', 'ignore'), - ('Mon, 18 Sep 2006 23:03:00 +1234567890', 'ignore'), - - ] - - # Add Unicode versions - try: - unicode - except NameError: - pass - else: - k = [] - for text, result in l: - k.append((unicode(text), result)) - l.extend(k) - - for text, reference in l: - try: - value = DateTimeFromString(text) - except: - if reference is None: - continue - else: - value = str(sys.exc_info()[1]) - valid_datetime = validateDateTimeString(text) - valid_date = validateDateString(text) - if str(value) != reference and \ - not reference == 'ignore': - print 'Failed to parse "%s"' % text - print ' expected: %s' % (reference or '') - print ' parsed: %s' % value - elif _debug: - print 'Parsed "%s" successfully' % text - if _debug: - if not valid_datetime: - print ' "%s" failed date/time validation' % text - if not valid_date: - print ' "%s" failed date validation' % text - - et = DateTime.now() - print 'done. (after %f seconds)' % ((et-t).seconds) - - ### - - print 'Testing DateTimeDelta Parser...' - - t = DateTime.now() - l = [ - - # Literal formats - ('Sun Nov 6 08:49:37 1994', '08:49:37.00'), - ('1 day, 8 hours, 49 minutes, 37 seconds', '1:08:49:37.00'), - ('10 days, 8 hours, 49 minutes, 37 seconds', '10:08:49:37.00'), - ('8 hours, 49 minutes, 37 seconds', '08:49:37.00'), - ('49 minutes, 37 seconds', '00:49:37.00'), - ('37 seconds', '00:00:37.00'), - ('37.5 seconds', '00:00:37.50'), - ('8 hours later', '08:00:00.00'), - ('2 days', '2:00:00:00.00'), - ('2 days 23h', '2:23:00:00.00'), - ('2 days 23:57', '2:23:57:00.00'), - ('2 days 23:57:13', '2:23:57:13.00'), - ('', '00:00:00.00'), - - # ISO formats - ('1994-11-06 08:49:37', '08:49:37.00'), - ('10:08:49:37', '10:08:49:37.00'), - ('08:49:37', '08:49:37.00'), - ('08:49', '08:49:00.00'), - ('-10:08:49:37', '-10:08:49:37.00'), - ('-08:49:37', '-08:49:37.00'), - ('-08:49', '-08:49:00.00'), - ('- 10:08:49:37', '-10:08:49:37.00'), - ('- 08:49:37', '-08:49:37.00'), - ('- 08:49', '-08:49:00.00'), - ('10:08:49:37.5', '10:08:49:37.50'), - ('08:49:37.5', '08:49:37.50'), - ('10:8:49:37', '10:08:49:37.00'), - ('8:9:37', '08:09:37.00'), - ('8:9', '08:09:00.00'), - ('8', '00:00:00.00'), - - # Invalid formats - #('', None), - #('8', None), - - ] - - for text, reference in l: - try: - value = DateTimeDeltaFromString(text) - except: - if reference is None: - continue - else: - value = str(sys.exc_info()[1]) - if str(value) != reference and \ - not reference == 'ignore': - print 'Failed to parse "%s"' % text - print ' expected: %s' % (reference or '') - print ' parsed: %s' % value - elif _debug: - print 'Parsed "%s" successfully' % text - - et = DateTime.now() - print 'done. (after %f seconds)' % ((et-t).seconds) - - ### - - print 'Testing Time Parser...' - - t = DateTime.now() - l = [ - - # Standard formats - ('08:49:37 AM', '08:49:37.00'), - ('08:49:37 PM', '20:49:37.00'), - ('12:00:00 AM', '00:00:00.00'), - ('12:00:00 PM', '12:00:00.00'), - ('8:09:37', '08:09:37.00'), - ('8:09', '08:09:00.00'), - - # ISO formats - ('08:49:37', '08:49:37.00'), - ('08:49', '08:49:00.00'), - ('08:49:37.5', '08:49:37.50'), - ('08:49:37,5', '08:49:37.50'), - ('08:09', '08:09:00.00'), - - # Invalid formats - ('', None), - ('8:9:37', 'XXX Should give an exception'), - ('08:9:37', 'XXX Should give an exception'), - ('8:9', None), - ('8', None), - - ] - - for text, reference in l: - try: - value = TimeFromString(text, formats=('standard', 'iso')) - except: - if reference is None: - continue - else: - value = str(sys.exc_info()[1]) - if str(value) != reference and \ - not reference == 'ignore': - print 'Failed to parse "%s"' % text - print ' expected: %s' % (reference or '') - print ' parsed: %s' % value - elif _debug: - print 'Parsed "%s" successfully' % text - - et = DateTime.now() - print 'done. (after %f seconds)' % ((et-t).seconds) - -if __name__ == '__main__': - _test() diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/Timezone.py --- a/embedded/mx/DateTime/Timezone.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -# -*- coding: latin-1 -*- - -""" Timezone information. - - XXX This module still has prototype status and is undocumented. - - XXX Double check the offsets given in the zonetable below. - - XXX Add TZ environment variable parsing functions. The REs are already - there. - - Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com - Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com - See the documentation for further information on copyrights, - or contact the author. All Rights Reserved. - -""" -import DateTime -import re,string - -### REs - -# time zone parsing -isozone = ('(?P[+-]\d\d:?(?:\d\d)?|Z)') -zone = ('(?P[A-Z]+|[+-]\d\d?:?(?:\d\d)?)') -zoneoffset = ('(?:' - '(?P[+-])?' - '(?P\d\d?)' - ':?' - '(?P\d\d)?' - '(?P\d+)?' - ')' - ) - -# TZ environment variable parsing -dstswitchtime = ('(?P\d\d?):?' - '(?P\d\d)?:?' - '(?P\d\d)?') -dstswitch = ('(?:' - '(?P\d+)|' - '(?:J(?P\d+))|' - '(?:M(?P\d+).(?P\d+).(?P\d+))' - ')' - '(?:/' + dstswitchtime + ')?' - ) - -# XXX Doesn't work since re doesn't like multiple occurrences of -# group names. -#tz = ('(?::(?P.+))|' -# '(?P[A-Z]+)' + zoneoffset + -# '(?:' -# '(?P[A-Z]+)' + zoneoffset + '?'+ -# '(?:[;,]' + dstswitch + '[;,]' + dstswitch + ')' -# ')?' -# ) - -# Compiled RE objects -isozoneRE = re.compile(zone) -zoneRE = re.compile(zone) -zoneoffsetRE = re.compile(zoneoffset) -#tzRE= re.compile(tz) - -### Time zone offset table -# -# The offset given here represent the difference between UTC and the -# given time zone. -# -# Additions and corrections are always welcome :-) -# -# Note that some zone names are ambiguous, e.g. IST can refer to Irish -# Summer Time, Indian Standard Time, Israel Standard Time. We've -# usualy chosen meaning with the most wide-spread use. -# -zonetable = { - # Timezone abbreviations - # Std Summer - - # Standards - 'UT':0, - 'UTC':0, - 'GMT':0, - - # A few common timezone abbreviations - 'CET':1, 'CEST':2, 'CETDST':2, # Central European - 'MET':1, 'MEST':2, 'METDST':2, # Mean European - 'MEZ':1, 'MESZ':2, # Mitteleuropische Zeit - 'EET':2, 'EEST':3, 'EETDST':3, # Eastern Europe - 'WET':0, 'WEST':1, 'WETDST':1, # Western Europe - 'MSK':3, 'MSD':4, # Moscow - 'IST':5.5, # India - 'JST':9, # Japan - 'KST':9, # Korea - 'HKT':8, # Hong Kong - - # US time zones - 'AST':-4, 'ADT':-3, # Atlantic - 'EST':-5, 'EDT':-4, # Eastern - 'CST':-6, 'CDT':-5, # Central - 'MST':-7, 'MDT':-6, # Midwestern - 'PST':-8, 'PDT':-7, # Pacific - - # Australian time zones - 'CAST':9.5, 'CADT':10.5, # Central - 'EAST':10, 'EADT':11, # Eastern - 'WAST':8, 'WADT':9, # Western - 'SAST':9.5, 'SADT':10.5, # Southern - - # US military time zones - 'Z': 0, - 'A': 1, - 'B': 2, - 'C': 3, - 'D': 4, - 'E': 5, - 'F': 6, - 'G': 7, - 'H': 8, - 'I': 9, - 'K': 10, - 'L': 11, - 'M': 12, - 'N':-1, - 'O':-2, - 'P':-3, - 'Q':-4, - 'R':-5, - 'S':-6, - 'T':-7, - 'U':-8, - 'V':-9, - 'W':-10, - 'X':-11, - 'Y':-12 - } - -def utc_offset(zone, - - atoi=string.atoi,zoneoffset=zoneoffsetRE, - zonetable=zonetable,zerooffset=DateTime.DateTimeDelta(0), - oneMinute=DateTime.oneMinute,upper=string.upper): - - """ utc_offset(zonestring) - - Return the UTC time zone offset as DateTimeDelta instance. - - zone must be string and can either be given as +-HH:MM, - +-HHMM, +-HH numeric offset or as time zone - abbreviation. Daylight saving time must be encoded into the - zone offset. - - Timezone abbreviations are treated case-insensitive. - - """ - if not zone: - return zerooffset - uzone = upper(zone) - if zonetable.has_key(uzone): - return zonetable[uzone]*DateTime.oneHour - offset = zoneoffset.match(zone) - if not offset: - raise ValueError,'wrong format or unknown time zone: "%s"' % zone - zonesign,hours,minutes,extra = offset.groups() - if extra: - raise ValueError,'illegal time zone offset: "%s"' % zone - offset = int(hours or 0) * 60 + int(minutes or 0) - if zonesign == '-': - offset = -offset - return offset*oneMinute - diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/__init__.py --- a/embedded/mx/DateTime/__init__.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -""" mxDateTime - Date and time handling routines and types - - Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com - Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com - See the documentation for further information on copyrights, - or contact the author. All Rights Reserved. -""" -from DateTime import * -from DateTime import __version__ - -## mock strptime implementation -from datetime import datetime - -def strptime(datestr, formatstr, datetime=datetime): - """mocked strptime implementation""" - date = datetime.strptime(datestr, formatstr) - return DateTime(date.year, date.month, date.day, - date.hour, date.minute, date.second) - -# don't expose datetime directly -del datetime diff -r a721966779be -r cba9f175da2d embedded/mx/DateTime/mxDateTime_python.py --- a/embedded/mx/DateTime/mxDateTime_python.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,614 +0,0 @@ -""" - Python implementation courtesy of Drew Csillag (StarMedia Network, Inc.) - - This version has been somewhat modified by MAL. It is still fairly - rough though and not necessarily high performance... - - XXX Still needs testing and checkup !!! - - WARNING: Using this file is only recommended if you really must - use it for some reason. It is not being actively maintained ! - -""" - -__version__ = '1.2.0 [Python]' - -import time,types,exceptions,math - -### Errors - -class Error(exceptions.StandardError): - pass - -class RangeError(Error): - pass - -### Constants (internal use only) - -month_offset=( - (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365), - (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366), - ) - -days_in_month=( - (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), - (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), - ) - -### Helpers - -def _IS_LEAPYEAR(d): - return ((d.year % 4 == 0) - and ( - (d.year % 100 != 0) - or (d.year % 400 == 0) - ) - ) - -def _YEAROFFSET(d): - return ( - (d.year - 1) * 365 - + (d.year - 1) / 4 - - (d.year - 1) / 100 - + (d.year - 1) / 400 - ) - -class _EmptyClass: - pass - -def createEmptyObject(Class, - _EmptyClass=_EmptyClass): - - o = _EmptyClass() - o.__class__ = Class - return o - -### DateTime class - -class DateTime: - - def __init__(self, year, month=1, day=1, hour=0, minute=0, second=0.0): - - second=1.0 * second - if month <= 0: - raise RangeError, "year out of range (>0)" - - #calculate absolute date - leap = (year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0)) - - #Negative values indicate days relative to the years end - if month < 0: - month = month + 13 - - if not (month >= 1 and month <= 12): - raise RangeError, "month out of range (1-12)" - - #Negative values indicate days relative to the months end - if (day < 0): - day = day + days_in_month[leap][month - 1] + 1; - - if not (day >= 1 and day <= days_in_month[leap][month - 1]): - raise RangeError, "day out of range" - - year = year - 1 - yearoffset = year * 365 + year / 4 - year / 100 + year / 400 - year = year + 1 - absdate = day + month_offset[leap][month - 1] + yearoffset; - - self.absdate = absdate - self.year = year - self.month = month - self.day = day - self.day_of_week = (absdate - 1) % 7 - self.day_of_year = absdate - yearoffset - self.days_in_month = days_in_month[leap][month - 1] - comdate = absdate - 693594 - - if not (hour >=0 and hour <= 23): - raise RangeError, "hour out of range (0-23)" - if not (minute >= 0 and minute <= 59): - raise RangeError, "minute out of range (0-59)" - if not (second >= 0.0 and - (second < 60.0 or - (hour == 23 and minute == 59 and second < 61.0))): - raise RangeError, "second out of range (0.0 - <60.0; <61.0 for 23:59)" - - self.abstime = (hour * 3600 + minute * 60) + second - self.hour = hour - self.minute = minute - self.second = second - self.dst = -1 - self.tz = "???" - self.is_leapyear = leap - self.yearoffset = yearoffset - self.iso_week = (self.year, self.day, self.day_of_week) - - if comdate < 0.0: - comdate = comdate - self.abstime / 86400.0 - else: - comdate = comdate + self.abstime / 86400.0 - - self.comdate = comdate - - def COMDate(self): - return self.comdate - - def __str__(self): - return "%04d-%02d-%02d %02d:%02d:%05.2f" % ( - self.year, self.month, self.day, self.hour, self.minute, - self.second) - - def __getattr__(self, attr): - if attr == 'mjd': - return (self - mjd0).days - elif attr == 'jdn': - return (self - jdn0).days - elif attr == 'tjd': - return (self - jdn0).days % 10000 - elif attr == 'tjd_myriad': - return int((self - jdn0).days) / 10000 + 240 - elif attr == 'absdays': - return self.absdate - 1 + self.abstime / 86400.0 - else: - try: - return self.__dict__[attr] - except: - raise AttributeError, attr - - def __mul__(self, other): - raise TypeError, "bad operand type(s) for *" - - def __div__(self, other): - raise TypeError, "bad operand type(s) for /" - - def strftime(self, format_string="%c"): - "localtime([seconds]) -> (tm_year,tm_mon,tm_day,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst)" - # The map prevents a deprecation warning on Python 2.5.1 (Mac) - # DeprecationWarning: integer argument expected, got float - items = [int(item) for item in self.tuple()] - return time.strftime(format_string, items) - - # Alias - Format = strftime - - def tuple(self): - return (self.year, self.month, self.day, self.hour, self.minute, - self.second, self.day_of_week, self.day_of_year, -1) - #return time.localtime(self.ticks()) - - def absvalues(self): - return self.absdate, self.abstime - - def __float__(self): - return self.ticks() - - def __int__(self): - return int(self.ticks) - - def ticks(self, offset=0.0, dst=-1): - tticks=time.mktime(self.year, self.month, self.day, self.hour, - self.minute, self.second, self.day_of_week, 0, dst) - if tticks == -1: - raise OverflowError, "cannot convert value to a time value" - ticks = (1.0*tticks) + (self.abstime - int(self.abstime)) - offset - return ticks - - def gmticks(self, offset=0.0): - from mx.DateTime import tz_offset - return (self-tz_offset(self)).ticks() - - def gmtoffset(self): - gmtime = DateTime(*time.gmtime()[:6]) - return - (now() - gmtime) - - def __repr__(self): - return ""% ( - self.year, self.month, self.day, self.hour, self.minute, - self.second, id(self)) - - def __cmp__(self, other, - cmp=cmp): - - if isinstance(other,DateTime): - cmpdate = cmp(self.absdate,other.absdate) - if cmpdate == 0: - return cmp(self.abstime,other.abstime) - else: - return cmpdate - elif type(other) == types.NoneType: - return -1 - elif type(other) == types.StringType: - return -1 - elif type(other) in (types.FloatType, types.LongType, types.IntType): - return 1 - return -1 - - def __hash__(self): - return hash(self.tuple()) - - def __add__(self, other): - abstime=self.abstime - absdate=self.absdate - - didadd=0 - - if type(other) == types.InstanceType: - if other.__class__ == DateTimeDelta: - abstime = abstime + other.seconds - didadd=1 - elif other.__class__ == DateTime: - raise TypeError, "DateTime + DateTime is not supported" - else: - return other.__class__.__radd__(other, self) - - elif type(other) == types.IntType or type(other) == types.FloatType: - abstime = abstime + other * 86400.0 - didadd=1 - - if not didadd: - raise TypeError, "cannot add these two types" - - if abstime >= 86400.0: - days = abstime / 86400.0 - absdate = absdate + days - abstime = abstime - (86400.0 * int(days)) - #print "absdate, abstime = ", absdate, abstime - elif abstime < 0.0: - days = int(((-abstime - 1) / 86400.0)) + 1 - #days = int(-abstime / 86400.0) - absdate = absdate - days - abstime = abstime + 86400.0 * int(days) - - if absdate < 1: - raise RangeError, "underflow while adding" - - return DateTimeFromAbsDateTime(absdate, abstime) - - def __radd__(self, other): - return DateTime.__add__(other, self) - - def __sub__(self, other): - abstime=self.abstime - absdate=self.absdate - - didsub=0 - if type(other) == types.InstanceType: - if other.__class__ == DateTimeDelta: - abstime = abstime - other.seconds - didsub = 1 - elif other.__class__ == DateTime: - absdate = absdate - other.absdate - abstime = abstime - other.abstime - return DateTimeDelta(absdate,0.0,0.0,abstime) - else: - return other.__rsub__(self) - - elif type(other) == types.IntType or type(other) == types.FloatType: - abstime = abstime - other * 86400.0; - didsub=1 - - if not didsub: - raise TypeError, "cannot subtract these two types" - - if abstime >= 86400.0: - days = abstime / 86400.0 - absdate = absdate + days - abstime = abstime - (86400.0 * days) - #print "absdate, abstime = ", absdate, abstime - elif abstime < 0.0: - #print "abstime < 0" - days = int( ((-abstime - 1) / 86400.0) + 1) - #days = -abstime / 86400.0 - absdate = absdate - int(days) - abstime = (1.0*abstime) + (86400.0 * days) - #print "absdate, abstime", absdate, abstime - if absdate < 1: - raise RangeError, "underflow while adding" - - return DateTimeFromAbsDateTime(absdate, abstime) - -# Constants -mjd0 = DateTime(1858, 11, 17) -jdn0 = DateTime(-4713, 1, 1, 12, 0, 0.0) - -# Other DateTime constructors - -def DateTimeFromCOMDate(comdate): - - absdate = int(comdate) - abstime = (comdate - float(absdate)) * 86400.0 - if abstime < 0.0: - abstime = -abstime - absdate = absdate + 693594; - dt = DateTimeFromAbsDateTime(absdate, abstime) - dt.comdate = comdate - return dt - -def DateTimeFromAbsDateTime(absdate, abstime): - - # Create the object without calling its default constructor - dt = createEmptyObject(DateTime) - - # Init. the object - abstime=1.0 * abstime - if abstime < 0 and abstime > -0.001: abstime = 0.0 - if not (absdate > 0): - raise RangeError, "absdate out of range (>0)" - if not (abstime >= 0.0 and abstime <= 86400.0): - raise RangeError, "abstime out of range (0.0 - 86400.0) <%s>" % abstime - - dt.absdate=absdate - dt.abstime=abstime - - #calculate com date - comdate = 1.0 * (dt.absdate - 693594) - if comdate < 0.0: - comdate = comdate - dt.abstime / 86400.0 - else: - comdate = comdate + dt.abstime / 86400.0 - dt.comdate = comdate - - #calculate the date - #print "absdate=", absdate - year = int((1.0 * absdate) / 365.2425) - - #newApproximation: - while 1: - #print "year=", year - yearoffset = year * 365 + year / 4 - year / 100 + year / 400 - #print "yearoffset=", yearoffset - #print "absdate=", absdate - if yearoffset >= absdate: - year = year - 1 - #print "year = ", year - continue #goto newApproximation - - year = year + 1 - leap = (year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0)) - dayoffset = absdate - yearoffset - #print "dayoffset=", dayoffset - if dayoffset > 365 and leap == 0: - #print "dayoffset=", dayoffset - continue #goto newApproximation - - monthoffset = month_offset[leap] - for month in range(1, 13): - if monthoffset[month] >= dayoffset: - break - dt.year = year - dt.month = month - dt.day = dayoffset - month_offset[leap][month-1] - dt.day_of_week = (dt.absdate - 1) % 7 - dt.day_of_year = dayoffset - break - - #calculate the time - inttime = int(abstime) - hour = inttime / 3600 - minute = (inttime % 3600) / 60 - second = abstime - 1.0 * (hour*3600 + minute*60) - dt.hour = hour; - dt.minute = minute; - dt.second = second; - dt.days_in_month = days_in_month[leap][month - 1] - dt.dst = -1 - dt.tz = "???" - dt.is_leapyear = leap - dt.yearoffset = yearoffset - return dt - -def now( - time=time.time,float=float,localtime=time.localtime, - round=round,int=int,DateTime=DateTime,floor=math.floor): - ticks = time() - Y,M,D,h,m,s = localtime(ticks)[:6] - s = s + (ticks - floor(ticks)) - return DateTime(Y,M,D,h,m,s) - -def utc( - time=time.time,float=float,gmtime=time.gmtime, - round=round,int=int,DateTime=DateTime,floor=math.floor): - - ticks = time() - Y,M,D,h,m,s = gmtime(ticks)[:6] - s = s + (ticks - floor(ticks)) - return DateTime(Y,M,D,h,m,s) - -# Aliases -Date = Timestamp = DateTime - -# XXX Calendars are not supported: -def notSupported(*args,**kws): - raise Error,'calendars are not supported by the Python version of mxDateTime' -JulianDateTime = notSupported - -### DateTimeDelta class - -class DateTimeDelta: - - def __init__(self, days=0, hours=0, minutes=0, seconds=0): - - seconds = seconds + (days * 86400.0 + hours * 3600.0 + minutes * 60.0) - self.seconds = seconds - if seconds < 0.0: - seconds = -seconds - day = long(seconds / 86400.0) - seconds = seconds - (86400.0 * day) - wholeseconds = int(seconds) - hour = wholeseconds / 3600 - minute = (wholeseconds % 3600) / 60 - second = seconds - (hour * 3600.0 + minute * 60.0) - self.day = day - self.hour = hour - self.minute = minute - self.second = second - seconds=self.seconds - self.minutes = seconds / 60.0 - self.hours = seconds / 3600.0 - self.days = seconds / 86400.0 - - def __str__(self): - if self.day != 0: - if self.seconds >= 0.0: - r="%s:%02d:%02d:%05.2f" % ( - self.day, self.hour, self.minute, self.second) - else: - r="-%s:%02d:%02d:%05.2f" % ( - self.day, self.hour, self.minute, self.second) - else: - if self.seconds >= 0.0: - r="%02d:%02d:%05.2f" % (self.hour, self.minute, self.second) - else: - r="-%02d:%02d:%05.2f" % (self.hour, self.minute, self.second) - return r - - def absvalues(self): - days=self.seconds / 86400 - seconds=self.seconds - (days * 86400.0) - return days, seconds - - def tuple(self): - return (self.day, self.hour, self.minute, self.second) - - def strftime(self, format_string): - raise NotImplementedError - - def __int__(self): - return int(self.seconds) - - def __float__(self): - return self.seconds - - def __cmp__(self, other, accuracy=0.0): - if (type(other) == types.InstanceType - and other.__class__ == DateTimeDelta): - - diff=self.seconds - other.seconds - if abs(diff) > accuracy: - if diff > 0: return 1 - return -1 - - elif type(other) == types.FloatType: - diff=self.seconds - other - if abs(diff) > accuracy: - if diff > 0: return 1 - return -1 - - elif type(other) == types.IntType: - diff=self.seconds - other - if abs(diff) > accuracy: - if diff > 0: return 1 - return -1 - - return 0 - - def __getattr__(self, attr): - seconds=self.__dict__['seconds'] - if attr in ('hour', 'minute', 'second', 'day'): - if seconds >= 0.0: - return self.__dict__[attr] - else: - return -self.__dict__[attr] - else: - try: - return self.__dict__[attr] - except: - raise AttributeError, attr - - def __div__(self, other): - if type(other) in (types.IntType, types.FloatType): - return DateTimeDelta(0.0,0.0,0.0,self.seconds / other) - elif (type(other) == types.InstanceType - and isinstance(other,DateTimeDelta)): - return DateTimeDelta(0.0,0.0,0.0,self.seconds / other.seconds) - raise TypeError, "bad operand types for /" - - def __mul__(self, other): - if type(other) == types.IntType or type(other) == types.FloatType: - return DateTimeDelta(0.0,0.0,0.0,self.seconds * other) - else: - #print "type", type(other) - raise TypeError, "cannot multiply these two types" - - def __rmul__(self, other): - return self.__mul__(other) - - def __neg__(self): - return DateTimeDelta(0.0,0.0,0.0,-self.seconds) - - def __repr__(self): - if self.day != 0: - if self.seconds >= 0.0: - strval="%s:%02d:%02d:%05.2f" % (self.day, self.hour, - self.minute, self.second) - else: - strval="-%s:%02d:%02d:%05.2f" % (self.day, self.hour, - self.minute, self.second) - else: - if self.seconds >= 0.0: - strval="%02d:%02d:%05.2f" % (self.hour, self.minute, - self.second) - else: - strval="-%02d:%02d:%05.2f" % (self.hour, self.minute, - self.second) - return "" % (strval, id(self)) - - def __abs__(self): - if self.seconds < 0: - return -self - return self - - def __nonzero__(self): - return self.seconds != 0.0 - - def __add__(self, other): - if type(other) == types.InstanceType: - if isinstance(other,DateTime): - return other + self - elif isinstance(other,DateTimeDelta): - return DateTimeDelta(0.0,0.0,0.0,self.seconds + other.seconds) - - # What about __radd__ ? - -# Other DateTimeDelta constructors - -def TimeDelta(hour=0.0, minute=0.0, second=0.0): - return DateTimeDelta(0.0, hours, minutes, seconds) - -Time=TimeDelta - -def DateTimeDeltaFromSeconds(seconds): - return DateTimeDelta(0.0,0.0,0.0,seconds) - -def DateTimeDeltaFromDays(days): - return DateTimeDelta(days) - -### Types - -DateTimeType = DateTime -DateTimeDeltaType = DateTimeDelta - -### Functions - -def cmp(a,b,acc): - - if isinstance(a,DateTime) and isinstance(b,DateTime): - diff = a.absdays - b.absdays - if (diff >= 0 and diff <= acc) or (diff < 0 and -diff <= acc): - return 0 - elif diff < 0: - return 1 - else: - return -1 - - elif isinstance(a,DateTimeDelta) and isinstance(b,DateTimeDelta): - diff = a.days - b.days - if (diff >= 0 and diff <= acc) or (diff < 0 and -diff <= acc): - return 0 - elif diff < 0: - return 1 - else: - return -1 - - else: - raise TypeError,"objects must be DateTime[Delta] instances" diff -r a721966779be -r cba9f175da2d embedded/mx/__init__.py diff -r a721966779be -r cba9f175da2d entities/authobjs.py --- a/entities/authobjs.py Thu May 07 16:33:22 2009 +0200 +++ b/entities/authobjs.py Thu May 07 16:42:34 2009 +0200 @@ -24,7 +24,7 @@ id = 'CWUser' fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname']) fetch_unrelated_order = fetch_order - + # used by repository to check if the user can log in or not AUTHENTICABLE_STATES = ('activated',) @@ -37,7 +37,7 @@ self._groups = groups if properties is not None: self._properties = properties - + @property def groups(self): try: @@ -45,7 +45,7 @@ except AttributeError: self._groups = set(g.name for g in self.in_group) return self._groups - + @property def properties(self): try: @@ -64,7 +64,7 @@ except ValueError: self.warning('incorrect value for eproperty %s of user %s', key, self.login) return self.vreg.property_value(key) - + def matching_groups(self, groups): """return the number of the given group(s) in which the user is @@ -86,7 +86,7 @@ """ checks if user is an anonymous user""" #FIXME on the web-side anonymous user is detected according # to config['anonymous-user'], we don't have this info on - # the server side. + # the server side. return self.groups == frozenset(('guests', )) def owns(self, eid): @@ -116,12 +116,12 @@ return self.req.execute(rql, kwargs, cachekey) except Unauthorized: return False - + # presentation utilities ################################################## - + def name(self): """construct a name using firstname / surname or login if not defined""" - + if self.firstname and self.surname: return self.req._('%(firstname)s %(surname)s') % { 'firstname': self.firstname, 'surname' : self.surname} diff -r a721966779be -r cba9f175da2d entities/test/data/bootstrap_packages --- a/entities/test/data/bootstrap_packages Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ - diff -r a721966779be -r cba9f175da2d entities/test/unittest_base.py --- a/entities/test/unittest_base.py Thu May 07 16:33:22 2009 +0200 +++ b/entities/test/unittest_base.py Thu May 07 16:42:34 2009 +0200 @@ -18,28 +18,27 @@ def setup_database(self): self.member = self.create_user('member') - - - + + + class MetadataTC(BaseEntityTC): def test_creator(self): self.login(u'member') - card = self.add_entity('Card', title=u"hello") + entity = self.add_entity('Bookmark', title=u"hello", path=u'project/cubicweb') self.commit() - self.assertEquals(card.creator.eid, self.member.eid) - self.assertEquals(card.dc_creator(), u'member') + self.assertEquals(entity.creator.eid, self.member.eid) + self.assertEquals(entity.dc_creator(), u'member') def test_type(self): - self.assertEquals(self.member.dc_type(), 'euser') - + self.assertEquals(self.member.dc_type(), 'cwuser') def test_entity_meta_attributes(self): # XXX move to yams self.assertEquals(self.schema['CWUser'].meta_attributes(), {}) - self.assertEquals(dict((str(k), v) for k, v in self.schema['Card'].meta_attributes().iteritems()), - {'content_format': ('format', 'content')}) - + self.assertEquals(dict((str(k), v) for k, v in self.schema['State'].meta_attributes().iteritems()), + {'description_format': ('format', 'description')}) + class CWUserTC(BaseEntityTC): def test_dc_title_and_name(self): @@ -53,9 +52,9 @@ self.assertEquals(e.dc_title(), 'member') self.assertEquals(e.name(), u'bouah lôt') - + class StateAndTransitionsTC(BaseEntityTC): - + def test_transitions(self): user = self.entity('CWUser X') e = self.entity('State S WHERE S name "activated"') @@ -74,7 +73,7 @@ user = self.entity('CWUser X') self.assert_(not user.can_pass_transition('deactivate')) self.assert_(not user.can_pass_transition('activate')) - + def test_transitions_with_dest_specfied(self): user = self.entity('CWUser X') e = self.entity('State S WHERE S name "activated"') @@ -85,20 +84,20 @@ self.assertEquals(trs[0].destination().name, u'deactivated') trs = list(e.transitions(user, e.eid)) self.assertEquals(len(trs), 0) - + def test_transitions_maybe_passed(self): self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", ' 'X expression "X owned_by U", T condition X ' 'WHERE T name "deactivate"') self._test_deactivated() - + def test_transitions_maybe_passed_using_has_update_perm(self): self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", ' 'X expression "U has_update_permission X", T condition X ' 'WHERE T name "deactivate"') self._test_deactivated() - - + + def _test_deactivated(self): ueid = self.create_user('toto').eid self.create_user('tutu') @@ -116,12 +115,12 @@ self.assertRaises(ValidationError, cu.execute, 'SET X in_state S WHERE X eid %(x)s, S name "activated"', {'x': ueid}, 'x') - + def test_transitions_selection(self): """ ------------------------ tr1 ----------------- - | state1 (Card, Bookmark) | ------> | state2 (Card) | + | state1 (CWGroup, Bookmark) | ------> | state2 (CWGroup) | ------------------------ ----------------- | tr2 ------------------ `------> | state3 (Bookmark) | @@ -132,11 +131,11 @@ state3 = self.add_entity('State', name=u'state3') tr1 = self.add_entity('Transition', name=u'tr1') tr2 = self.add_entity('Transition', name=u'tr2') - self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "Card"' % + self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "CWGroup"' % (state1.eid, state2.eid)) self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "Bookmark"' % (state1.eid, state3.eid)) - self.execute('SET X transition_of Y WHERE X eid %s, Y name "Card"' % tr1.eid) + self.execute('SET X transition_of Y WHERE X eid %s, Y name "CWGroup"' % tr1.eid) self.execute('SET X transition_of Y WHERE X eid %s, Y name "Bookmark"' % tr2.eid) self.execute('SET X allowed_transition Y WHERE X eid %s, Y eid %s' % (state1.eid, tr1.eid)) @@ -146,36 +145,35 @@ (tr1.eid, state2.eid)) self.execute('SET X destination_state Y WHERE X eid %s, Y eid %s' % (tr2.eid, state3.eid)) - self.execute('SET X initial_state Y WHERE Y eid %s, X name "Card"' % state1.eid) + self.execute('SET X initial_state Y WHERE Y eid %s, X name "CWGroup"' % state1.eid) self.execute('SET X initial_state Y WHERE Y eid %s, X name "Bookmark"' % state1.eid) - card = self.add_entity('Card', title=u't1') - bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view') - - transitions = list(state1.transitions(card)) + group = self.add_entity('CWGroup', name=u't1') + transitions = list(state1.transitions(group)) self.assertEquals(len(transitions), 1) self.assertEquals(transitions[0].name, 'tr1') + bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view') transitions = list(state1.transitions(bookmark)) self.assertEquals(len(transitions), 1) self.assertEquals(transitions[0].name, 'tr2') - + def test_transitions_selection2(self): """ ------------------------ tr1 (Bookmark) ----------------------- - | state1 (Card, Bookmark) | -------------> | state2 (Card,Bookmark) | + | state1 (CWGroup, Bookmark) | -------------> | state2 (CWGroup,Bookmark) | ------------------------ ----------------------- - | tr2 (Card) | + | tr2 (CWGroup) | `---------------------------------/ """ state1 = self.add_entity('State', name=u'state1') state2 = self.add_entity('State', name=u'state2') tr1 = self.add_entity('Transition', name=u'tr1') tr2 = self.add_entity('Transition', name=u'tr2') - self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "Card"' % + self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "CWGroup"' % (state1.eid, state2.eid)) self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "Bookmark"' % (state1.eid, state2.eid)) - self.execute('SET X transition_of Y WHERE X eid %s, Y name "Card"' % tr1.eid) + self.execute('SET X transition_of Y WHERE X eid %s, Y name "CWGroup"' % tr1.eid) self.execute('SET X transition_of Y WHERE X eid %s, Y name "Bookmark"' % tr2.eid) self.execute('SET X allowed_transition Y WHERE X eid %s, Y eid %s' % (state1.eid, tr1.eid)) @@ -185,18 +183,17 @@ (tr1.eid, state2.eid)) self.execute('SET X destination_state Y WHERE X eid %s, Y eid %s' % (tr2.eid, state2.eid)) - self.execute('SET X initial_state Y WHERE Y eid %s, X name "Card"' % state1.eid) + self.execute('SET X initial_state Y WHERE Y eid %s, X name "CWGroup"' % state1.eid) self.execute('SET X initial_state Y WHERE Y eid %s, X name "Bookmark"' % state1.eid) - card = self.add_entity('Card', title=u't1') - bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view') - - transitions = list(state1.transitions(card)) + group = self.add_entity('CWGroup', name=u't1') + transitions = list(state1.transitions(group)) self.assertEquals(len(transitions), 1) self.assertEquals(transitions[0].name, 'tr1') + bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view') transitions = list(state1.transitions(bookmark)) self.assertEquals(len(transitions), 1) self.assertEquals(transitions[0].name, 'tr2') - + class EmailAddressTC(BaseEntityTC): def test_canonical_form(self): @@ -226,12 +223,12 @@ class CWUserTC(BaseEntityTC): - + def test_complete(self): e = self.entity('CWUser X WHERE X login "admin"') e.complete() - + def test_matching_groups(self): e = self.entity('CWUser X WHERE X login "admin"') self.failUnless(e.matching_groups('managers')) @@ -275,7 +272,7 @@ # clear selector cache clear_cache(self.vreg, 'etype_class') return self.vreg.etype_class(etype) - + def test_etype_class_selection_and_specialization(self): # no specific class for Subdivisions, the default one should be selected eclass = self.select_eclass('SubDivision') @@ -297,6 +294,6 @@ # check Division eclass is still selected for plain Division entities eclass = self.select_eclass('Division') self.assertEquals(eclass.id, 'Division') - + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d entities/wfobjs.py --- a/entities/wfobjs.py Thu May 07 16:33:22 2009 +0200 +++ b/entities/wfobjs.py Thu May 07 16:42:34 2009 +0200 @@ -8,7 +8,7 @@ from cubicweb.entities import AnyEntity, fetch_config - + class Transition(AnyEntity): """customized class for Transition entities @@ -17,7 +17,7 @@ """ id = 'Transition' fetch_attrs, fetch_order = fetch_config(['name']) - + def may_be_passed(self, eid, stateeid): """return true if the logged user may pass this transition @@ -44,7 +44,7 @@ def destination(self): return self.destination_state[0] - + def after_deletion_path(self): """return (path, parameters) which should be used as redirect information when this entity is being deleted @@ -53,7 +53,7 @@ return self.transition_of[0].rest_path(), {'vid': 'workflow'} return super(Transition, self).after_deletion_path() - + class State(AnyEntity): """customized class for State entities @@ -63,7 +63,7 @@ id = 'State' fetch_attrs, fetch_order = fetch_config(['name']) rest_attr = 'eid' - + def transitions(self, entity, desteid=None): rql = ('Any T,N,DS where S allowed_transition T, S eid %(x)s, ' 'T name N, T destination_state DS, ' @@ -75,7 +75,7 @@ for tr in rset.entities(): if tr.may_be_passed(entity.eid, self.eid): yield tr - + def after_deletion_path(self): """return (path, parameters) which should be used as redirect information when this entity is being deleted @@ -84,7 +84,7 @@ return self.state_of[0].rest_path(), {'vid': 'workflow'} return super(State, self).after_deletion_path() - + class TrInfo(AnyEntity): """customized class for Transition information entities """ @@ -97,7 +97,7 @@ @property def previous_state(self): return self.from_state and self.from_state[0] - + @property def new_state(self): return self.to_state[0] diff -r a721966779be -r cba9f175da2d entity.py --- a/entity.py Thu May 07 16:33:22 2009 +0200 +++ b/entity.py Thu May 07 16:42:34 2009 +0200 @@ -51,11 +51,11 @@ def _dispatch_rtags(tags, rtype, role, stype, otype): for tag in tags: if tag in _MODE_TAGS: - uicfg.rmode.set_rtag(tag, rtype, role, stype, otype) + uicfg.rmode.tag_relation(tag, (stype, rtype, otype), role) elif tag in _CATEGORY_TAGS: - uicfg.rcategories.set_rtag(tag, rtype, role, stype, otype) + uicfg.rcategories.tag_relation(tag, (stype, rtype, otype), role) elif tag == 'inlineview': - uicfg.rinlined.set_rtag(True, rtype, role, stype, otype) + uicfg.rinlined.tag_relation(True, (stype, rtype, otype), role) else: raise ValueError(tag) @@ -127,7 +127,9 @@ if wdgname == 'StringWidget': wdgname = 'TextInput' widget = getattr(formwidgets, wdgname) - AutomaticEntityForm.rwidgets.set_rtag(wdgname, rtype, 'subject', etype) + assert hasattr(widget, 'render') + AutomaticEntityForm.rwidgets.tag_relation( + widget, (etype, rtype, '*'), 'subject') return super(_metaentity, mcs).__new__(mcs, name, bases, classdict) diff -r a721966779be -r cba9f175da2d etwist/server.py --- a/etwist/server.py Thu May 07 16:33:22 2009 +0200 +++ b/etwist/server.py Thu May 07 16:42:34 2009 +0200 @@ -10,6 +10,7 @@ import select from time import mktime from datetime import date, timedelta +from urlparse import urlsplit, urlunsplit from twisted.application import service, strports from twisted.internet import reactor, task, threads @@ -43,6 +44,14 @@ # ensure no tasks will be further added repo._looping_tasks = () +def host_prefixed_baseurl(baseurl, host): + scheme, netloc, url, query, fragment = urlsplit(baseurl) + netloc_domain = '.' + '.'.join(netloc.split('.')[-2:]) + if host.endswith(netloc_domain): + netloc = host + baseurl = urlunsplit((scheme, netloc, url, query, fragment)) + return baseurl + class LongTimeExpiringFile(static.File): """overrides static.File and sets a far futre ``Expires`` date @@ -167,6 +176,9 @@ else: https = False baseurl = self.base_url + if self.config['use-request-subdomain']: + baseurl = host_prefixed_baseurl(baseurl, host) + self.warning('used baseurl is %s for this request', baseurl) req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl) if req.authmode == 'http': # activate realm-based auth diff -r a721966779be -r cba9f175da2d etwist/test/unittest_server.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etwist/test/unittest_server.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,35 @@ +from cubicweb.devtools.apptest import EnvBasedTC +from cubicweb.etwist.server import host_prefixed_baseurl + + +class HostPrefixedBaseURLTC(EnvBasedTC): + + def _check(self, baseurl, host, waited): + self.assertEquals(host_prefixed_baseurl(baseurl, host), waited, + 'baseurl %s called through host %s should be considered as %s' + % (baseurl, host, waited)) + + def test1(self): + self._check('http://www.cubicweb.org/hg/', 'code.cubicweb.org', + 'http://code.cubicweb.org/hg/') + + def test2(self): + self._check('http://www.cubicweb.org/hg/', 'cubicweb.org', + 'http://www.cubicweb.org/hg/') + + def test3(self): + self._check('http://cubicweb.org/hg/', 'code.cubicweb.org', + 'http://code.cubicweb.org/hg/') + + def test4(self): + self._check('http://www.cubicweb.org/hg/', 'localhost', + 'http://www.cubicweb.org/hg/') + + def test5(self): + self._check('http://www.cubicweb.org/cubes/', 'hg.code.cubicweb.org', + 'http://hg.code.cubicweb.org/cubes/') + + def test6(self): + self._check('http://localhost:8080/hg/', 'code.cubicweb.org', + 'http://localhost:8080/hg/') + diff -r a721966779be -r cba9f175da2d ext/rest.py --- a/ext/rest.py Thu May 07 16:33:22 2009 +0200 +++ b/ext/rest.py Thu May 07 16:42:34 2009 +0200 @@ -76,31 +76,6 @@ register_canonical_role('eid', eid_reference_role) -def card_reference_role(role, rawtext, text, lineno, inliner, - options={}, content=[]): - text = text.strip() - try: - wikiid, rest = text.split(u':', 1) - except: - wikiid, rest = text, text - context = inliner.document.settings.context - cardrset = context.req.execute('Card X WHERE X wikiid %(id)s', - {'id': wikiid}) - if cardrset: - ref = cardrset.get_entity(0, 0).absolute_url() - else: - schema = context.schema - if schema.eschema('Card').has_perm(context.req, 'add'): - ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid) - else: - ref = '#' - set_classes(options) - return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref, - **options)], [] - -register_canonical_role('card', card_reference_role) - - def winclude_directive(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): """Include a reST file as part of the content of this reST file. diff -r a721966779be -r cba9f175da2d ext/test/unittest_rest.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/test/unittest_rest.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,40 @@ +from logilab.common.testlib import unittest_main +from cubicweb.devtools.apptest import EnvBasedTC + +from cubicweb.ext.rest import rest_publish + +class RestTC(EnvBasedTC): + def context(self): + return self.execute('CWUser X WHERE X login "admin"').get_entity(0, 0) + + def test_eid_role(self): + context = self.context() + self.assertEquals(rest_publish(context, ':eid:`%s`' % context.eid), + '

#%s

\n' % context.eid) + self.assertEquals(rest_publish(context, ':eid:`%s:some text`' % context.eid), + '

some text

\n') + + def test_bad_rest_no_crash(self): + data = rest_publish(self.context(), ''' +| card | implication | +-------------------------- +| 1-1 | N1 = N2 | +| 1-? | N1 <= N2 | +| 1-+ | N1 >= N2 | +| 1-* | N1>0 => N2>0 | +-------------------------- +| ?-? | N1 # N2 | +| ?-+ | N1 >= N2 | +| ?-* | N1 # N2 | +-------------------------- +| +-+ | N1>0 => N2>0 et | +| | N2>0 => N1>0 | +| +-* | N1>+ => N2>0 | +-------------------------- +| *-* | N1#N2 | +-------------------------- + +''') + +if __name__ == '__main__': + unittest_main() diff -r a721966779be -r cba9f175da2d goa/skel/views.py --- a/goa/skel/views.py Thu May 07 16:33:22 2009 +0200 +++ b/goa/skel/views.py Thu May 07 16:42:34 2009 +0200 @@ -1,10 +1,8 @@ # custom application views - -from mx.DateTime import DateTime +from datetime import date -from cubicweb.web.views import baseviews -from cubicweb.web.views.boxes import BoxTemplate -from cubicweb.web.views.calendar import MONTHNAMES +from cubicweb.utils import last_day +from cubicweb.web.views import baseviews, boxes, calendar from cubicweb.web.htmlwidgets import BoxLink, BoxWidget _ = unicode @@ -12,15 +10,15 @@ class BlogEntryPrimaryView(baseviews.PrimaryView): accepts = ('BlogEntry',) - + def cell_call(self, row, col): entity = self.entity(row, col) self.w(u'

%s

' % entity.dc_title()) entity.view('metadata', w=self.w) self.w(entity.printable_value('text')) - + -class BlogArchiveBox(BoxTemplate): +class BlogArchiveBox(boxes.BoxTemplate): """side box usually displaying some related entities in a primary view""" id = 'blog_archives_box' title = _('blog archives') @@ -37,12 +35,12 @@ blogmonths.append( (year, month) ) box = BoxWidget(_('Blog archives'), id=self.id) for year, month in blogmonths: - firstday = DateTime(year, month, 1) - lastday = DateTime(year, month, firstday.days_in_month) + firstday = date(year, month, 1) + lastday = last_day(firstday) rql = ('Any B WHERE B is BlogEntry, B creation_date >= "%s", B creation_date <= "%s"' % (firstday.strftime('%Y-%m-%d'), lastday.strftime('%Y-%m-%d'))) url = self.build_url(rql=rql) - label = u'%s %s' % (_(MONTHNAMES[month-1]), year) + label = u'%s %s' % (_(calendar.MONTHNAMES[month-1]), year) box.append( BoxLink(url, label) ) box.render(self.w) diff -r a721966779be -r cba9f175da2d i18n/en.po --- a/i18n/en.po Thu May 07 16:33:22 2009 +0200 +++ b/i18n/en.po Thu May 07 16:42:34 2009 +0200 @@ -118,6 +118,10 @@ msgid "%s software version of the database" msgstr "" +#, python-format +msgid "%s_perm" +msgstr "" + msgid "**" msgstr "0..n 0..n" @@ -196,12 +200,6 @@ msgid "Bytes_plural" msgstr "Bytes" -msgid "Card" -msgstr "Card" - -msgid "Card_plural" -msgstr "Cards" - msgid "Date" msgstr "Date" @@ -332,9 +330,6 @@ msgid "New Bookmark" msgstr "New bookmark" -msgid "New Card" -msgstr "New card" - msgid "New CWCache" msgstr "" @@ -471,9 +466,6 @@ msgid "This Bookmark" msgstr "This bookmark" -msgid "This Card" -msgstr "This card" - msgid "This CWCache" msgstr "" @@ -596,11 +588,6 @@ msgstr "" msgid "" -"a card is a textual content used as documentation, reference, procedure " -"reminder" -msgstr "" - -msgid "" "a simple cache entity characterized by a name and a validity date. The " "target application is responsible for updating timestamp when necessary to " "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject." @@ -820,9 +807,6 @@ msgid "add a Bookmark" msgstr "add a bookmark" -msgid "add a Card" -msgstr "add a card" - msgid "add a CWCache" msgstr "" @@ -928,9 +912,6 @@ msgid "am/pm calendar (year)" msgstr "" -msgid "an abstract for this card" -msgstr "" - msgid "an electronic mail address associated to a short alias" msgstr "" @@ -968,6 +949,9 @@ msgid "attribute" msgstr "" +msgid "attributes with modified permissions:" +msgstr "" + msgid "august" msgstr "" @@ -1121,6 +1105,9 @@ msgid "cardinality" msgstr "" +msgid "category" +msgstr "" + #, python-format msgid "changed state of %(etype)s #%(eid)s (%(title)s)" msgstr "" @@ -1131,6 +1118,9 @@ msgid "click on the box to cancel the deletion" msgstr "" +msgid "close all" +msgstr "" + msgid "comment" msgstr "" @@ -1236,12 +1226,6 @@ msgid "constraints applying on this relation" msgstr "" -msgid "content" -msgstr "" - -msgid "content_format" -msgstr "content format" - msgid "contentnavigation" msgstr "contextual components" @@ -1827,7 +1811,7 @@ msgstr "" msgid "hide meta-data" -msgstr "" +msgstr "hide meta entities and relations" msgid "home" msgstr "" @@ -1855,9 +1839,6 @@ msgid "i18n_login_popup" msgstr "login" -msgid "i18n_register_user" -msgstr "register" - msgid "i18nprevnext_next" msgstr "next" @@ -1948,9 +1929,6 @@ msgid "inlined" msgstr "" -msgid "inlined view" -msgstr "" - msgid "internationalizable" msgstr "" @@ -2068,9 +2046,6 @@ msgid "login" msgstr "" -msgid "login or email" -msgstr "" - msgid "login_action" msgstr "log in" @@ -2174,6 +2149,18 @@ msgid "navigation" msgstr "" +msgid "navigation.combobox-limit" +msgstr "\"related\" combo-box" + +msgid "navigation.page-size" +msgstr "number of results" + +msgid "navigation.related-limit" +msgstr "number of entities in the primary view " + +msgid "navigation.short-line-size" +msgstr "short description" + msgid "navtop" msgstr "page top" @@ -2226,6 +2213,9 @@ msgid "object" msgstr "" +msgid "object_plural:" +msgstr "objects:" + msgid "october" msgstr "" @@ -2241,6 +2231,9 @@ msgid "only select queries are authorized" msgstr "" +msgid "open all" +msgstr "" + msgid "order" msgstr "" @@ -2284,6 +2277,12 @@ msgid "permission" msgstr "" +msgid "permissions for entities" +msgstr "" + +msgid "permissions for relations" +msgstr "" + msgid "permissions for this entity" msgstr "" @@ -2296,9 +2295,6 @@ msgid "pkey" msgstr "key" -msgid "planned_delivery" -msgstr "planned delivery" - msgid "please correct errors below" msgstr "" @@ -2354,6 +2350,9 @@ msgid "relation_type_object" msgstr "relation definitions" +msgid "relations" +msgstr "" + msgid "relations deleted" msgstr "" @@ -2363,9 +2362,6 @@ msgid "remove this Bookmark" msgstr "remove this bookmark" -msgid "remove this Card" -msgstr "remove this card" - msgid "remove this CWCache" msgstr "" @@ -2549,7 +2545,7 @@ msgstr "" msgid "show meta-data" -msgstr "" +msgstr "show the complete schema" msgid "site configuration" msgstr "" @@ -2603,6 +2599,9 @@ msgid "subject/object cardinality" msgstr "" +msgid "subject_plural:" +msgstr "subjects:" + msgid "sunday" msgstr "" @@ -2612,9 +2611,6 @@ msgid "symetric" msgstr "" -msgid "synopsis" -msgstr "" - msgid "system entities" msgstr "" @@ -2715,6 +2711,36 @@ msgid "ui" msgstr "" +msgid "ui.date-format" +msgstr "date format" + +msgid "ui.datetime-format" +msgstr "date and time format" + +msgid "ui.default-text-format" +msgstr "text format" + +msgid "ui.encoding" +msgstr "encoding" + +msgid "ui.fckeditor" +msgstr "content editor" + +msgid "ui.float-format" +msgstr "float format" + +msgid "ui.language" +msgstr "language" + +msgid "ui.main-template" +msgstr "main template" + +msgid "ui.site-title" +msgstr "site title" + +msgid "ui.time-format" +msgstr "time format" + msgid "unaccessible" msgstr "" @@ -2730,6 +2756,9 @@ msgid "unknown property key" msgstr "" +msgid "up" +msgstr "" + msgid "upassword" msgstr "password" @@ -2856,9 +2885,6 @@ "which is the preferred form." msgstr "" -msgid "wikiid" -msgstr "wiki identifier" - #, python-format msgid "workflow for %s" msgstr "" @@ -2877,3 +2903,30 @@ msgid "you have been logged out" msgstr "" + +#~ msgid "Card" +#~ msgstr "Card" + +#~ msgid "Card_plural" +#~ msgstr "Cards" + +#~ msgid "New Card" +#~ msgstr "New card" + +#~ msgid "This Card" +#~ msgstr "This card" + +#~ msgid "add a Card" +#~ msgstr "add a card" + +#~ msgid "content_format" +#~ msgstr "content format" + +#~ msgid "planned_delivery" +#~ msgstr "planned delivery" + +#~ msgid "remove this Card" +#~ msgstr "remove this card" + +#~ msgid "wikiid" +#~ msgstr "wiki identifier" diff -r a721966779be -r cba9f175da2d i18n/es.po --- a/i18n/es.po Thu May 07 16:33:22 2009 +0200 +++ b/i18n/es.po Thu May 07 16:42:34 2009 +0200 @@ -5,8 +5,8 @@ msgstr "" "Project-Id-Version: cubicweb 2.46.0\n" "PO-Revision-Date: 2008-11-27 07:59+0100\n" -"Last-Translator: Celso \n" -"Language-Team: fr \n" +"Last-Translator: Celso Flores\n" +"Language-Team: es \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -23,8 +23,8 @@ "url: %(url)s\n" msgstr "" "\n" -"%(user)s a cambiado su estado de <%(previous_state)s> hacia <%(current_state)" -"s> por la entidad\n" +"%(user)s ha cambiado su estado de <%(previous_state)s> hacia <%" +"(current_state)s> por la entidad\n" "'%(title)s'\n" "\n" "%(comment)s\n" @@ -33,7 +33,7 @@ #, python-format msgid " from state %(fromstate)s to state %(tostate)s\n" -msgstr " de el estado %(fromstate)s hacia el estado %(tostate)s\n" +msgstr " del estado %(fromstate)s hacia el estado %(tostate)s\n" #, python-format msgid "%(cstr)s constraint failed for value %(value)r" @@ -121,7 +121,11 @@ #, python-format msgid "%s software version of the database" -msgstr "version sistema de la base para %s" +msgstr "versión sistema de la base para %s" + +#, python-format +msgid "%s_perm" +msgstr "" msgid "**" msgstr "0..n 0..n" @@ -181,10 +185,10 @@ msgstr "Aplicación" msgid "Bookmark" -msgstr "Atajo" +msgstr "Favorito" msgid "Bookmark_plural" -msgstr "Atajos" +msgstr "Favoritos" msgid "Boolean" msgstr "Booleano" @@ -193,19 +197,13 @@ msgstr "Booleanos" msgid "Browse by category" -msgstr "Selecciona por categoría" +msgstr "Busca por categoría" msgid "Bytes" -msgstr "Datos binarios" +msgstr "Bytes" msgid "Bytes_plural" -msgstr "Datos binarios" - -msgid "Card" -msgstr "Ficha" - -msgid "Card_plural" -msgstr "Fichas" +msgstr "Bytes" msgid "Date" msgstr "Fecha" @@ -224,34 +222,34 @@ msgstr "Nivel de debug puesto a %s" msgid "Decimal" -msgstr "Número decimal" +msgstr "Decimal" msgid "Decimal_plural" -msgstr "Números decimales" +msgstr "Decimales" msgid "Do you want to delete the following element(s) ?" msgstr "Desea suprimir el(los) elemento(s) siguiente(s)" msgid "CWCache" -msgstr "Memoria Cache" +msgstr "Cache" msgid "CWCache_plural" -msgstr "Memorias Caches" +msgstr "Caches" msgid "CWConstraint" -msgstr "Condición" +msgstr "Restricción" msgid "CWConstraintType" -msgstr "Tipo de condición" +msgstr "Tipo de Restricción" msgid "CWConstraintType_plural" -msgstr "Tipos de condición" +msgstr "Tipos de Restricción" msgid "CWConstraint_plural" -msgstr "Condiciones" +msgstr "Restricciones" msgid "CWEType" -msgstr "Tipo de entidades" +msgstr "Tipo de entidad" msgid "CWEType_plural" msgstr "Tipos de entidades" @@ -305,10 +303,10 @@ msgstr "Correo Electrónico" msgid "EmailAddress_plural" -msgstr "Direcciónes de Correo Electrónico" +msgstr "Direcciones de Correo Electrónico" msgid "Entities" -msgstr "entidades" +msgstr "Entidades" msgid "Environment" msgstr "Ambiente" @@ -335,22 +333,19 @@ msgstr "Duraciones" msgid "New Bookmark" -msgstr "Nuevo Atajo" - -msgid "New Card" -msgstr "Nueva ficha" +msgstr "Agregar a Favoritos" msgid "New CWCache" -msgstr "Nueva memoria cache" +msgstr "Agregar Cache" msgid "New CWConstraint" -msgstr "Nueva condición" +msgstr "Agregar Restricción" msgid "New CWConstraintType" -msgstr "Nuevo tipo de condición" +msgstr "Agregar tipo de Restricción" msgid "New CWEType" -msgstr "Nuevo tipo de entidad" +msgstr "Agregar tipo de entidad" msgid "New CWAttribute" msgstr "Nueva definición de relación final" @@ -362,31 +357,31 @@ msgstr "Nueva definición de relación final" msgid "New CWPermission" -msgstr "Nueva autorización" +msgstr "Agregar autorización" msgid "New CWProperty" -msgstr "Nueva Propiedad" +msgstr "Agregar Propiedad" msgid "New CWRType" -msgstr "Nuevo tipo de relación" +msgstr "Agregar tipo de relación" msgid "New CWUser" -msgstr "Nuevo usuario" +msgstr "Agregar usuario" msgid "New EmailAddress" -msgstr "Nuevo Email" +msgstr "Agregar Email" msgid "New RQLExpression" -msgstr "Nueva expresión rql" +msgstr "Agregar expresión rql" msgid "New State" -msgstr "Nuevo Estado" +msgstr "Agregar Estado" msgid "New TrInfo" -msgstr "Nueva Información de Transición" +msgstr "Agregar Información de Transición" msgid "New Transition" -msgstr "Nueva transición" +msgstr "Agregar transición" msgid "No query has been executed" msgstr "Ninguna búsqueda ha sido ejecutada" @@ -422,7 +417,7 @@ msgstr "Relaciones" msgid "Request" -msgstr "Búsqueda" +msgstr "Petición" #, python-format msgid "Schema %s" @@ -435,7 +430,7 @@ msgstr "Servidor" msgid "Startup views" -msgstr "Vistas de inicio" +msgstr "Vistas de Inicio" msgid "State" msgstr "Estado" @@ -444,10 +439,10 @@ msgstr "Estados" msgid "String" -msgstr "Cadena de caractéres" +msgstr "Cadena de caracteres" msgid "String_plural" -msgstr "Cadenas de caractéres" +msgstr "Cadenas de caracteres" msgid "Subject: " msgstr "Objeto : " @@ -474,19 +469,16 @@ msgstr "Este %s" msgid "This Bookmark" -msgstr "Este atajo" - -msgid "This Card" -msgstr "Esta Ficha" +msgstr "Este favorito" msgid "This CWCache" -msgstr "Esta Memoria Cache" +msgstr "Este Cache" msgid "This CWConstraint" -msgstr "Esta condición" +msgstr "Esta Restricción" msgid "This CWConstraintType" -msgstr "Este tipo de condición" +msgstr "Este tipo de Restricción" msgid "This CWEType" msgstr "Este tipo de Entidad" @@ -553,10 +545,10 @@ msgstr "Utilizado por :" msgid "What's new?" -msgstr "Ultimas Noticias" +msgstr "Lo último en el sitio" msgid "Workflow history" -msgstr "Registro de cambios de estado" +msgstr "Histórico del Workflow" msgid "You are not connected to an application !" msgstr "Usted no esta conectado a una aplicación" @@ -571,7 +563,7 @@ "box, or edit file content online with the widget below." msgstr "" "Usted puede proponer un nuevo archivo utilizando el botón\n" -"\"buscar\" aqui arriba, o eliminar el archivo ya elegido al\n" +"\"buscar\" aquí arriba, o eliminar el archivo ya elegido al\n" "seleccionar el cuadro \"soltar archivo adjunto\", o editar el contenido\n" "del archivo en línea con el componente inferior." @@ -585,7 +577,7 @@ msgid "You can use any of the following substitutions in your text" msgstr "" -"Puede realizar cualquiera de las siguietes sustituciones en el contenido de " +"Puede realizar cualquiera de las siguientes sustituciones en el contenido de " "su email." msgid "You have no access to this view or it's not applyable to current data" @@ -595,8 +587,8 @@ "You're not authorized to access this page. If you think you should, please " "contact the site administrator." msgstr "" -"Usted no esta autorizado a acceder a esta página. Si Usted cree aue \n" -"es un error, favor de contactar al administrador del sitio." +"Usted no esta autorizado a acceder a esta página. Si Usted cree que \n" +"hay un error, favor de contactar al administrador del sitio." #, python-format msgid "[%s supervision] changes summary" @@ -610,16 +602,9 @@ "be available. This query may use X and U variables that will respectivly " "represents the current entity and the current user" msgstr "" -"una expresión RQL deviendo regresado resultado para que la transición pueda " -"ser realizada. Esta expresión puede utilizar las variables X y U que " -"representan respectivamente la entidad en transición y el usuarioactual. " - -msgid "" -"a card is a textual content used as documentation, reference, procedure " -"reminder" -msgstr "" -"una ficha es un texto utilizado como documentación, referencia, memoria de " -"procedimiento..." +"una expresión RQL que debe haber enviado resultados, para que la transición " +"pueda ser realizada. Esta expresión puede utilizar las variables X y U que " +"representan respectivamente la entidad en transición y el usuario actual. " msgid "" "a simple cache entity characterized by a name and a validity date. The " @@ -627,12 +612,16 @@ "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject." "get_cache() method." msgstr "" +"una entidad cache simple caracterizada por un nombre y una fecha correcta. " +"El sistema objetivo es responsable de actualizar timestamp cuand se " +"necesario para invalidar el cache (usualmente en hooks).Recomendamos revisar " +"el METODO AppRsetObject.get_cache()." msgid "about this site" -msgstr "Sobre este espacio" +msgstr "Sobre este Espacio" msgid "access type" -msgstr "tipo de acceso" +msgstr "Tipo de Acceso" msgid "account state" msgstr "Estado de la Cuenta" @@ -650,55 +639,55 @@ msgstr "" msgid "actions_cancel" -msgstr "Anular la selección" +msgstr "Anular" msgid "actions_cancel_description" msgstr "" msgid "actions_copy" -msgstr "copiar" +msgstr "Copiar" msgid "actions_copy_description" msgstr "" msgid "actions_delete" -msgstr "eliminar" +msgstr "Eliminar" msgid "actions_delete_description" msgstr "" msgid "actions_download_as_owl" -msgstr "" +msgstr "Download como OWL" msgid "actions_download_as_owl_description" msgstr "" msgid "actions_edit" -msgstr "modificar" +msgstr "Modificar" msgid "actions_edit_description" msgstr "" msgid "actions_embed" -msgstr "embarcar" +msgstr "Embarcar" msgid "actions_embed_description" msgstr "" msgid "actions_follow" -msgstr "seguir" +msgstr "Seguir" msgid "actions_follow_description" msgstr "" msgid "actions_logout" -msgstr "desconectarse" +msgstr "Desconectarse" msgid "actions_logout_description" msgstr "" msgid "actions_manage" -msgstr "administración del sitio" +msgstr "Administración del sitio" msgid "actions_manage_description" msgstr "" @@ -710,76 +699,76 @@ msgstr "" msgid "actions_myinfos" -msgstr "información personal" +msgstr "Información personal" msgid "actions_myinfos_description" msgstr "" msgid "actions_myprefs" -msgstr "preferencias del usuario" +msgstr "Preferencias del usuario" msgid "actions_myprefs_description" msgstr "" msgid "actions_prefs" -msgstr "preferencias" +msgstr "Preferencias" msgid "actions_prefs_description" msgstr "" msgid "actions_schema" -msgstr "ver el esquema" +msgstr "Ver el esquema" msgid "actions_schema_description" msgstr "" msgid "actions_select" -msgstr "seleccionar" +msgstr "Seleccionar" msgid "actions_select_description" msgstr "" msgid "actions_sendemail" -msgstr "enviar un email" +msgstr "Enviar un email" msgid "actions_sendemail_description" msgstr "" msgid "actions_siteconfig" -msgstr "configuración del sitio" +msgstr "Configuración del sitio" msgid "actions_siteconfig_description" msgstr "" msgid "actions_view" -msgstr "ver" +msgstr "Ver" msgid "actions_view_description" msgstr "" msgid "actions_workflow" -msgstr "ver el workflow" +msgstr "Ver el workflow" msgid "actions_workflow_description" msgstr "" msgid "activate" -msgstr "activar" +msgstr "Activar" msgid "activated" -msgstr "activado" +msgstr "Activado" msgid "add" -msgstr "agregar" +msgstr "Agregar" msgid "add Bookmark bookmarked_by CWUser object" -msgstr "" +msgstr "Agregar a los favoritos " msgid "add CWEType add_permission RQLExpression subject" -msgstr "Definir una expresión RQL de agregación" +msgstr "Agregar una autorización" msgid "add CWEType delete_permission RQLExpression subject" -msgstr "Definir una expresión RQL de eliminación" +msgstr "Eliminar una autorización" msgid "add CWEType read_permission RQLExpression subject" msgstr "Definir una expresión RQL de lectura" @@ -788,138 +777,135 @@ msgstr "Definir una expresión RQL de actualización" msgid "add CWAttribute constrained_by CWConstraint subject" -msgstr "condición" +msgstr "Restricción" msgid "add CWAttribute relation_type CWRType object" -msgstr "definición de atributo" +msgstr "Definición de atributo" msgid "add CWRelation constrained_by CWConstraint subject" -msgstr "condición" +msgstr "Restricción" msgid "add CWRelation relation_type CWRType object" -msgstr "definición de relación" +msgstr "Definición de relación" msgid "add CWProperty for_user CWUser object" -msgstr "propiedad" +msgstr "Agregar Propiedad" msgid "add CWRType add_permission RQLExpression subject" -msgstr "expresión RQL de agregación" +msgstr "Agregar expresión RQL de agregación" msgid "add CWRType delete_permission RQLExpression subject" -msgstr "expresión RQL de eliminación" +msgstr "Agregar expresión RQL de eliminación" msgid "add CWRType read_permission RQLExpression subject" -msgstr "expresión RQL de lectura" +msgstr "Agregar expresión RQL de lectura" msgid "add CWUser in_group CWGroup object" -msgstr "usuario" +msgstr "Agregar usuario" msgid "add CWUser use_email EmailAddress subject" -msgstr "agregar email" +msgstr "Agregar email" msgid "add State allowed_transition Transition object" -msgstr "agregar un estado en entrada" +msgstr "Agregar un estado en entrada" msgid "add State allowed_transition Transition subject" -msgstr "agregar una transición en salida" +msgstr "Agregar una transición en salida" msgid "add State state_of CWEType object" -msgstr "agregar un estado" +msgstr "Agregar un estado" msgid "add Transition condition RQLExpression subject" -msgstr "agregar una condición" +msgstr "Agregar una Restricción" msgid "add Transition destination_state State object" -msgstr "agregar una transición de entrada" +msgstr "Agregar una transición de entrada" msgid "add Transition destination_state State subject" -msgstr "agregar el estado de salida" +msgstr "Agregar el estado de salida" msgid "add Transition transition_of CWEType object" -msgstr "agregar una transición" +msgstr "Agregar una transición" msgid "add a Bookmark" -msgstr "agregar un atajo" - -msgid "add a Card" -msgstr "agregar una ficha" +msgstr "Agregar un Favorito" msgid "add a CWCache" -msgstr "agregar una memoria cache" +msgstr "Agregar un cache" msgid "add a CWConstraint" -msgstr "agregar una condición" +msgstr "Agregar una Restricción" msgid "add a CWConstraintType" -msgstr "aun tipo de condición" +msgstr "Agregar un tipo de Restricción" msgid "add a CWEType" -msgstr "agregar un tipo de entidad" +msgstr "Agregar un tipo de entidad" msgid "add a CWAttribute" -msgstr "agregar un tipo de relación" +msgstr "Agregar un tipo de relación" msgid "add a CWGroup" -msgstr "agregar un grupo de usuarios" +msgstr "Agregar un grupo de usuarios" msgid "add a CWRelation" -msgstr "agregar una relación" +msgstr "Agregar una relación" msgid "add a CWPermission" -msgstr "agregar una autorización" +msgstr "Agregar una autorización" msgid "add a CWProperty" -msgstr "agregar una propiedad" +msgstr "Agregar una propiedad" msgid "add a CWRType" -msgstr "agregar un tipo de relación" +msgstr "Agregar un tipo de relación" msgid "add a CWUser" -msgstr "agregar un usuario" +msgstr "Agregar un usuario" msgid "add a EmailAddress" -msgstr "agregar un email" +msgstr "Agregar un email" msgid "add a RQLExpression" -msgstr "agregar una expresión rql" +msgstr "Agregar una expresión rql" msgid "add a State" -msgstr "agregar un estado" +msgstr "Agregar un estado" msgid "add a TrInfo" -msgstr "agregar una información de transición" +msgstr "Agregar una información de transición" msgid "add a Transition" -msgstr "agregar una transición" +msgstr "Agregar una transición" msgid "add a new permission" -msgstr "agregar una autorización" +msgstr "Agregar una autorización" msgid "add relation" -msgstr "agregar una relación" +msgstr "Agregar una relación" msgid "add_perm" -msgstr "agregado" +msgstr "Agregado" # subject and object forms for each relation type # (no object form for final relation types) msgid "add_permission" -msgstr "autorización para agregar" +msgstr "Autorización para agregar" msgid "add_permission_object" msgstr "tiene la autorización para agregar" #, python-format msgid "added %(etype)s #%(eid)s (%(title)s)" -msgstr "agregado de la entidad %(etype)s #%(eid)s (%(title)s)" +msgstr "Agregado %(etype)s #%(eid)s (%(title)s)" #, python-format msgid "" "added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%" "(toeid)s" msgstr "" -"agregado de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %" -"(toetype)s #%(toeid)s" +"Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)s #" +"%(toeid)s" msgid "address" msgstr "dirección" @@ -951,14 +937,11 @@ msgid "am/pm calendar (year)" msgstr "calendario am/pm (año)" -msgid "an abstract for this card" -msgstr "un resumen para esta ficha" - msgid "an electronic mail address associated to a short alias" msgstr "una dirección electrónica asociada a este alias" msgid "an error occured" -msgstr "a ocurrido un error" +msgstr "ha ocurrido un error" msgid "an error occured while processing your request" msgstr "un error ocurrió al procesar su demanda" @@ -973,16 +956,16 @@ msgstr "y/o entre los diferentes valores" msgid "anonymous" -msgstr "anónimo" +msgstr "Anónimo" msgid "application entities" -msgstr "entidades de la aplicación" +msgstr "Entidades de la aplicación" msgid "application schema" -msgstr "esquema de la aplicación" +msgstr "Esquema de la aplicación" msgid "april" -msgstr "abril" +msgstr "Abril" #, python-format msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)" @@ -991,117 +974,119 @@ "otra via la relación %(rtype)s" msgid "attribute" -msgstr "atributo" +msgstr "Atributo" + +msgid "attributes with modified permissions:" +msgstr "" msgid "august" -msgstr "agosto" +msgstr "Agosto" msgid "authentication failure" msgstr "Usuario o contraseña incorrecta" msgid "automatic" -msgstr "automático" +msgstr "Automático" msgid "bad value" -msgstr "valor erróneo" +msgstr "Valor erróneo" msgid "base url" -msgstr "url de base" +msgstr "Url de base" msgid "bookmark has been removed" -msgstr "el atajo ha sido eliminado" +msgstr "ha sido eliminado de sus favoritos" msgid "bookmark this page" -msgstr "Agregue un atajo a esta página" +msgstr "Agregar esta página a sus favoritos" msgid "bookmark this search" -msgstr "Guarde esta búsqueda" +msgstr "Guardar esta búsqueda" msgid "bookmarked_by" -msgstr "utilizada por" +msgstr "está en los favoritos de" msgid "bookmarked_by_object" -msgstr "tiene como atajo" +msgstr "selecciona en sus favoritos a" msgid "bookmarks" -msgstr "atajos" +msgstr "Favoritos" msgid "boxes" -msgstr "cajas" +msgstr "Cajas" msgid "boxes_bookmarks_box" -msgstr "caja de atajos" +msgstr "Caja de Favoritos" msgid "boxes_bookmarks_box_description" -msgstr "Caja que contiene los atajos del usuario" +msgstr "Caja que contiene los espacios favoritos del usuario" msgid "boxes_download_box" -msgstr "" +msgstr "Caja de download" msgid "boxes_download_box_description" -msgstr "" +msgstr "Caja que contiene los elementos bajados" msgid "boxes_edit_box" -msgstr "caja de acciones" +msgstr "Caja de acciones" msgid "boxes_edit_box_description" msgstr "" -"caja aue muestra las diferentes acciones posibles sobre los datos presentes" +"Caja que muestra las diferentes acciones posibles sobre los datos presentes" msgid "boxes_filter_box" -msgstr "filtrar" +msgstr "Filtros" msgid "boxes_filter_box_description" -msgstr "" -"caja permitiendo de realizar filtros sobre los resultados de una búsqueda" +msgstr "Caja que permite realizar filtros sobre los resultados de una búsqueda" msgid "boxes_possible_views_box" -msgstr "caja de vistas posibles" +msgstr "Caja de Vistas Posibles" msgid "boxes_possible_views_box_description" -msgstr "caja mostrando las vistas posibles para los datos actuales" +msgstr "Caja mostrando las vistas posibles para los datos actuales" msgid "boxes_rss" msgstr "ícono RSS" msgid "boxes_rss_description" -msgstr "el ícono RSS permite recuperar las vistas RSS de los datos presentes" +msgstr "El ícono RSS permite recuperar las vistas RSS de los datos presentes" msgid "boxes_search_box" -msgstr "caja de búsqueda" +msgstr "Caja de búsqueda" msgid "boxes_search_box_description" -msgstr "caja con un espacio de búsqueda simple" +msgstr "Caja con un espacio de búsqueda simple" msgid "boxes_startup_views_box" -msgstr "caja de las vistas de inicio" +msgstr "Caja Vistas de inicio" msgid "boxes_startup_views_box_description" -msgstr "caja mostrando las vistas de inicio de la aplicación" +msgstr "Caja mostrando las vistas de inicio de la aplicación" msgid "bug report sent" -msgstr "reporte de error enviado" +msgstr "Reporte de error enviado" msgid "button_apply" -msgstr "aplicar" +msgstr "Aplicar" msgid "button_cancel" -msgstr "anular" +msgstr "Cancelar" msgid "button_delete" -msgstr "eliminar" +msgstr "Eliminar" msgid "button_ok" -msgstr "validar" +msgstr "Validar" msgid "button_reset" -msgstr "anular los cambios" +msgstr "Cancelar los cambios" msgid "by" msgstr "por" msgid "by relation" -msgstr "por la relación" +msgstr "por relación" msgid "calendar" msgstr "mostrar un calendario" @@ -1139,10 +1124,10 @@ "cardinalidad %(card)s" msgid "cancel select" -msgstr "anular la selección" +msgstr "Cancelar la selección" msgid "cancel this insert" -msgstr "anular esta inserción" +msgstr "Cancelar esta inserción" msgid "canonical" msgstr "canónico" @@ -1150,93 +1135,99 @@ msgid "cardinality" msgstr "cardinalidad" +msgid "category" +msgstr "" + #, python-format msgid "changed state of %(etype)s #%(eid)s (%(title)s)" -msgstr "cambiar del estado de %(etype)s #%(eid)s (%(title)s)" +msgstr "Cambiar del estado de %(etype)s #%(eid)s (%(title)s)" msgid "changes applied" -msgstr "cambios realizados" +msgstr "Cambios realizados" msgid "click on the box to cancel the deletion" -msgstr "seleccione la zona de edición para anular la eliminación" +msgstr "Seleccione la zona de edición para cancelar la eliminación" + +msgid "close all" +msgstr "" msgid "comment" -msgstr "comentario" +msgstr "Comentario" msgid "comment:" -msgstr "comentario :" +msgstr "Comentario:" msgid "comment_format" -msgstr "formato" +msgstr "Formato" msgid "components" -msgstr "componentes" +msgstr "Componentes" msgid "components_appliname" -msgstr "título de la aplicación" +msgstr "Título de la aplicación" msgid "components_appliname_description" -msgstr "muestra el título de la aplicación en el encabezado de la página" +msgstr "Muestra el título de la aplicación en el encabezado de la página" msgid "components_applmessages" -msgstr "mensajes de la aplicación" +msgstr "Mensajes de la aplicación" msgid "components_applmessages_description" -msgstr "muestra los mensajes de la aplicación" +msgstr "Muestra los mensajes de la aplicación" msgid "components_breadcrumbs" -msgstr "hilo de Ariadna" +msgstr "Ruta de Navegación" msgid "components_breadcrumbs_description" msgstr "" -"muestra un camino que permite identificar el lugar donde se encuentra la " +"Muestra un camino que permite identificar el lugar donde se encuentra la " "página en el sitio" msgid "components_etypenavigation" -msgstr "filtro por tipo" +msgstr "Filtro por tipo" msgid "components_etypenavigation_description" -msgstr "permite filtrar por tipo de entidad los resultados de búsqueda" +msgstr "Permite filtrar por tipo de entidad los resultados de búsqueda" msgid "components_help" -msgstr "botón de ayuda" +msgstr "Botón de ayuda" msgid "components_help_description" -msgstr "el botón de ayuda, en el encabezado de página" +msgstr "El botón de ayuda, en el encabezado de página" msgid "components_loggeduserlink" -msgstr "liga usuario" +msgstr "Liga usuario" msgid "components_loggeduserlink_description" msgstr "" -"muestra un enlace hacia el formulario de conexión para los usuarios " +"Muestra un enlace hacia el formulario de conexión para los usuarios " "anónimos, o una caja que contiene las ligas propias a el usuarioconectado. " msgid "components_logo" -msgstr "logo" +msgstr "Logo" msgid "components_logo_description" -msgstr "el logo de la aplicación, en el encabezado de página" +msgstr "El logo de la aplicación, en el encabezado de página" msgid "components_navigation" -msgstr "navigación por página" +msgstr "Navigación por página" msgid "components_navigation_description" msgstr "" -"componente aue permite distribuir sobre varias páginas las búsquedas que " -"arrojan mayores resultados a un número previamente elegido" +"Componente que permite distribuir sobre varias páginas las búsquedas que " +"arrojan mayores resultados que un número previamente elegido" msgid "components_rqlinput" -msgstr "barra rql" +msgstr "Barra rql" msgid "components_rqlinput_description" -msgstr "la barre de demanda rql, en el encabezado de página" +msgstr "La barra de demanda rql, en el encabezado de página" msgid "components_rss_feed_url" -msgstr "" +msgstr "RSS FEED URL" msgid "components_rss_feed_url_description" -msgstr "" +msgstr "El espacio para administrar RSS" msgid "composite" msgstr "composite" @@ -1245,82 +1236,76 @@ msgstr "condición" msgid "condition:" -msgstr "condición :" +msgstr "condición:" msgid "condition_object" msgstr "condición de" msgid "confirm password" -msgstr "confirmar contraseña" +msgstr "Confirmar contraseña" msgid "constrained_by" -msgstr "condicionado por" +msgstr "Restricción hecha por" msgid "constrained_by_object" -msgstr "condición de" +msgstr "ha restringido" msgid "constraint factory" -msgstr "fabrica de condiciones" +msgstr "FAbrica de restricciones" msgid "constraints" -msgstr "condiciones" +msgstr "Restricciones" msgid "constraints applying on this relation" -msgstr "condiciones que se aplican a esta relación" - -msgid "content" -msgstr "contenido" - -msgid "content_format" -msgstr "formato" +msgstr "Restricciones que se aplican a esta relación" msgid "contentnavigation" -msgstr "composantes contextuales" +msgstr "Componentes contextuales" msgid "contentnavigation_breadcrumbs" -msgstr "hilo de Ariadna" +msgstr "Ruta de Navegación" msgid "contentnavigation_breadcrumbs_description" -msgstr "muestra un camino que permite localizar la página actual en el sitio" +msgstr "Muestra un camino que permite localizar la página actual en el sitio" msgid "contentnavigation_prevnext" msgstr "Elemento anterior / siguiente" msgid "contentnavigation_prevnext_description" msgstr "" -"muestra las ligas que permiten pasar de una entidad a otra en lasentidades " +"Muestra las ligas que permiten pasar de una entidad a otra en lasentidades " "que implementan la interface \"anterior/siguiente\"." msgid "contentnavigation_seealso" -msgstr "vea también" +msgstr "Vea también" msgid "contentnavigation_seealso_description" msgstr "" -"sección aue muestra las entidades ligadas por la relación \"vea también\" , " +"sección que muestra las entidades ligadas por la relación \"vea también\" , " "si la entidad soporta esta relación." msgid "contentnavigation_wfhistory" -msgstr "histórico del workflow." +msgstr "Histórico del workflow." msgid "contentnavigation_wfhistory_description" msgstr "" -"sección que ofrece el reporte histórico del workflow para las entidades que " +"Sección que ofrece el reporte histórico del workflow para las entidades que " "posean un workflow." msgid "context" -msgstr "contexto" +msgstr "Contexto" msgid "context where this box should be displayed" -msgstr "contexto en el cual la caja debe aparecer en el sistema" +msgstr "Contexto en el cual la caja debe aparecer en el sistema" msgid "context where this component should be displayed" -msgstr "contexto en el cual el componente debe aparecer en el sistema" +msgstr "Contexto en el cual el componente debe aparecer en el sistema" msgid "control subject entity's relations order" -msgstr "controla el orden de relaciones de la entidad sujeto" +msgstr "Controla el orden de relaciones de la entidad sujeto" msgid "copy" -msgstr "copiar" +msgstr "Copiar" msgid "copy edition" msgstr "Edición de una copia" @@ -1329,66 +1314,66 @@ "core relation giving to a group the permission to add an entity or relation " "type" msgstr "" -"relación sistema que otorga a un grupo la autorización de agregar unaentidad " -"o una relación" +"Relación sistema que otorga a un grupo la autorización de agregar una " +"entidad o una relación" msgid "" "core relation giving to a group the permission to delete an entity or " "relation type" msgstr "" -"relación sistema que otorga a un grupo la autorización de eliminar una " +"Relación sistema que otorga a un grupo la autorización de eliminar una " "entidad o relación" msgid "" "core relation giving to a group the permission to read an entity or relation " "type" msgstr "" -"relación sistema que otorga a un grupo la autorización de leer una entidad o " +"Relación sistema que otorga a un grupo la autorización de leer una entidad o " "una relación " msgid "core relation giving to a group the permission to update an entity type" msgstr "" -"relación sistema que otorga a un grupo la autorización de actualizar una " +"Relación sistema que otorga a un grupo la autorización de actualizar una " "entidad" msgid "core relation indicating a user's groups" msgstr "" -"relación sistema que indica los grupos a los cuales pertenece un usuario" +"Relación sistema que indica los grupos a los cuales pertenece un usuario" msgid "" "core relation indicating owners of an entity. This relation implicitly put " "the owner into the owners group for the entity" msgstr "" -"relación sistema que indica el(los) propietario(s) de una entidad. Esta " +"Relación sistema que indica el(los) propietario(s) de una entidad. Esta " "relación pone de manera implícita al propietario en el grupo de propietarios " -"por una entidad" +"de una entidad" msgid "core relation indicating the original creator of an entity" -msgstr "relación sistema que indica el creador de una entidad." +msgstr "Relación sistema que indica el creador de una entidad." msgid "core relation indicating the type of an entity" -msgstr "relación sistema que indica el tipo de entidad" +msgstr "Relación sistema que indica el tipo de entidad" msgid "" "core relation indicating the types (including specialized types) of an entity" msgstr "" -"relación sistema indicando los tipos (incluídos los tipos padres) de una " +"Relación sistema indicando los tipos (incluídos los tipos padres) de una " "entidad" msgid "cost" -msgstr "costo" +msgstr "Costo" msgid "could not connect to the SMTP server" -msgstr "imposible de conectarse al servidor SMTP" +msgstr "Imposible de conectarse al servidor SMTP" msgid "create an index for quick search on this attribute" -msgstr "crear un índice para accelerar las búsquedas sobre este atributo" +msgstr "Crear un índice para accelerar las búsquedas sobre este atributo" msgid "create an index page" -msgstr "crear una página de inicio" +msgstr "Crear una página de inicio" msgid "created on" -msgstr "creado el" +msgstr "Creado el" msgid "created_by" msgstr "creado por" @@ -1397,50 +1382,49 @@ msgstr "ha creado" msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)" -msgstr "" +msgstr "Creando Favorito" msgid "creating CWConstraint (CWAttribute %(linkto)s constrained_by CWConstraint)" -msgstr "creación condicionada por el atributo %(linkto)s" +msgstr "Creación condicionada por el atributo %(linkto)s" msgid "creating CWConstraint (CWRelation %(linkto)s constrained_by CWConstraint)" -msgstr "creación condicionada por la relación %(linkto)s" +msgstr "Creación condicionada por la relación %(linkto)s" msgid "creating CWAttribute (CWAttribute relation_type CWRType %(linkto)s)" -msgstr "creación atributo %(linkto)s" +msgstr "Creación del atributo %(linkto)s" msgid "creating CWRelation (CWRelation relation_type CWRType %(linkto)s)" -msgstr "creación relación %(linkto)s" +msgstr "Creación de la relación %(linkto)s" msgid "creating CWProperty (CWProperty for_user CWUser %(linkto)s)" -msgstr "creación de una propiedad por el usuario %(linkto)s" +msgstr "Creación de una propiedad por el usuario %(linkto)s" msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)" -msgstr "creación de un usuario para agregar al grupo %(linkto)s" +msgstr "Creación de un usuario para agregar al grupo %(linkto)s" msgid "creating EmailAddress (CWUser %(linkto)s use_email EmailAddress)" -msgstr "creación de una dirección electrónica para el usuario %(linkto)s" +msgstr "Creación de una dirección electrónica para el usuario %(linkto)s" msgid "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)" msgstr "" -"creación de una expresión RQL para la autorización de agregar %(linkto)s" +"Creación de una expresión RQL para la autorización de agregar %(linkto)s" msgid "" "creating RQLExpression (CWEType %(linkto)s delete_permission RQLExpression)" msgstr "" -"creación de una expresión RQL para la autorización de eliminar %(linkto)s" +"Creación de una expresión RQL para la autorización de eliminar %(linkto)s" msgid "" "creating RQLExpression (CWEType %(linkto)s read_permission RQLExpression)" -msgstr "creación de una expresión RQL para la autorización de leer %(linkto)s" +msgstr "Creación de una expresión RQL para la autorización de leer %(linkto)s" msgid "" "creating RQLExpression (CWEType %(linkto)s update_permission RQLExpression)" -msgstr "" -"creación de una expresión RQL para la autorización de actualizar %(linkto)s" +msgstr "Creación de una expresión RQL para autorizar actualizar %(linkto)s" msgid "creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)" msgstr "" -"creación de una expresión RQL para la autorización de agregar relaciones %" +"Creación de una expresión RQL para la autorización de agregar relaciones %" "(linkto)s" msgid "" @@ -1452,90 +1436,90 @@ msgid "" "creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)" msgstr "" -"creación de una expresión RQL para autorizar la lectura de relaciones %" +"Creación de una expresión RQL para autorizar la lectura de relaciones %" "(linkto)s" msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)" -msgstr "creación de una expresión RQL para la transición %(linkto)s" +msgstr "Creación de una expresión RQL para la transición %(linkto)s" msgid "creating State (State allowed_transition Transition %(linkto)s)" -msgstr "creación de un estado que pueda ir hacia la transición %(linkto)s" +msgstr "Creación de un estado que pueda ir hacia la transición %(linkto)s" msgid "creating State (State state_of CWEType %(linkto)s)" -msgstr "creación de un estado por el tipo %(linkto)s" +msgstr "Creación de un estado por el tipo %(linkto)s" msgid "creating State (Transition %(linkto)s destination_state State)" -msgstr "creación de un estado destinación de la transición %(linkto)s" +msgstr "Creación de un estado destinación de la transición %(linkto)s" msgid "creating Transition (State %(linkto)s allowed_transition Transition)" -msgstr "creación de una transición autorizada desde el estado %(linkto)s" +msgstr "Creación de una transición autorizada desde el estado %(linkto)s" msgid "creating Transition (Transition destination_state State %(linkto)s)" -msgstr "creación de un transición hacia el estado %(linkto)s" +msgstr "Creación de un transición hacia el estado %(linkto)s" msgid "creating Transition (Transition transition_of CWEType %(linkto)s)" -msgstr "creación de una transición para el tipo %(linkto)s" +msgstr "Creación de una transición para el tipo %(linkto)s" msgid "creation" -msgstr "creación" +msgstr "Creación" msgid "creation time of an entity" -msgstr "fecha de creación de una entidad" +msgstr "Fecha de creación de una entidad" msgid "creation_date" msgstr "fecha de creación" msgid "cstrtype" -msgstr "tipo de condición" +msgstr "Tipo de condición" msgid "cstrtype_object" msgstr "utilizado por" msgid "csv entities export" -msgstr "exportar entidades en csv" +msgstr "Exportar entidades en csv" msgid "csv export" -msgstr "exportar CSV" +msgstr "Exportar CSV" #, python-format msgid "currently attached file: %s" msgstr "" msgid "data directory url" -msgstr "url del repertorio de datos" +msgstr "Url del repertorio de datos" msgid "date" -msgstr "fecha" +msgstr "Fecha" msgid "deactivate" -msgstr "desactivar" +msgstr "Desactivar" msgid "deactivated" -msgstr "desactivado" +msgstr "Desactivado" msgid "december" -msgstr "diciembre" +msgstr "Diciembre" msgid "default" -msgstr "valor por default" +msgstr "Valor por defecto" msgid "default text format for rich text fields." -msgstr "formato de texto como opción por defecto para los campos texto" +msgstr "Formato de texto como opción por defecto para los campos texto" msgid "defaultval" -msgstr "valor por defecto" +msgstr "Valor por defecto" msgid "define a CubicWeb user" -msgstr "define un usuario CubicWeb" +msgstr "Define un usuario CubicWeb" msgid "define a CubicWeb users group" -msgstr "define un grupo de usuarios CubicWeb" +msgstr "Define un grupo de usuarios CubicWeb" msgid "" "define a final relation: link a final relation type from a non final entity " "to a final entity type. used to build the application schema" msgstr "" -"define una relación no final: liga un tipo de relación no final desde una " +"Define una relación no final: liga un tipo de relación no final desde una " "entidad hacia un tipo de entidad no final. Utilizada para construir el " "esquema de la aplicación" @@ -1543,76 +1527,76 @@ "define a non final relation: link a non final relation type from a non final " "entity to a non final entity type. used to build the application schema" msgstr "" -"define una relación 'atributo', utilizada para construir el esquema dela " +"Define una relación 'atributo', utilizada para construir el esquema dela " "aplicación" msgid "define a relation type, used to build the application schema" msgstr "" -"define un tipo de relación, utilizada para construir el esquema de la " +"Define un tipo de relación, utilizada para construir el esquema de la " "aplicación" msgid "define a rql expression used to define permissions" msgstr "Expresión RQL utilizada para definir los derechos de acceso" msgid "define a schema constraint" -msgstr "define una condición de esquema" +msgstr "Define una condición de esquema" msgid "define a schema constraint type" -msgstr "define un tipo de condición de esquema" +msgstr "Define un tipo de condición de esquema" msgid "define an entity type, used to build the application schema" msgstr "" -"define un tipo de entidad, utilizada para construir el esquema de la " +"Define un tipo de entidad, utilizada para construir el esquema de la " "aplicación" msgid "" "defines what's the property is applied for. You must select this first to be " "able to set value" msgstr "" -"define a que se aplica la propiedad . Usted debe seleccionar esto antes de " +"Define a que se aplica la propiedad . Usted debe seleccionar esto antes de " "poder fijar un valor" msgid "delete" -msgstr "eliminar" +msgstr "Eliminar" msgid "delete this bookmark" -msgstr "eliminar este atajo" +msgstr "Eliminar este favorito" msgid "delete this permission" -msgstr "eliminar esta autorización" +msgstr "Eliminar esta autorización" msgid "delete this relation" -msgstr "eliminar estar relación" +msgstr "Eliminar esta relación" msgid "delete_perm" -msgstr "eliminar" +msgstr "Eliminar" msgid "delete_permission" -msgstr "autorización de eliminar" +msgstr "Autorización de eliminar" msgid "delete_permission_object" msgstr "posee la autorización de eliminar" #, python-format msgid "deleted %(etype)s #%(eid)s (%(title)s)" -msgstr "eliminación de la entidad %(etype)s #%(eid)s (%(title)s)" +msgstr "Eliminación de la entidad %(etype)s #%(eid)s (%(title)s)" #, python-format msgid "" "deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%" "(toeid)s" msgstr "" -"eliminación de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %" +"Eliminación de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %" "(toetype)s #%(toeid)s" msgid "depends on the constraint type" -msgstr "depende del tipo de condición" +msgstr "Depende del tipo de condición" msgid "description" -msgstr "descripción" +msgstr "Descripción" msgid "description_format" -msgstr "formato" +msgstr "Formato" msgid "destination state for this transition" msgstr "Estado destino para esta transición" @@ -1624,337 +1608,334 @@ msgstr "Estado destino" msgid "destination_state_object" -msgstr "destino de" +msgstr "Destino de" #, python-format msgid "detach attached file %s" -msgstr "" +msgstr "Quitar archivo adjunto %s" msgid "detailed schema view" -msgstr "vista detallada del esquema" +msgstr "Vista detallada del esquema" msgid "display order of the action" -msgstr "orden de aparición de la acción" +msgstr "Orden de aparición de la acción" msgid "display order of the box" -msgstr "orden de aparición de la caja" +msgstr "Orden de aparición de la caja" msgid "display order of the component" -msgstr "orden de aparición del componente" +msgstr "Orden de aparición del componente" msgid "display the action or not" -msgstr "mostrar la acción o no" +msgstr "Mostrar la acción o no" msgid "display the box or not" -msgstr "mostrar la caja o no" +msgstr "Mostrar la caja o no" msgid "display the component or not" -msgstr "mostrar el componente o no" +msgstr "Mostrar el componente o no" msgid "" "distinct label to distinguate between other permission entity of the same " "name" msgstr "" -"etiqueta que permite distinguir esta autorización de otras que posean el " +"Etiqueta que permite distinguir esta autorización de otras que posean el " "mismo nombre" msgid "download" -msgstr "descargar" +msgstr "Descargar" msgid "download icon" msgstr "ícono de descarga" msgid "download schema as owl" -msgstr "" +msgstr "Descargar esquema en OWL" msgid "edit bookmarks" -msgstr "editar los atajos" +msgstr "Editar favoritos" msgid "edit the index page" -msgstr "editar la página de inicio" +msgstr "Modificar la página de inicio" msgid "editable-table" -msgstr "tabla modificable" +msgstr "Tabla modificable" msgid "edition" -msgstr "edición" +msgstr "Edición" msgid "eid" msgstr "eid" msgid "element copied" -msgstr "elemeto copiado" +msgstr "Elemento copiado" msgid "element created" -msgstr "elemento creado" +msgstr "Elemento creado" msgid "element edited" -msgstr "elemento editado" +msgstr "Elemento editado" msgid "email address to use for notification" -msgstr "dirección electrónica a utilizarse para notificar" +msgstr "Dirección electrónica a utilizarse para notificar" msgid "emails successfully sent" -msgstr "mensajes enviados con éxito" +msgstr "Mensajes enviados con éxito" msgid "embed" -msgstr "incrustrado" +msgstr "Incrustrado" msgid "embedding this url is forbidden" -msgstr "la inclusión de este url esta prohibida" +msgstr "La inclusión de este url esta prohibida" msgid "entities deleted" -msgstr "entidades eliminadas" +msgstr "Entidades eliminadas" msgid "entity deleted" -msgstr "entidad eliminada" +msgstr "Entidad eliminada" msgid "entity type" -msgstr "tipo de entidad" +msgstr "Tipo de entidad" msgid "" "entity type that may be used to construct some advanced security " "configuration" msgstr "" -"tipo de entidqd utilizada para definir una configuración de seguridad " +"Tipo de entidad utilizada para definir una configuración de seguridad " "avanzada" msgid "entity types which may use this state" -msgstr "tipo de entidades que pueden utilizar este estado" +msgstr "Tipo de entidades que pueden utilizar este estado" msgid "entity types which may use this transition" -msgstr "entidades que pueden utilizar esta transición" +msgstr "Entidades que pueden utilizar esta transición" msgid "error while embedding page" -msgstr "error durante la inclusión de la página" +msgstr "Error durante la inclusión de la página" #, python-format msgid "error while handling __method: %s" -msgstr "error ocurrido durante el tratamiento del formulario (%s)" +msgstr "Error ocurrido durante el tratamiento del formulario (%s)" msgid "error while publishing ReST text" msgstr "" -"se ha producido un error durante la interpretación del texto en formatoReST" +"Se ha producido un error durante la interpretación del texto en formatoReST" #, python-format msgid "error while querying source %s, some data may be missing" msgstr "" -"un error ha ocurrido al interrogar %s, es posible que los \n" +"Un error ha ocurrido al interrogar %s, es posible que los \n" "datos visibles se encuentren incompletos" msgid "eta_date" msgstr "fecha de fin" msgid "expected:" -msgstr "previsto :" +msgstr "Previsto :" msgid "expression" -msgstr "expresión" +msgstr "Expresión" msgid "exprtype" -msgstr "tipo de la expresión" +msgstr "Tipo de la expresión" msgid "external page" -msgstr "página externa" +msgstr "Página externa" msgid "facetbox" -msgstr "caja de facetas" +msgstr "Caja de facetas" msgid "facets_created_by-facet" msgstr "faceta \"creada por\"" msgid "facets_created_by-facet_description" -msgstr "" +msgstr "faceta creado por" msgid "facets_etype-facet" msgstr "faceta \"es de tipo\"" msgid "facets_etype-facet_description" -msgstr "" +msgstr "faceta es de tipo" msgid "facets_has_text-facet" msgstr "faceta \"contiene el texto\"" msgid "facets_has_text-facet_description" -msgstr "" +msgstr "faceta contiene el texto" msgid "facets_in_group-facet" msgstr "faceta \"forma parte del grupo\"" msgid "facets_in_group-facet_description" -msgstr "" +msgstr "faceta en grupo" msgid "facets_in_state-facet" msgstr "faceta \"en el estado\"" msgid "facets_in_state-facet_description" -msgstr "" +msgstr "faceta en el estado" msgid "february" -msgstr "febrero" +msgstr "Febrero" msgid "file tree view" -msgstr "" +msgstr "File Vista Arborescencia" msgid "final" -msgstr "final" +msgstr "Final" msgid "firstname" -msgstr "nombre" +msgstr "Nombre" msgid "foaf" -msgstr "" +msgstr "Amigo de un Amigo, FOAF" msgid "follow" -msgstr "seguir la liga" +msgstr "Seguir la liga" msgid "for_user" -msgstr "para el usuario" +msgstr "Para el usuario" msgid "for_user_object" -msgstr "utiliza las propiedades" +msgstr "Utiliza las propiedades" msgid "friday" -msgstr "viernes" +msgstr "Viernes" msgid "from" -msgstr "de" +msgstr "De" msgid "from_entity" -msgstr "de la entidad" +msgstr "De la entidad" msgid "from_entity_object" -msgstr "relación sujeto" +msgstr "Relación sujeto" msgid "from_state" -msgstr "de el estado" +msgstr "De el estado" msgid "from_state_object" -msgstr "transiciones desde este estado" +msgstr "Transiciones desde este estado" msgid "full text or RQL query" -msgstr "texto de búsqueda o demanda RQL" +msgstr "Texto de búsqueda o demanda RQL" msgid "fulltext_container" -msgstr "contenedor de texto indexado" +msgstr "Contenedor de texto indexado" msgid "fulltextindexed" -msgstr "indexación de texto" +msgstr "Indexación de texto" msgid "generic plot" -msgstr "trazado de curbas estándares" +msgstr "Trazado de curbas estándares" msgid "go back to the index page" -msgstr "regresar a la página de inicio" +msgstr "Regresar a la página de inicio" msgid "granted to groups" -msgstr "otorgado a los grupos" +msgstr "Otorgado a los grupos" msgid "graphical representation of the application'schema" -msgstr "representación gráfica del esquema de la aplicación" +msgstr "Representación gráfica del esquema de la aplicación" #, python-format msgid "graphical schema for %s" -msgstr "gráfica del esquema por %s" +msgstr "Gráfica del esquema por %s" #, python-format msgid "graphical workflow for %s" -msgstr "gráfica del workflow por %s" +msgstr "Gráfica del workflow por %s" msgid "group in which a user should be to be allowed to pass this transition" -msgstr "grupo en el cual el usuario debe estar para poder pasar la transición" +msgstr "Grupo en el cual el usuario debe estar para poder pasar la transición" msgid "groups" -msgstr "grupos" +msgstr "Grupos" msgid "groups allowed to add entities/relations of this type" -msgstr "grupos autorizados a agregar entidades/relaciones de este tipo" +msgstr "Grupos autorizados a agregar entidades/relaciones de este tipo" msgid "groups allowed to delete entities/relations of this type" -msgstr "grupos autorizados a eliminar entidades/relaciones de este tipo" +msgstr "Grupos autorizados a eliminar entidades/relaciones de este tipo" msgid "groups allowed to read entities/relations of this type" -msgstr "grupos autorizados a leer entidades/relaciones de este tipo" +msgstr "Grupos autorizados a leer entidades/relaciones de este tipo" msgid "groups allowed to update entities of this type" -msgstr "grupos autorizados a actualizar entidades de este tipo" +msgstr "Grupos autorizados a actualizar entidades de este tipo" msgid "groups grant permissions to the user" -msgstr "los grupos otorgan las autorizaciones al usuario" +msgstr "Los grupos otorgan las autorizaciones al usuario" msgid "groups to which the permission is granted" -msgstr "grupos quienes tienen otorgada esta autorización" +msgstr "Grupos quienes tienen otorgada esta autorización" msgid "groups:" -msgstr "grupos :" +msgstr "Grupos :" msgid "guests" -msgstr "invitados" +msgstr "Invitados" msgid "hCalendar" msgstr "hCalendar" msgid "has_text" -msgstr "contiene el texto" +msgstr "Contiene el texto" msgid "help" -msgstr "ayuda" +msgstr "Ayuda" msgid "hide filter form" -msgstr "esconder el filtro" +msgstr "Esconder el filtro" msgid "hide meta-data" -msgstr "esconder los meta-datos" +msgstr "Esconder los meta-datos" msgid "home" -msgstr "inicio" +msgstr "Inicio" msgid "" "how to format date and time in the ui (\"man strftime\" for format " "description)" msgstr "" -"como formatear la fecha en la interface (\"man strftime\" por la descripción " +"Como formatear la fecha en la interface (\"man strftime\" por la descripción " "del formato)" msgid "how to format date in the ui (\"man strftime\" for format description)" msgstr "" -"como formatear la fecha en la interface (\"man strftime\" por la descripción " +"Como formatear la fecha en la interface (\"man strftime\" por la descripción " "del formato)" msgid "how to format float numbers in the ui" -msgstr "como formatear los números flotantes en la interface" +msgstr "Como formatear los números flotantes en la interface" msgid "how to format time in the ui (\"man strftime\" for format description)" msgstr "" -"como formatear la hora en la interface (\"man strftime\" por la descripción " +"Como formatear la hora en la interface (\"man strftime\" por la descripción " "del formato)" msgid "html class of the component" -msgstr "clase HTML de este componente" +msgstr "Clase HTML de este componente" msgid "htmlclass" -msgstr "clase html" +msgstr "Clase html" msgid "i18n_login_popup" -msgstr "identificarse" - -msgid "i18n_register_user" -msgstr "registrarse" +msgstr "Identificarse" msgid "i18nprevnext_next" -msgstr "siguiente" +msgstr "Siguiente" msgid "i18nprevnext_previous" -msgstr "anterior" +msgstr "Anterior" msgid "i18nprevnext_up" -msgstr "padre" +msgstr "Padre" msgid "iCalendar" msgstr "iCalendar" msgid "id of main template used to render pages" -msgstr "id del template principal" +msgstr "ID del template principal" msgid "identical_to" msgstr "idéntico a" @@ -1969,23 +1950,23 @@ "if full text content of subject/object entity should be added to other side " "entity (the container)." msgstr "" -"si el texto indexado de la entidad sujeto/objeto debe ser agregado a la " +"Si el texto indexado de la entidad sujeto/objeto debe ser agregado a la " "entidad a el otro extremo de la relación (el contenedor)." msgid "image" -msgstr "imagen" +msgstr "Imagen" msgid "in memory entity schema" -msgstr "esquema de la entidad en memoria" +msgstr "Esquema de la entidad en memoria" msgid "in memory relation schema" -msgstr "esquema de la relación en memoria" +msgstr "Esquema de la relación en memoria" msgid "in_group" -msgstr "en el grupo" +msgstr "En el grupo" msgid "in_group_object" -msgstr "miembros" +msgstr "Miembros" msgid "in_state" msgstr "estado" @@ -1994,67 +1975,64 @@ msgstr "estado de" msgid "incontext" -msgstr "en el contexto" +msgstr "En el contexto" #, python-format msgid "incorrect value (%(value)s) for type \"%(type)s\"" msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\"" msgid "index" -msgstr "índice" +msgstr "Indice" msgid "index this attribute's value in the plain text index" -msgstr "indexar el valor de este atributo en el índice de texto simple" +msgstr "Indexar el valor de este atributo en el índice de texto simple" msgid "indexed" -msgstr "indexado" +msgstr "Indexado" msgid "indicate the current state of an entity" -msgstr "indica el estado actual de una entidad" +msgstr "Indica el estado actual de una entidad" msgid "" "indicate which state should be used by default when an entity using states " "is created" msgstr "" -"indica cual estado deberá ser utilizado por defecto al crear una entidad" +"Indica cual estado deberá ser utilizado por defecto al crear una entidad" #, python-format msgid "initial estimation %s" -msgstr "estimación inicial %s" +msgstr "Estimación inicial %s" msgid "initial state for entities of this type" -msgstr "estado inicial para las entidades de este tipo" +msgstr "Estado inicial para las entidades de este tipo" msgid "initial_state" msgstr "estado inicial" msgid "initial_state_object" -msgstr "estado inicial de" +msgstr "es el estado inicial de" msgid "inlined" -msgstr "puesto en línea" - -msgid "inlined view" -msgstr "vista incluída (en línea)" +msgstr "Puesto en línea" msgid "internationalizable" -msgstr "internacionalizable" +msgstr "Internacionalizable" #, python-format msgid "invalid action %r" -msgstr "acción %r invalida" +msgstr "Acción %r invalida" msgid "invalid date" -msgstr "esta fecha no es válida" +msgstr "Esta fecha no es válida" msgid "is" -msgstr "de tipo" +msgstr "es" msgid "is it an application entity type or not ?" -msgstr "es una entidad aplicativa o no ?" +msgstr "Es un Tipo de entidad en la aplicación o no ?" msgid "is it an application relation type or not ?" -msgstr "es una relación aplicativa o no ?" +msgstr "Es una relación aplicativa o no ?" msgid "" "is the subject/object entity of the relation composed of the other ? This " @@ -2064,53 +2042,53 @@ "ser así, el destruir el composite destruirá de igual manera sus componentes " msgid "is this attribute's value translatable" -msgstr "es el valor de este atributo traducible ?" +msgstr "Es el valor de este atributo traducible ?" msgid "is this relation equivalent in both direction ?" -msgstr "es esta relación equivalente en los ambos sentidos ?" +msgstr "Es esta relación equivalente en los ambos sentidos ?" msgid "" "is this relation physically inlined? you should know what you're doing if " "you are changing this!" msgstr "" -"es esta relación puesta en línea en la base de datos ? Usted debe saber lo " +"Es esta relación puesta en línea en la base de datos ? Usted debe saber lo " "que hace si cambia esto !" msgid "is_instance_of" msgstr "es una instancia de" msgid "is_instance_of_object" -msgstr "tipo de" +msgstr "tiene como instancias" msgid "is_object" msgstr "tiene por instancia" msgid "january" -msgstr "enero" +msgstr "Enero" msgid "july" -msgstr "julio" +msgstr "Julio" msgid "june" -msgstr "junio" +msgstr "Junio" msgid "label" -msgstr "etiqueta" +msgstr "Etiqueta" msgid "language of the user interface" -msgstr "idioma para la interface del usuario" +msgstr "Idioma para la interface del usuario" msgid "last connection date" -msgstr "última fecha de conexión" +msgstr "Ultima fecha de conexión" msgid "last_login_time" -msgstr "última fecha de conexión" +msgstr "Ultima fecha de conexión" msgid "latest modification time of an entity" -msgstr "fecha de la última modificación de una entidad " +msgstr "Fecha de la última modificación de una entidad " msgid "latest update on" -msgstr "última actualización" +msgstr "actualizado el" msgid "left" msgstr "izquierda" @@ -2119,7 +2097,7 @@ "link a property to the user which want this property customization. Unless " "you're a site manager, this relation will be handled automatically." msgstr "" -"liga una propiedad a el usuario que desea esta personalización. A menos que " +"Liga una propiedad a el usuario que desea esta personalización. Salvo que " "usted sea un administrador del sistema, esta relación es gestionada " "automáticamente." @@ -2130,214 +2108,233 @@ msgstr "liga una definición de relación a su tipo de relación" msgid "link a relation definition to its subject entity type" -msgstr "lie une dÈfinition de relation ‡ son type d'entitÈ sujet" +msgstr "liga una definición de relación a su tipo de entidad" msgid "link a state to one or more entity type" -msgstr "lier un Ètat ‡ une ou plusieurs entitÈs" +msgstr "liga un estado a una o mas entidades" msgid "link a transition information to its object" -msgstr "liÈ une enregistrement de transition vers l'objet associÈ" +msgstr "liga una transcion de informacion a los objetos asociados" msgid "link a transition to one or more entity type" -msgstr "lie une transition ‡ un ou plusieurs types d'entitÈs" +msgstr "liga una transición a una o mas tipos de entidad" msgid "" "link a transition to one or more rql expression allowing to go through this " "transition" msgstr "" +"liga una transición a una o mas expresiones RQL permitiendo que funcione" msgid "link to each item in" -msgstr "lier vers chaque ÈlÈment dans" +msgstr "ligar hacia cada elemento en" msgid "list" -msgstr "liste" +msgstr "Lista" msgid "loading" -msgstr "" +msgstr "Cargando" msgid "log in" -msgstr "s'identifier" +msgstr "Identificarse" msgid "login" -msgstr "identifiant" +msgstr "Clave de acesso" msgid "login_action" -msgstr "identifiez vous" +msgstr "Ingresa tus datos" msgid "logout" -msgstr "se dÈconnecter" +msgstr "Desconectarse" #, python-format msgid "loop in %(rel)s relation (%(eid)s)" -msgstr "boucle dÈtectÈe en parcourant la relation %(rel)s de l'entitÈ #%(eid)s" +msgstr "loop detectado en %(rel)s de la entidad #%(eid)s" msgid "main informations" -msgstr "Informations gÈnÈrales" +msgstr "Informaciones Generales" msgid "mainvars" -msgstr "variables principales" +msgstr "Principales variables" msgid "manage" -msgstr "gestion du site" +msgstr "Administracion del Sitio" msgid "manage bookmarks" -msgstr "gÈrer les signets" +msgstr "Administra tus favoritos" msgid "manage permissions" -msgstr "" +msgstr "Administración de Autorizaciones" msgid "manage security" -msgstr "gestion de la sÈcuritÈ" +msgstr "Administración de la Seguridad" msgid "managers" -msgstr "administrateurs" +msgstr "editores" msgid "march" -msgstr "mars" +msgstr "Marzo" msgid "maximum number of characters in short description" -msgstr "nombre maximum de caractËres dans les descriptions courtes" +msgstr "Numero maximo de caracteres en las descripciones cortas" msgid "maximum number of entities to display in related combo box" -msgstr "nombre maximum d'entitÈs ‡ afficher dans les listes dÈroulantes" +msgstr "Numero maximo de entidades a mostrar en las listas dinamicas" msgid "maximum number of objects displayed by page of results" -msgstr "nombre maximum d'entitÈs affichÈes par pages" +msgstr "Numero maximo de objetos mostrados por pagina de resultados" msgid "maximum number of related entities to display in the primary view" -msgstr "nombre maximum d'entitÈs liÈes ‡ afficher dans la vue primaire" +msgstr "Numero maximo de entidades ligadas a mostrar en la vista primaria" msgid "may" -msgstr "mai" +msgstr "Mayo" msgid "meta" -msgstr "mÈta" +msgstr "Meta" msgid "milestone" -msgstr "jalon" +msgstr "Milestone" #, python-format msgid "missing parameters for entity %s" -msgstr "paramËtres manquants pour l'entitÈ %s" +msgstr "Parametros faltantes a la entidad %s" msgid "modification_date" -msgstr "date de modification" +msgstr "Fecha de modificacion" msgid "modify" -msgstr "modifier" +msgstr "Modificar" msgid "monday" -msgstr "lundi" +msgstr "Lundi" msgid "more actions" -msgstr "plus d'actions" +msgstr "mas acciones" msgid "multiple edit" -msgstr "Èdition multiple" +msgstr "Edicion multiple" msgid "my custom search" -msgstr "ma recherche personnalisÈe" +msgstr "Mi busqueda personalizada" msgid "name" -msgstr "nom" +msgstr "Nombre" msgid "name of the cache" -msgstr "nom du cache applicatif" +msgstr "Nombre del Cache" msgid "" "name of the main variables which should be used in the selection if " "necessary (comma separated)" msgstr "" -"nom des variables principaes qui devrait Ítre utilisÈes dans la sÈlection si " -"nÈcessaire (les sÈparer par des virgules)" +"Nombre de las variables principales que deberian se utilizadas en la " +"selecciónde ser necesario (separarlas con comas)" msgid "name or identifier of the permission" -msgstr "nom (identifiant) de la permission" +msgstr "Nombre o indentificador de la autorización" msgid "navbottom" -msgstr "bas de page" +msgstr "Pie de pagina" msgid "navcontentbottom" -msgstr "bas de page du contenu principal" +msgstr "Pie de pagina del contenido principal" msgid "navcontenttop" -msgstr "haut de page" +msgstr "Encabezado" msgid "navigation" -msgstr "navigation" +msgstr "Navegación" + +msgid "navigation.combobox-limit" +msgstr "" + +msgid "navigation.page-size" +msgstr "" + +msgid "navigation.related-limit" +msgstr "" + +msgid "navigation.short-line-size" +msgstr "" msgid "navtop" -msgstr "haut de page du contenu principal" +msgstr "Encabezado del contenido principal" msgid "new" -msgstr "nouveau" +msgstr "Nuevo" msgid "next_results" -msgstr "rÈsultats suivants" +msgstr "Siguientes resultados" msgid "no" -msgstr "non" +msgstr "no" msgid "no associated epermissions" -msgstr "aucune permission spÈcifique n'est dÈfinie" +msgstr "permisos no asociados" msgid "no possible transition" -msgstr "aucune transition possible" +msgstr "transición no posible" msgid "no related project" -msgstr "pas de projet rattachÈ" +msgstr "no hay proyecto relacionado" msgid "no selected entities" -msgstr "pas d'entitÈ sÈlectionnÈe" +msgstr "no hay entidades seleccionadas" #, python-format msgid "no such entity type %s" -msgstr "le type d'entitÈ '%s' n'existe pas" +msgstr "el tipo de entidad '%s' no existe" msgid "no version information" -msgstr "pas d'information de version" +msgstr "no información de version" msgid "not authorized" -msgstr "non autorisÈ" +msgstr "no autorizado" msgid "not selected" -msgstr "" +msgstr "no seleccionado" msgid "not specified" -msgstr "non spÈcifiÈ" +msgstr "no especificado" msgid "not the initial state for this entity" -msgstr "n'est pas l'Ètat initial pour cette entitÈ" +msgstr "no el estado inicial para esta entidad" msgid "nothing to edit" -msgstr "rien ‡ Èditer" +msgstr "nada que editar" msgid "november" -msgstr "novembre" +msgstr "noviembre" msgid "object" -msgstr "objet" +msgstr "objeto" + +msgid "object_plural:" +msgstr "" msgid "october" -msgstr "octobre" +msgstr "octubre" msgid "one month" -msgstr "un mois" +msgstr "un mes" msgid "one week" -msgstr "une semaine" +msgstr "una semana" msgid "oneline" -msgstr "une ligne" +msgstr "una linea" msgid "only select queries are authorized" -msgstr "seules les requÍtes de sÈlections sont autorisÈes" +msgstr "solo estan permitidas consultas de lectura" + +msgid "open all" +msgstr "" msgid "order" -msgstr "ordre" +msgstr "orden" msgid "ordernum" -msgstr "ordre" +msgstr "orden" msgid "owl" msgstr "" @@ -2346,178 +2343,179 @@ msgstr "" msgid "owned_by" -msgstr "appartient ‡" +msgstr "pertenece a" msgid "owned_by_object" -msgstr "possËde" +msgstr "pertenece al objeto" msgid "owners" -msgstr "propriÈtaires" +msgstr "proprietarios" msgid "ownership" -msgstr "propriÈtÈ" +msgstr "pertenencia" msgid "ownerships have been changed" -msgstr "les droits de propriÈtÈ ont ÈtÈ modifiÈs" +msgstr "la pertenencia ha sido modificada" msgid "pageid-not-found" -msgstr "" -"des donnÈes nÈcessaires semblent expirÈes, veuillez recharger la page et " -"recommencer." +msgstr "pagina no encontrada." msgid "password" -msgstr "mot de passe" +msgstr "Clave de acceso" msgid "password and confirmation don't match" -msgstr "le mot de passe et la confirmation sont diffÈrents" +msgstr "La clave de acceso y la confirmación no concuerdan" msgid "path" -msgstr "chemin" +msgstr "Ruta" msgid "permission" -msgstr "permission" +msgstr "Permiso" + +msgid "permissions for entities" +msgstr "" + +msgid "permissions for relations" +msgstr "" msgid "permissions for this entity" -msgstr "permissions pour cette entitÈ" +msgstr "Permisos para esta entidad" msgid "personnal informations" -msgstr "informations personnelles" +msgstr "Información personal" msgid "pick existing bookmarks" -msgstr "rÈcupÈrer des signets existants" +msgstr "Seleccione los favoritos existentes" msgid "pkey" -msgstr "clÈ" - -msgid "planned_delivery" msgstr "" msgid "please correct errors below" -msgstr "veuillez corriger les erreurs ci-dessous" +msgstr "Favor de corregir errores" msgid "please correct the following errors:" -msgstr "veuillez corriger les erreurs suivantes :" +msgstr "Favor de corregir los siguientes errores :" msgid "possible views" -msgstr "vues possibles" +msgstr "Vistas posibles" msgid "preferences" -msgstr "prÈfÈrences" +msgstr "Preferencias" msgid "previous_results" -msgstr "rÈsultats prÈcÈdents" +msgstr "Resultados anteriores" msgid "primary" -msgstr "primaire" +msgstr "Primaria" msgid "primary_email" -msgstr "adresse email principale" +msgstr "Dirección de email principal" msgid "primary_email_object" -msgstr "adresse email principale (object)" +msgstr "Dirección de email principal (objeto)" msgid "progress" -msgstr "avancement" +msgstr "Avance" msgid "progress bar" -msgstr "barre d'avancement" +msgstr "Barra de progreso de avance" msgid "project" -msgstr "projet" +msgstr "Proyecto" msgid "read" -msgstr "lecture" +msgstr "Lectura" msgid "read_perm" -msgstr "lecture" +msgstr "Lectura" msgid "read_permission" -msgstr "permission de lire" +msgstr "Permiso de lectura" msgid "read_permission_object" -msgstr "a la permission de lire" +msgstr "Objeto_permiso_lectura" #, python-format msgid "relation %(relname)s of %(ent)s" -msgstr "relation %(relname)s de %(ent)s" +msgstr "relación %(relname)s de %(ent)s" msgid "relation_type" -msgstr "type de relation" +msgstr "tipo de relación" msgid "relation_type_object" -msgstr "dÈfinition" +msgstr "Definición" + +msgid "relations" +msgstr "" msgid "relations deleted" -msgstr "relations supprimÈes" +msgstr "Relaciones eliminadas" msgid "relative url of the bookmarked page" -msgstr "url relative de la page" +msgstr "Url relativa de la pagina" msgid "remove this Bookmark" -msgstr "supprimer ce signet" - -msgid "remove this Card" -msgstr "supprimer cette fiche" +msgstr "Eliminar este Favorito" msgid "remove this CWCache" -msgstr "supprimer ce cache applicatif" +msgstr "Eliminar esta cache de aplicación" msgid "remove this CWConstraint" -msgstr "supprimer cette contrainte" +msgstr "Eliminar esta restricción" msgid "remove this CWConstraintType" -msgstr "supprimer ce type de contrainte" +msgstr "Eliminar este tipo de restricción" msgid "remove this CWEType" -msgstr "supprimer ce type d'entitÈ" +msgstr "Eliminar este tipo de entidad" msgid "remove this CWAttribute" -msgstr "supprimer cet attribut" +msgstr "Eliminar este atributo" msgid "remove this CWGroup" -msgstr "supprimer ce groupe" +msgstr "Eliminar este grupo" msgid "remove this CWRelation" -msgstr "supprimer cette relation" +msgstr "Eliminar esta relación" msgid "remove this CWPermission" -msgstr "supprimer cette permission" +msgstr "Eliminar este permiso" msgid "remove this CWProperty" -msgstr "supprimer cette propriÈtÈ" +msgstr "Eliminar esta propiedad" msgid "remove this CWRType" -msgstr "supprimer cette dÈfinition de relation" +msgstr "Eliminar esta definición de relación" msgid "remove this CWUser" -msgstr "supprimer cet utilisateur" +msgstr "Eliminar este usuario" msgid "remove this EmailAddress" -msgstr "supprimer cette adresse email" +msgstr "Eliminar este correo electronico" msgid "remove this RQLExpression" -msgstr "supprimer cette expression rql" +msgstr "Eliminar esta expresión RQL" msgid "remove this State" -msgstr "supprimer cet Ètat" +msgstr "Eliminar este estado" msgid "remove this TrInfo" -msgstr "retirer cette information de transition" +msgstr "Eliminar información de esta transición" msgid "remove this Transition" -msgstr "supprimer cette transition" +msgstr "Eliminar esta transición" msgid "require_group" -msgstr "nÈcessite le groupe" +msgstr "Requiere_grupo" msgid "require_group_object" -msgstr "‡ les droits" +msgstr "Objeto_grupo_requerido" msgid "required attribute" -msgstr "attribut requis" +msgstr "Atributo requerido" msgid "required field" -msgstr "champ requis" +msgstr "Campo requerido" msgid "" "restriction part of a rql query. For entity rql expression, X and U are " @@ -2525,460 +2523,487 @@ "relation rql expression, S, O and U are predefined respectivly to the " "current relation'subject, object and to the request user. " msgstr "" -"partie restriction de la requÍte rql. Pour une expression s'appliquant ‡ une " -"entitÈ, X et U sont respectivement prÈfÈfinis ‡ l'entitÈ et ‡ l'utilisateur " -"courant. Pour une expression s'appliquant ‡ une relation, S, O et U sont " -"respectivement prÈfÈfinis au sujet/objet de la relation et ‡ l'utilisateur " -"courant." +"restriction part of a rql query. For entity rql expression, X and U are " +"predefined respectivly to the current object and to the request user. For " +"relation rql expression, S, O and U are predefined respectivly to the " +"current relation'subject, object and to the request user. " msgid "revert changes" -msgstr "annuler les changements" +msgstr "Revertir cambios" msgid "right" -msgstr "droite" +msgstr "Derecha" msgid "rql expression allowing to add entities/relations of this type" -msgstr "" -"expression RQL donnant le droit d'ajouter des entitÈs/relations de ce type" +msgstr "expresion RQL permitiendo agregar entidades/relaciones de este tipo" msgid "rql expression allowing to delete entities/relations of this type" -msgstr "" -"expression RQL donnant le droit de supprimer des entitÈs/relations de ce type" +msgstr "expresion RQL permitiendo eliminar entidades/relaciones de este tipo" msgid "rql expression allowing to read entities/relations of this type" -msgstr "" -"expression RQL donnant le droit de lire des entitÈs/relations de ce type" +msgstr "expresion RQL permitiendo leer entidades/relaciones de este tipo" msgid "rql expression allowing to update entities of this type" -msgstr "" -"expression RQL donnant le droit de modifier des entitÈs/relations de ce type" +msgstr "expresion RQL permitiendo actualizar entidades de este tipo" msgid "rql expressions" -msgstr "conditions rql" +msgstr "expresiones rql" msgid "rss" msgstr "RSS" msgid "sample format" -msgstr "exemple" +msgstr "ejemplo" msgid "saturday" -msgstr "samedi" +msgstr "sabado" msgid "schema entities" -msgstr "entitÈs dÈfinissant le schÈma" +msgstr "entidades del esquema" msgid "schema's permissions definitions" -msgstr "permissions dÈfinies dans le schÈma" +msgstr "definiciones de permisos del esquema" msgid "search" -msgstr "rechercher" +msgstr "buscar" msgid "search for association" -msgstr "rechercher pour associer" +msgstr "buscar por asociación" msgid "searching for" -msgstr "Recherche de" +msgstr "buscando " msgid "secondary" -msgstr "secondaire" +msgstr "secundario" msgid "security" -msgstr "sÈcuritÈ" +msgstr "seguridad" msgid "see them all" -msgstr "les voir toutes" +msgstr "Ver todos" msgid "select" -msgstr "sÈlectionner" +msgstr "Seleccionar" msgid "select a" -msgstr "sÈlectionner un" +msgstr "seleccione un" msgid "select a relation" -msgstr "sÈlectionner une relation" +msgstr "seleccione una relación" msgid "select this entity" -msgstr "sÈlectionner cette entitÈ" +msgstr "seleccionar esta entidad" msgid "selected" -msgstr "" +msgstr "seleccionado" msgid "semantic description of this attribute" -msgstr "description sÈmantique de cet attribut" +msgstr "descripción semantica de este atributo" msgid "semantic description of this entity type" -msgstr "description sÈmantique de ce type d'entitÈ" +msgstr "descripción semantica de este tipo de entidad" msgid "semantic description of this relation" -msgstr "description sÈmantique de cette relation" +msgstr "descripción semantica de esta relación" msgid "semantic description of this relation type" -msgstr "description sÈmantique de ce type de relation" +msgstr "descripción semantica de este tipo de relación" msgid "semantic description of this state" -msgstr "description sÈmantique de cet Ètat" +msgstr "descripción semantica de este estado" msgid "semantic description of this transition" -msgstr "description sÈmantique de cette transition" +msgstr "descripcion semantica de esta transición" msgid "send email" -msgstr "envoyer un courriel" +msgstr "enviar email" msgid "september" -msgstr "septembre" +msgstr "septiembre" msgid "server debug information" -msgstr "informations de dÈboguage serveur" +msgstr "server debug information" msgid "server information" -msgstr "informations serveur" +msgstr "server information" msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " "fckeditor." msgstr "" -"indique si les champs HTML doivent Ítre Èditer avec fckeditor (un\n" -"Èditer HTML WYSIWYG). Il est Ègalement conseill'de choisir text/html\n" -"comme format de texte par dÈfaut pour pouvoir utiliser fckeditor." +"indique si los campos deberan ser editados usando fckeditor (un\n" +"editor HTML WYSIWYG). Debera tambien elegir text/html\n" +"como formato de texto por default para poder utilizar fckeditor." #, python-format msgid "show %s results" -msgstr "montrer %s rÈsultats" +msgstr "mostrar %s resultados" msgid "show advanced fields" -msgstr "montrer les champs avancÈs" +msgstr "mostrar campos avanzados" msgid "show filter form" msgstr "afficher le filtre" msgid "show meta-data" -msgstr "afficher les mÈta-donnÈes" +msgstr "mostrar meta-data" msgid "site configuration" -msgstr "configuration du site" +msgstr "configuracion del sitio" msgid "site documentation" -msgstr "documentation du site" +msgstr "documentacion del sitio" msgid "site schema" -msgstr "schÈma du site" +msgstr "esquema del sitio" msgid "site title" -msgstr "titre du site" +msgstr "titulo del sitio" msgid "site-wide property can't be set for user" -msgstr "une propriÈtÈ spÈcifique au site ne peut Ítre propre ‡ un utilisateur" +msgstr "" +"una propiedad especifica para el sitio no puede establecerse para el usuario" msgid "sorry, the server is unable to handle this query" -msgstr "dÈsolÈ, le serveur ne peut traiter cette requÍte" +msgstr "lo sentimos, el servidor no puede manejar esta consulta" msgid "specializes" -msgstr "dÈrive de" +msgstr "derivado de" msgid "specializes_object" -msgstr "parent de" +msgstr "objeto_derivado" msgid "startup views" -msgstr "vues de dÈpart" +msgstr "vistas de inicio" msgid "state" -msgstr "Ètat" +msgstr "estado" msgid "state_of" -msgstr "Ètat de" +msgstr "estado_de" msgid "state_of_object" -msgstr "a pour Ètat" +msgstr "objeto_estado_de" msgid "status change" -msgstr "changer l'Ètat" +msgstr "cambio de estatus" msgid "status changed" -msgstr "changement d'Ètat" +msgstr "estatus cambiado" #, python-format msgid "status will change from %(st1)s to %(st2)s" -msgstr "l'entitÈ passera de l'Ètat %(st1)s ‡ l'Ètat %(st2)s" +msgstr "estatus cambiara de %(st1)s a %(st2)s" msgid "subject" -msgstr "sujet" +msgstr "sujeto" msgid "subject/object cardinality" -msgstr "cardinalitÈ sujet/objet" +msgstr "cardinalidad sujeto/objeto" + +msgid "subject_plural:" +msgstr "" msgid "sunday" -msgstr "dimanche" +msgstr "domingo" msgid "surname" -msgstr "nom" +msgstr "apellido" msgid "symetric" -msgstr "symÈtrique" - -msgid "synopsis" -msgstr "synopsis" +msgstr "simetrico" msgid "system entities" -msgstr "entitÈs systËmes" +msgstr "entidades de sistema" msgid "table" -msgstr "table" +msgstr "tabla" msgid "tablefilter" -msgstr "filtre de tableau" +msgstr "filtro de tabla" msgid "task progression" -msgstr "avancement de la t‚che" +msgstr "progreso de la tarea" msgid "text" msgstr "text" msgid "text/cubicweb-page-template" -msgstr "contenu dynamique" +msgstr "text/cubicweb-page-template" msgid "text/html" msgstr "html" msgid "text/plain" -msgstr "texte pur" +msgstr "text/plain" msgid "text/rest" -msgstr "ReST" +msgstr "text/rest" msgid "the prefered email" -msgstr "l'adresse Èlectronique principale" +msgstr "dirección principal de email" #, python-format msgid "the value \"%s\" is already used, use another one" -msgstr "la valeur \"%s\" est dÈj‡ utilisÈe, veuillez utiliser une autre valeur" +msgstr "el valor \"%s\" ya esta en uso, favor de utilizar otro" msgid "this action is not reversible!" -msgstr "" -"Attention ! Cette opÈration va dÈtruire les donnÈes de faÁon irrÈversible." +msgstr "esta acción es irreversible!." msgid "this entity is currently owned by" -msgstr "cette entitÈ appartient ‡" +msgstr "esta entidad es propiedad de" msgid "this resource does not exist" -msgstr "cette ressource est introuvable" +msgstr "este recurso no existe" msgid "thursday" -msgstr "jeudi" +msgstr "jueves" msgid "timestamp" -msgstr "date" +msgstr "fecha" msgid "timestamp of the latest source synchronization." -msgstr "date de la derniËre synchronisation avec la source." +msgstr "fecha de la ultima sincronización de la fuente." msgid "timetable" -msgstr "emploi du temps" +msgstr "tabla de tiempos" msgid "title" -msgstr "titre" +msgstr "titulo" msgid "to" -msgstr "‡" +msgstr "a" msgid "to associate with" -msgstr "pour associer ‡" +msgstr "a asociar con" msgid "to_entity" -msgstr "vers l'entitÈ" +msgstr "hacia entidad" msgid "to_entity_object" -msgstr "relation objet" +msgstr "hacia entidad objeto" msgid "to_state" -msgstr "vers l'Ètat" +msgstr "hacia el estado" msgid "to_state_object" -msgstr "transitions vers cette Ètat" +msgstr "hacia objeto estado" msgid "todo_by" -msgstr "‡ faire par" +msgstr "a hacer por" msgid "transition is not allowed" -msgstr "transition non permise" +msgstr "transition no permitida" msgid "transition_of" -msgstr "transition de" +msgstr "transicion de" msgid "transition_of_object" -msgstr "a pour transition" +msgstr "objeto de transición" msgid "tree view" msgstr "" msgid "tuesday" -msgstr "mardi" +msgstr "martes" msgid "type" msgstr "type" msgid "ui" -msgstr "propriÈtÈs gÈnÈriques de l'interface" +msgstr "interfaz de usuario" + +msgid "ui.date-format" +msgstr "" + +msgid "ui.datetime-format" +msgstr "" + +msgid "ui.default-text-format" +msgstr "" + +msgid "ui.encoding" +msgstr "" + +msgid "ui.fckeditor" +msgstr "" + +msgid "ui.float-format" +msgstr "" + +msgid "ui.language" +msgstr "" + +msgid "ui.main-template" +msgstr "" + +msgid "ui.site-title" +msgstr "" + +msgid "ui.time-format" +msgstr "" msgid "unaccessible" -msgstr "inaccessible" +msgstr "inaccesible" msgid "unauthorized value" -msgstr "valeur non autorisÈe" +msgstr "valor no permitido" msgid "unique identifier used to connect to the application" -msgstr "identifiant unique utilisÈ pour se connecter ‡ l'application" +msgstr "identificador unico utilizado para conectar a la aplicación" msgid "unknown external entity" -msgstr "entitÈ (externe) introuvable" +msgstr "entidad externa desconocida" msgid "unknown property key" -msgstr "clÈ de propriÈtÈ inconnue" +msgstr "propiedad desconocida" + +msgid "up" +msgstr "" msgid "upassword" -msgstr "mot de passe" +msgstr "clave de acceso" msgid "update" -msgstr "modification" +msgstr "modificación" msgid "update_perm" -msgstr "modification" +msgstr "modificación" msgid "update_permission" -msgstr "permission de modification" +msgstr "Permiso de modificación" msgid "update_permission_object" -msgstr "‡ la permission de modifier" +msgstr "objeto de autorización de modificaciones" #, python-format msgid "updated %(etype)s #%(eid)s (%(title)s)" -msgstr "modification de l'entitÈ %(etype)s #%(eid)s (%(title)s)" +msgstr "actualización de la entidad %(etype)s #%(eid)s (%(title)s)" msgid "use template languages" -msgstr "utiliser les langages de template" +msgstr "utilizar plantillas de lenguaje" msgid "" "use to define a transition from one or multiple states to a destination " "states in workflow's definitions." msgstr "" -"utiliser dans une dÈfinition de processus pour ajouter une transition depuis " -"un ou plusieurs Ètats vers un Ètat de destination." +"utilizado para definir una transición desde uno o multiples estados hacia " +"uno o varios estados destino en las definiciones del workflow" msgid "use_email" -msgstr "adresse Èlectronique" +msgstr "correo electrónico" msgid "use_email_object" -msgstr "adresse utilisÈe par" +msgstr "objeto email utilizado" msgid "use_template_format" -msgstr "utilisation du format 'cubicweb template'" +msgstr "utilización del formato 'cubicweb template'" msgid "" "used for cubicweb configuration. Once a property has been created you can't " "change the key." msgstr "" -"utilisÈ pour la configuration de l'application. Une fois qu'une propriÈtÈ a " -"ÈtÈ crÈÈe, vous ne pouvez plus changez la clÈ associÈe" +"utilizado para la configuración de cubicweb. Una vez que la propiedad ha " +"sido creada no puede cambiar la llave" msgid "" "used to associate simple states to an entity type and/or to define workflows" -msgstr "associe les Ètats ‡ un type d'entitÈ pour dÈfinir un workflow" +msgstr "" +"utilizado para asociar estados simples a un tipo de entidad y/o para definir " +"workflows" msgid "used to grant a permission to a group" -msgstr "utiliser pour donner une permission ‡ un groupe" +msgstr "utilizado para otorgar permisos a un grupo" #, python-format msgid "" "user %s has made the following change(s):\n" "\n" msgstr "" -"l'utilisateur %s a effectuÈ le(s) changement(s) suivant(s):\n" +"el usuario %s ha efectuado los siguentes cambios:\n" "\n" msgid "" "user for which this property is applying. If this relation is not set, the " "property is considered as a global property" msgstr "" -"utilisateur a qui s'applique cette propriÈtÈ. Si cette relation n'est pas " -"spÈcifiÈe la propriÈtÈ est considÈrÈe comme globale." +"usuario para el cual aplica esta propiedad. Si no se establece esta " +"relación, la propiedad es considerada como una propiedad global." msgid "user interface encoding" -msgstr "encodage utilisÈ dans l'interface utilisateur" +msgstr "codificación de la interfaz de usuario" msgid "user preferences" -msgstr "prÈfÈrences utilisateur" +msgstr "preferencias del usuario" msgid "users" -msgstr "utilisateurs" +msgstr "usuarios" msgid "users using this bookmark" -msgstr "utilisateurs utilisant ce signet" +msgstr "usuarios en este favorito" msgid "validate modifications on selected items" -msgstr "valider les modifications apportÈes aux ÈlÈments sÈlectionnÈs" +msgstr "valida modificaciones sobre elementos seleccionados" msgid "validating..." -msgstr "chargement en cours ..." +msgstr "validando ..." msgid "value" -msgstr "valeur" +msgstr "valor" msgid "value associated to this key is not editable manually" -msgstr "la valeur associÈe ‡ cette clÈ n'est pas Èditable manuellement" +msgstr "el valor asociado a este elemento no es editable manualmente" msgid "vcard" msgstr "vcard" msgid "view" -msgstr "voir" +msgstr "ver" msgid "view all" -msgstr "voir tous" +msgstr "ver todos" msgid "view detail for this entity" -msgstr "voir les dÈtails de cette entitÈ" +msgstr "ver detalle de esta entidad" msgid "view workflow" -msgstr "voir les Ètats possibles" +msgstr "ver workflow" msgid "views" -msgstr "vues" +msgstr "vistas" msgid "visible" msgstr "visible" msgid "wednesday" -msgstr "mercredi" +msgstr "miercoles" msgid "week" msgstr "sem." #, python-format msgid "welcome %s !" -msgstr "bienvenue %s !" +msgstr "bienvenido %s !" msgid "wf_info_for" -msgstr "historique de" +msgstr "historial de" msgid "wf_info_for_object" -msgstr "historique des transitions" +msgstr "historial de transiciones" msgid "" "when multiple addresses are equivalent (such as python-projects@logilab.org " "and python-projects@lists.logilab.org), set this to true on one of them " "which is the preferred form." msgstr "" -"quand plusieurs adresses sont Èquivalentes (comme python-projects@logilab." -"org et python-projects@lists.logilab.org), mettez cette propriÈtÈ ‡ vrai sur " -"l'une d'entre-elle qui sera la forme canonique" - -msgid "wikiid" -msgstr "identifiant wiki" +"cuando multiples direcciones de correo son equivalentes (como python-" +"projects@logilab.org y python-projects@lists.logilab.org), establecer esto " +"como verdadero en una de ellas es la forma preferida " #, python-format msgid "workflow for %s" -msgstr "workflow pour %s" +msgstr "workflow para %s" msgid "xbel" msgstr "xbel" @@ -2990,10 +3015,10 @@ msgstr "" msgid "yes" -msgstr "oui" +msgstr "si" msgid "you have been logged out" -msgstr "vous avez ÈtÈ dÈconnectÈ" +msgstr "ha terminado la sesion" #~ msgid "%s constraint failed" #~ msgstr "La contrainte %s n'est pas satisfaite" @@ -3004,12 +3029,37 @@ #~ msgid "%s, or without time: %s" #~ msgstr "%s, ou bien sans prÈciser d'heure: %s" +#~ msgid "Card" +#~ msgstr "Ficha" + +#~ msgid "Card_plural" +#~ msgstr "Fichas" + #~ msgid "Loading" #~ msgstr "chargement" +#~ msgid "New Card" +#~ msgstr "Agregar Ficha" + #~ msgid "Problem occured while setting new value" #~ msgstr "Un problËme est survenu lors de la mise ‡ jour" +#~ msgid "This Card" +#~ msgstr "Esta Ficha" + +#~ msgid "" +#~ "a card is a textual content used as documentation, reference, procedure " +#~ "reminder" +#~ msgstr "" +#~ "una ficha es un texto utilizado como documentación, referencia, memoria " +#~ "de algún procedimiento..." + +#~ msgid "add a Card" +#~ msgstr "Agregar una ficha" + +#~ msgid "an abstract for this card" +#~ msgstr "un resumen para esta ficha" + #~ msgid "and" #~ msgstr "et" @@ -3019,6 +3069,12 @@ #~ msgid "cancel edition" #~ msgstr "annuler l'Èdition" +#~ msgid "content" +#~ msgstr "Contenido" + +#~ msgid "content_format" +#~ msgstr "Formato" + #~ msgid "" #~ "default language (look at the i18n directory of the application to see " #~ "available languages)" @@ -3044,6 +3100,9 @@ #~ msgid "incorrect value for type \"%s\"" #~ msgstr "valeur incorrecte pour le type \"%s\"" +#~ msgid "inlined view" +#~ msgstr "Vista incluída (en línea)" + #~ msgid "linked" #~ msgstr "liÈ" @@ -3058,11 +3117,23 @@ #~ msgid "owned by" #~ msgstr "appartient ‡" +#~ msgid "planned_delivery" +#~ msgstr "entrega planeada" + +#~ msgid "remove this Card" +#~ msgstr "Eliminar esta Ficha" + #~ msgid "see also" #~ msgstr "voir aussi" #~ msgid "status will change from %s to %s" #~ msgstr "l'Ètat va passer de %s ‡ %s" +#~ msgid "synopsis" +#~ msgstr "sinopsis" + +#~ msgid "wikiid" +#~ msgstr "identificador wiki" + #~ msgid "workflow history" #~ msgstr "historique du workflow" diff -r a721966779be -r cba9f175da2d i18n/fr.po --- a/i18n/fr.po Thu May 07 16:33:22 2009 +0200 +++ b/i18n/fr.po Thu May 07 16:42:34 2009 +0200 @@ -123,6 +123,10 @@ msgid "%s software version of the database" msgstr "version logicielle de la base pour %s" +#, python-format +msgid "%s_perm" +msgstr "" + msgid "**" msgstr "0..n 0..n" @@ -201,12 +205,6 @@ msgid "Bytes_plural" msgstr "Données binaires" -msgid "Card" -msgstr "Fiche" - -msgid "Card_plural" -msgstr "Fiches" - msgid "Date" msgstr "Date" @@ -337,9 +335,6 @@ msgid "New Bookmark" msgstr "Nouveau signet" -msgid "New Card" -msgstr "Nouvelle fiche" - msgid "New CWCache" msgstr "Nouveau cache applicatif" @@ -476,9 +471,6 @@ msgid "This Bookmark" msgstr "Ce signet" -msgid "This Card" -msgstr "Cette fiche" - msgid "This CWCache" msgstr "Ce cache applicatif" @@ -617,13 +609,6 @@ "transition et l'utilisateur courant." msgid "" -"a card is a textual content used as documentation, reference, procedure " -"reminder" -msgstr "" -"une fiche est un texte utilisé comme documentation, référence, rappel de " -"procédure..." - -msgid "" "a simple cache entity characterized by a name and a validity date. The " "target application is responsible for updating timestamp when necessary to " "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject." @@ -843,9 +828,6 @@ msgid "add a Bookmark" msgstr "ajouter un signet" -msgid "add a Card" -msgstr "ajouter une fiche" - msgid "add a CWCache" msgstr "ajouter un cache applicatif" @@ -953,9 +935,6 @@ msgid "am/pm calendar (year)" msgstr "calendrier am/pm (année)" -msgid "an abstract for this card" -msgstr "un résumé pour cette fiche" - msgid "an electronic mail address associated to a short alias" msgstr "une addresse électronique associée à un alias" @@ -995,6 +974,9 @@ msgid "attribute" msgstr "attribut" +msgid "attributes with modified permissions:" +msgstr "attributs ayant des permissions modifiées :" + msgid "august" msgstr "août" @@ -1151,6 +1133,9 @@ msgid "cardinality" msgstr "cardinalité" +msgid "category" +msgstr "categorie" + #, python-format msgid "changed state of %(etype)s #%(eid)s (%(title)s)" msgstr "changement de l'état de %(etype)s #%(eid)s (%(title)s)" @@ -1161,6 +1146,9 @@ msgid "click on the box to cancel the deletion" msgstr "cliquer dans la zone d'édition pour annuler la suppression" +msgid "close all" +msgstr "tout fermer" + msgid "comment" msgstr "commentaire" @@ -1269,12 +1257,6 @@ msgid "constraints applying on this relation" msgstr "contraintes s'appliquant à cette relation" -msgid "content" -msgstr "contenu" - -msgid "content_format" -msgstr "format" - msgid "contentnavigation" msgstr "composants contextuels" @@ -1904,7 +1886,7 @@ msgstr "cacher le filtre" msgid "hide meta-data" -msgstr "cacher les méta-données" +msgstr "cacher les entités et relations \"méta\"" msgid "home" msgstr "maison" @@ -1938,9 +1920,6 @@ msgid "i18n_login_popup" msgstr "s'authentifier" -msgid "i18n_register_user" -msgstr "s'enregister" - msgid "i18nprevnext_next" msgstr "suivant" @@ -2034,9 +2013,6 @@ msgid "inlined" msgstr "mise en ligne" -msgid "inlined view" -msgstr "vue embarquée (en ligne)" - msgid "internationalizable" msgstr "internationalisable" @@ -2162,9 +2138,6 @@ msgid "login" msgstr "identifiant" -msgid "login or email" -msgstr "identifiant ou email" - msgid "login_action" msgstr "identifiez vous" @@ -2270,6 +2243,18 @@ msgid "navigation" msgstr "navigation" +msgid "navigation.combobox-limit" +msgstr "nombre d'entités dans les listes déroulantes" + +msgid "navigation.page-size" +msgstr "nombre de résultats" + +msgid "navigation.related-limit" +msgstr "nombre d'entités dans la vue primaire" + +msgid "navigation.short-line-size" +msgstr "description courtes" + msgid "navtop" msgstr "haut de page du contenu principal" @@ -2322,6 +2307,9 @@ msgid "object" msgstr "objet" +msgid "object_plural:" +msgstr "objets :" + msgid "october" msgstr "octobre" @@ -2337,6 +2325,9 @@ msgid "only select queries are authorized" msgstr "seules les requêtes de sélections sont autorisées" +msgid "open all" +msgstr "tout ouvrir" + msgid "order" msgstr "ordre" @@ -2381,6 +2372,12 @@ msgid "permission" msgstr "permission" +msgid "permissions for entities" +msgstr "permissions pour les entités" + +msgid "permissions for relations" +msgstr "permissions pour les relations" + msgid "permissions for this entity" msgstr "permissions pour cette entité" @@ -2393,9 +2390,6 @@ msgid "pkey" msgstr "clé" -msgid "planned_delivery" -msgstr "livraison prévue" - msgid "please correct errors below" msgstr "veuillez corriger les erreurs ci-dessous" @@ -2451,6 +2445,9 @@ msgid "relation_type_object" msgstr "définition" +msgid "relations" +msgstr "" + msgid "relations deleted" msgstr "relations supprimées" @@ -2460,9 +2457,6 @@ msgid "remove this Bookmark" msgstr "supprimer ce signet" -msgid "remove this Card" -msgstr "supprimer cette fiche" - msgid "remove this CWCache" msgstr "supprimer ce cache applicatif" @@ -2658,7 +2652,7 @@ msgstr "afficher le filtre" msgid "show meta-data" -msgstr "afficher les méta-données" +msgstr "afficher le schéma complet" msgid "site configuration" msgstr "configuration du site" @@ -2712,6 +2706,9 @@ msgid "subject/object cardinality" msgstr "cardinalité sujet/objet" +msgid "subject_plural:" +msgstr "sujets :" + msgid "sunday" msgstr "dimanche" @@ -2721,9 +2718,6 @@ msgid "symetric" msgstr "symétrique" -msgid "synopsis" -msgstr "synopsis" - msgid "system entities" msgstr "entités systèmes" @@ -2825,6 +2819,36 @@ msgid "ui" msgstr "propriétés génériques de l'interface" +msgid "ui.date-format" +msgstr "format de date" + +msgid "ui.datetime-format" +msgstr "format de date et de l'heure" + +msgid "ui.default-text-format" +msgstr "format de texte" + +msgid "ui.encoding" +msgstr "encodage" + +msgid "ui.fckeditor" +msgstr "éditeur du contenu" + +msgid "ui.float-format" +msgstr "format des flottants" + +msgid "ui.language" +msgstr "langue" + +msgid "ui.main-template" +msgstr "gabarit principal" + +msgid "ui.site-title" +msgstr "titre du site" + +msgid "ui.time-format" +msgstr "format de l'heure" + msgid "unaccessible" msgstr "inaccessible" @@ -2840,6 +2864,9 @@ msgid "unknown property key" msgstr "clé de propriété inconnue" +msgid "up" +msgstr "" + msgid "upassword" msgstr "mot de passe" @@ -2977,9 +3004,6 @@ "org et python-projects@lists.logilab.org), mettez cette propriété à vrai sur " "l'une d'entre-elle qui sera la forme canonique" -msgid "wikiid" -msgstr "identifiant wiki" - #, python-format msgid "workflow for %s" msgstr "workflow pour %s" @@ -3008,12 +3032,40 @@ #~ msgid "%s, or without time: %s" #~ msgstr "%s, ou bien sans préciser d'heure: %s" +#~ msgid "Card" +#~ msgstr "Fiche" + +#~ msgid "Card_plural" +#~ msgstr "Fiches" + #~ msgid "Loading" #~ msgstr "chargement" +#~ msgid "New Card" +#~ msgstr "Nouvelle fiche" + #~ msgid "Problem occured while setting new value" #~ msgstr "Un problème est survenu lors de la mise à jour" +#~ msgid "This Card" +#~ msgstr "Cette fiche" + +#~ msgid "" +#~ "a card is a textual content used as documentation, reference, procedure " +#~ "reminder" +#~ msgstr "" +#~ "une fiche est un texte utilisé comme documentation, référence, rappel de " +#~ "procédure..." + +#~ msgid "actions_addpermission" +#~ msgstr "ajouter une permission" + +#~ msgid "add a Card" +#~ msgstr "ajouter une fiche" + +#~ msgid "an abstract for this card" +#~ msgstr "un résumé pour cette fiche" + #~ msgid "and" #~ msgstr "et" @@ -3023,6 +3075,12 @@ #~ msgid "cancel edition" #~ msgstr "annuler l'édition" +#~ msgid "content" +#~ msgstr "contenu" + +#~ msgid "content_format" +#~ msgstr "format" + #~ msgid "" #~ "default language (look at the i18n directory of the application to see " #~ "available languages)" @@ -3048,6 +3106,9 @@ #~ msgid "incorrect value for type \"%s\"" #~ msgstr "valeur incorrecte pour le type \"%s\"" +#~ msgid "inlined view" +#~ msgstr "vue embarquée (en ligne)" + #~ msgid "linked" #~ msgstr "lié" @@ -3062,11 +3123,23 @@ #~ msgid "owned by" #~ msgstr "appartient à" +#~ msgid "planned_delivery" +#~ msgstr "livraison prévue" + +#~ msgid "remove this Card" +#~ msgstr "supprimer cette fiche" + #~ msgid "see also" #~ msgstr "voir aussi" #~ msgid "status will change from %s to %s" #~ msgstr "l'état va passer de %s à %s" +#~ msgid "synopsis" +#~ msgstr "synopsis" + +#~ msgid "wikiid" +#~ msgstr "identifiant wiki" + #~ msgid "workflow history" #~ msgstr "historique du workflow" diff -r a721966779be -r cba9f175da2d interfaces.py --- a/interfaces.py Thu May 07 16:33:22 2009 +0200 +++ b/interfaces.py Thu May 07 16:42:34 2009 +0200 @@ -11,7 +11,7 @@ class IEmailable(Interface): """interface for emailable entities""" - + def get_email(self): """return email address""" @@ -28,7 +28,7 @@ def as_email_context(self): """returns the dictionary as used by the sendmail controller to build email bodies. - + NOTE: the dictionary keys should match the list returned by the `allowed_massmail_keys` method. """ @@ -45,12 +45,12 @@ """change the entity's state according to a state defined in given parameters """ - + def can_pass_transition(self, trname): """return true if the current user can pass the transition with the given name """ - + def latest_trinfo(self): """return the latest transition information for this entity """ @@ -73,7 +73,7 @@ @property def todo(self): """what remains to be done""" - + def progress_info(self): """returns a dictionary describing progress/estimated cost of the version. @@ -93,19 +93,19 @@ def progress(self): """returns the % progress of the task item""" - - + + class IMileStone(IProgress): """represents an ITask's item""" - + parent_type = None # specify main task's type - + def get_main_task(self): """returns the main ITask entity""" def initial_prevision_date(self): """returns the initial expected end of the milestone""" - + def eta_date(self): """returns expected date of completion based on what remains to be done @@ -128,7 +128,7 @@ def __iter__(self): """iterates over the item's children""" - + def is_leaf(self): """returns true if this node as no child""" @@ -146,7 +146,7 @@ """interface for entities which can be linked to a previous and/or next entity """ - + def next_entity(self): """return the 'next' entity""" def previous_entity(self): @@ -155,10 +155,10 @@ class IBreadCrumbs(Interface): """interface for entities which can be "located" on some path""" - + def breadcrumbs(self, view, recurs=False): """return a list containing some: - + * tuple (url, label) * entity * simple label string @@ -173,7 +173,7 @@ class IDownloadable(Interface): """interface for downloadable entities""" - + def download_url(self): # XXX not really part of this interface """return an url to download entity's content""" def download_content_type(self): @@ -188,31 +188,31 @@ class IEmbedable(Interface): """interface for embedable entities""" - + def embeded_url(self): """embed action interface""" - + class ICalendarable(Interface): """interface for items that do have a begin date 'start' and an end date 'stop' - """ - + """ + class ICalendarViews(Interface): """calendar views interface""" def matching_dates(self, begin, end): """ :param begin: day considered as begin of the range (`DateTime`) :param end: day considered as end of the range (`DateTime`) - + :return: a list of dates (`DateTime`) in the range [`begin`, `end`] on which this entity apply """ - + class ITimetableViews(Interface): """timetable views interface""" def timetable_date(self): """XXX explain - + :return: date (`DateTime`) """ @@ -231,17 +231,18 @@ """returns the icon that should be used as the marker (returns None for default) """ - + class IFeed(Interface): """interface for entities with rss flux""" - + def rss_feed_url(self): """return an url which layout sub-entities item """ + class ISiocItem(Interface): """interface for entities (which are item in sioc specification) with sioc views""" - + def isioc_content(self): """return content entity""" @@ -252,11 +253,11 @@ """return container type (post, BlogPost, MailMessage)""" def isioc_replies(self): - """return replies items""" + """return replies items""" def isioc_topics(self): """return topics items""" - + class ISiocContainer(Interface): """interface for entities (which are container in sioc specification) with sioc views""" @@ -267,5 +268,5 @@ def isioc_items(self): """return contained items""" - - + + diff -r a721966779be -r cba9f175da2d misc/migration/3.2.0_Any.py --- a/misc/migration/3.2.0_Any.py Thu May 07 16:33:22 2009 +0200 +++ b/misc/migration/3.2.0_Any.py Thu May 07 16:42:34 2009 +0200 @@ -2,4 +2,3 @@ 'X pkey "ui.main-template", X value "main"') checkpoint() -add_cube('card', update_database=False) diff -r a721966779be -r cba9f175da2d misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Thu May 07 16:33:22 2009 +0200 +++ b/misc/migration/bootstrapmigration_repository.py Thu May 07 16:42:34 2009 +0200 @@ -7,6 +7,9 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ +if applcubicwebversion < (3, 2, 0) and cubicwebversion >= (3, 2, 0): + add_cube('card', update_database=False) + if applcubicwebversion < (2, 47, 0) and cubicwebversion >= (2, 47, 0): from cubicweb.server import schemaserial schemaserial.HAS_FULLTEXT_CONTAINER = False @@ -15,7 +18,7 @@ schemaserial.HAS_FULLTEXT_CONTAINER = True - + if applcubicwebversion < (2, 50, 0) and cubicwebversion >= (2, 50, 0): session.set_shared_data('do-not-insert-is_instance_of', True) add_relation_type('is_instance_of') @@ -40,4 +43,4 @@ sql('CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime)') sql('CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid)') checkpoint() - + diff -r a721966779be -r cba9f175da2d rtags.py --- a/rtags.py Thu May 07 16:33:22 2009 +0200 +++ b/rtags.py Thu May 07 16:42:34 2009 +0200 @@ -6,85 +6,84 @@ """ __docformat__ = "restructuredtext en" + class RelationTags(object): - """RelationTags instances are a tag store for full relation definitions : + """a tag store for full relation definitions : - (subject type, relation type, object type, role) + (subject type, relation type, object type, tagged) allowing to set tags using wildcard (eg '*') as subject type / object type - if `use_set` is True, a set of tags is associated to each key, and you - should use rtags / etype_rtags / add_rtag api. Otherwise, a single tag is - associated to each key, and you should use rtag / etype_rtag / set_rtag api. + This class associates a single tag to each key. """ - def __init__(self, use_set=False): - self.use_set = use_set + def __init__(self): self._tagdefs = {} - def set_rtag(self, tag, rtype, role, stype='*', otype='*'): - assert not self.use_set - assert role in ('subject', 'object'), role - self._tagdefs[(str(rtype), role, str(stype), str(otype))] = tag + def __repr__(self): + return repr(self._tagdefs) + + # dict compat + def __getitem__(self, key): + return self.get(*key) + __contains__ = __getitem__ - def del_rtag(self, rtype, role, stype='*', otype='*'): - assert not self.use_set - assert role in ('subject', 'object'), role - del self._tagdefs[(str(rtype), role, str(stype), str(otype))] + def _get_keys(self, rtype, tagged, stype, otype): + assert tagged in ('subject', 'object'), tagged + keys = [(rtype, tagged, '*', '*'), + (rtype, tagged, '*', otype), + (rtype, tagged, stype, '*'), + (rtype, tagged, stype, otype)] + if stype == '*' or otype == '*': + keys.remove((rtype, tagged, '*', '*')) + if stype == '*': + keys.remove((rtype, tagged, '*', otype)) + if otype == '*': + keys.remove((rtype, tagged, stype, '*')) + return keys - def rtag(self, rtype, role, stype='*', otype='*'): - assert not self.use_set - for key in reversed(self._get_keys(rtype, role, stype, otype)): + def tag_attribute(self, tag, stype, attr): + self._tagdefs[(str(attr), 'subject', str(stype), '*')] = tag + + def tag_relation(self, tag, relation, tagged): + assert tagged in ('subject', 'object'), tagged + stype, rtype, otype = relation + self._tagdefs[(str(rtype), tagged, str(stype), str(otype))] = tag + + def del_rtag(self, relation, tagged): + assert tagged in ('subject', 'object'), tagged + stype, rtype, otype = relation + del self._tagdefs[(str(rtype), tagged, str(stype), str(otype))] + + def get(self, rtype, tagged, stype='*', otype='*'): + for key in reversed(self._get_keys(rtype, tagged, stype, otype)): try: return self._tagdefs[key] except KeyError: continue return None - def etype_rtag(self, etype, rtype, role, ttype='*'): - if role == 'subject': - return self.rtag(rtype, role, etype, ttype) - return self.rtag(rtype, role, ttype, etype) + def etype_get(self, etype, rtype, tagged, ttype='*'): + if tagged == 'subject': + return self.get(rtype, tagged, etype, ttype) + return self.get(rtype, tagged, ttype, etype) + + - def add_rtag(self, tag, rtype, role, stype='*', otype='*'): - assert self.use_set - assert role in ('subject', 'object'), role - rtags = self._tagdefs.setdefault((rtype, role, stype, otype), set()) +class RelationTagsSet(RelationTags): + """This class associates a set of tags to each key.""" + + def tag_relation(self, tag, relation, tagged): + assert tagged in ('subject', 'object'), tagged + stype, rtype, otype = relation + rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), set()) rtags.add(tag) - def rtags(self, rtype, role, stype='*', otype='*'): - assert self.use_set + def get(self, rtype, tagged, stype='*', otype='*'): rtags = set() - for key in self._get_keys(rtype, role, stype, otype): + for key in self._get_keys(rtype, tagged, stype, otype): try: rtags.update(self._tagdefs[key]) except KeyError: continue return rtags - - def etype_rtags(self, etype, rtype, role, ttype='*'): - if role == 'subject': - return self.rtags(rtype, role, etype, ttype) - return self.rtags(rtype, role, ttype, etype) - - def _get_keys(self, rtype, role, stype, otype): - assert role in ('subject', 'object'), role - keys = [(rtype, role, '*', '*'), - (rtype, role, '*', otype), - (rtype, role, stype, '*'), - (rtype, role, stype, otype)] - if stype == '*' or otype == '*': - keys.remove((rtype, role, '*', '*')) - if stype == '*': - keys.remove((rtype, role, '*', otype)) - if otype == '*': - keys.remove((rtype, role, stype, '*')) - return keys - - # dict compat - def __getitem__(self, key): - if isinstance(key, basestring): - key = (key,) - return self.rtags(*key) - - __contains__ = __getitem__ diff -r a721966779be -r cba9f175da2d schemas/workflow.py --- a/schemas/workflow.py Thu May 07 16:33:22 2009 +0200 +++ b/schemas/workflow.py Thu May 07 16:42:34 2009 +0200 @@ -13,14 +13,14 @@ maxsize=256) description = RichString(fulltextindexed=True, default_format='text/rest', description=_('semantic description of this state')) - + state_of = SubjectRelation('CWEType', cardinality='+*', description=_('entity types which may use this state'), constraints=[RQLConstraint('O final FALSE')]) allowed_transition = SubjectRelation('Transition', cardinality='**', constraints=[RQLConstraint('S state_of ET, O transition_of ET')], description=_('allowed transitions from this state')) - + initial_state = ObjectRelation('CWEType', cardinality='?*', # S initial_state O, O state_of S constraints=[RQLConstraint('O state_of S')], @@ -43,7 +43,7 @@ 'This query may use X and U variables ' 'that will respectivly represents ' 'the current entity and the current user')) - + require_group = SubjectRelation('CWGroup', cardinality='**', description=_('group in which a user should be to be ' 'allowed to pass this transition')) @@ -78,12 +78,12 @@ inlined = True composite = 'object' fulltext_container = composite - + class state_of(MetaRelationType): """link a state to one or more entity type""" class transition_of(MetaRelationType): """link a transition to one or more entity type""" - + class initial_state(MetaRelationType): """indicate which state should be used by default when an entity using states is created @@ -93,7 +93,7 @@ class destination_state(MetaRelationType): """destination state of a transition""" inlined = True - + class allowed_transition(MetaRelationType): """allowed transition from this state""" @@ -102,7 +102,7 @@ meta = True # not inlined intentionnaly since when using ldap sources, user'state # has to be stored outside the CWUser table - + # add/delete perms given to managers/users, after what most of the job # is done by workflow enforcment - + diff -r a721966779be -r cba9f175da2d schemaviewer.py --- a/schemaviewer.py Thu May 07 16:33:22 2009 +0200 +++ b/schemaviewer.py Thu May 07 16:42:34 2009 +0200 @@ -157,7 +157,7 @@ return layout _ = self.req._ if self.req.user.matching_groups('managers'): - layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update'))) + # layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update'))) # possible views for this entity type views = [_(view.title) for view in self.possible_views(etype)] layout.append(Section(children=(Table(cols=1, rheaders=1, diff -r a721966779be -r cba9f175da2d selectors.py --- a/selectors.py Thu May 07 16:33:22 2009 +0200 +++ b/selectors.py Thu May 07 16:42:34 2009 +0200 @@ -51,7 +51,8 @@ from yams import BASE_TYPES -from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role +from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity, + role, typed_eid) from cubicweb.vregistry import (NoSelectableObject, Selector, chainall, objectify_selector) from cubicweb.cwconfig import CubicWebConfiguration @@ -529,6 +530,20 @@ return score +class match_transition(match_search_state): + @lltrace + def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): + try: + trname = req.execute('Any XN WHERE X is Transition, X eid %(x)s, X name XN', + {'x': typed_eid(req.form['treid'])})[0][0] + except (KeyError, IndexError): + return 0 + # XXX check this is a transition that apply to the object? + if not trname in self.expected: + return 0 + return 1 + + class match_view(match_search_state): """accept if the current view is in one of the expected vid given to the initializer @@ -1096,7 +1111,8 @@ def plug_selector(cls, vreg): cls = registered(cls, vreg) if getattr(cls, 'accepts', None): - warn('use "implements("EntityType", IFace)" instead of using accepts', + warn('use "implements("EntityType", IFace)" instead of using accepts on %s' + % cls, DeprecationWarning) cls.__select__ &= implements(*cls.accepts) return cls diff -r a721966779be -r cba9f175da2d server/repository.py --- a/server/repository.py Thu May 07 16:33:22 2009 +0200 +++ b/server/repository.py Thu May 07 16:42:34 2009 +0200 @@ -497,6 +497,7 @@ given password. This method is designed to be used for anonymous registration on public web site. """ + # XXX should not be called from web interface session = self.internal_session() # for consistency, keep same error as unique check hook (although not required) errmsg = session._('the value "%s" is already used, use another one') diff -r a721966779be -r cba9f175da2d server/schemaserial.py --- a/server/schemaserial.py Thu May 07 16:33:22 2009 +0200 +++ b/server/schemaserial.py Thu May 07 16:42:34 2009 +0200 @@ -32,7 +32,7 @@ if missing: print 'some native groups are missing but the following groups have been found:' print '\n'.join('* %s (%s)' % (n, eid) for n, eid in res.items()) - print + print print 'enter the eid of a to group to map to each missing native group' print 'or just type enter to skip permissions granted to a group' for group in missing: @@ -56,7 +56,7 @@ print 'changed SQL_PREFIX for %s' % module except KeyError: pass - + def _update_database(schema, sqlcu): """3.2.0 migration function: update database schema by adding SQL_PREFIX to entity type tables and columns @@ -90,7 +90,8 @@ repo = session.repo sqlcu = session.pool['system'] _3_2_migration = False - if 'eetype' in [t.lower() for t in repo.system_source.dbhelper.list_tables(sqlcu)]: + tables = set(t.lower() for t in repo.system_source.dbhelper.list_tables(sqlcu)) + if 'eetype' in tables: _3_2_migration = True # 3.2 migration _set_sql_prefix('') @@ -99,9 +100,11 @@ for etype in ('EFRDef', 'ENFRDef', 'ERType', 'EEType', 'EConstraintType', 'EConstraint', 'EGroup', 'EUser', 'ECache', 'EPermission', 'EProperty'): - sql = 'ALTER TABLE %s RENAME TO %s' % (etype, ETYPE_NAME_MAP[etype]) - print sql - sqlcu.execute(sql) + if etype.lower() in tables: + sql = 'ALTER TABLE %s RENAME TO %s' % (etype, + ETYPE_NAME_MAP[etype]) + print sql + sqlcu.execute(sql) # other table renaming done once schema has been readen # print 'reading schema from the database...' index = {} @@ -132,9 +135,9 @@ except: pass tocleanup = [eid] - tocleanup += (eid for eid, (eidetype, uri, extid) in session.repo._type_source_cache.items() + tocleanup += (eid for eid, (eidetype, uri, extid) in repo._type_source_cache.items() if etype == eidetype) - session.repo.clear_caches(tocleanup) + repo.clear_caches(tocleanup) session.commit(False) etype = ETYPE_NAME_MAP[etype] etype = ybo.EntityType(name=etype, description=desc, meta=meta, eid=eid) @@ -167,7 +170,7 @@ fulltext_container=ft_container, eid=eid) rschema = schema.add_relation_type(rtype) index[eid] = rschema - set_perms(rschema, permsdict.get(eid, {})) + set_perms(rschema, permsdict.get(eid, {})) cstrsdict = deserialize_rdef_constraints(session) for values in session.execute( 'Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT WHERE X is CWAttribute,' @@ -181,7 +184,7 @@ rtype = index[reid].type toetype = index[teid].type rdef = ybo.RelationDefinition(frometype, rtype, toetype, cardinality=card, - order=ord, description=desc, + order=ord, description=desc, constraints=constraints, indexed=idx, fulltextindexed=ftidx, internationalizable=i18n, @@ -197,7 +200,7 @@ toetype = index[teid].type constraints = cstrsdict.get(rdefeid, ()) rdef = ybo.RelationDefinition(frometype, rtype, toetype, cardinality=card, - order=ord, description=desc, + order=ord, description=desc, composite=c, constraints=constraints, eid=rdefeid) schema.add_relation_def(rdef) @@ -241,7 +244,7 @@ actperms.append(erschema.rql_expression(*something)) else: # group name actperms.append(something) - erschema.set_permissions(action, actperms) + erschema.set_permissions(action, actperms) def deserialize_rdef_constraints(session): @@ -254,8 +257,8 @@ cstr.eid = ceid res.setdefault(rdefeid, []).append(cstr) return res - - + + # schema / perms serialization ################################################ def serialize_schema(cursor, schema, verbose=False): @@ -352,12 +355,12 @@ value = unicode(value) values[amap.get(prop, prop)] = value return values - + def nfrdef_relations_values(rschema, objtype, props): values = _rdef_values(rschema, objtype, props) relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] return relations, values - + def frdef_relations_values(rschema, objtype, props): values = _rdef_values(rschema, objtype, props) default = values['default'] @@ -371,7 +374,7 @@ relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] return relations, values - + def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None): if subjtype is None: assert objtype is None @@ -405,7 +408,7 @@ elif allow is not None: return chain(*[erschema2rql(schema[t]) for t in all if t in allow]) return chain(*[erschema2rql(schema[t]) for t in all]) - + def erschema2rql(erschema): if isinstance(erschema, schemamod.EntitySchema): return eschema2rql(erschema) @@ -442,7 +445,7 @@ if addrdef: for rql, values in rdef2rql(rschema): yield rql, values - + def rdef2rql(rschema, subjtype=None, objtype=None, props=None): genmap = {True: frdef2rql, False: nfrdef2rql} return __rdef2rql(genmap, rschema, subjtype, objtype, props) @@ -458,7 +461,7 @@ yield 'INSERT CWAttribute X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props): yield rql + ', EDEF is CWAttribute', values - + def nfrdef2rql(rschema, subjtype, objtype, props): relations, values = nfrdef_relations_values(rschema, objtype, props) relations.append(_LOCATE_RDEF_RQL0) @@ -466,7 +469,7 @@ yield 'INSERT CWRelation X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props): yield rql + ', EDEF is CWRelation', values - + def rdefrelations2rql(rschema, subjtype, objtype, props): iterators = [] for constraint in props['constraints']: @@ -525,7 +528,7 @@ relations, values = rschema_relations_values(rschema) values['rt'] = rschema.type yield 'SET %s WHERE X is CWRType, X name %%(rt)s' % ','.join(relations), values - + def updaterdef2rql(rschema, subjtype=None, objtype=None, props=None): genmap = {True: updatefrdef2rql, False: updatenfrdef2rql} return __rdef2rql(genmap, rschema, subjtype, objtype, props) @@ -536,7 +539,7 @@ yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations), _LOCATE_RDEF_RQL0, _LOCATE_RDEF_RQL1), values - + def updatenfrdef2rql(rschema, subjtype, objtype, props): relations, values = nfrdef_relations_values(rschema, objtype, props) values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype}) diff -r a721966779be -r cba9f175da2d server/serverconfig.py --- a/server/serverconfig.py Thu May 07 16:33:22 2009 +0200 +++ b/server/serverconfig.py Thu May 07 16:42:34 2009 +0200 @@ -81,7 +81,7 @@ 'system (using cron for instance).', 'group': 'main', 'inputlevel': 1, }), - + # email configuration ('default-recipients-mode', {'type' : 'choice', @@ -123,14 +123,14 @@ 'group': 'pyro-server', 'inputlevel': 2, }), ) + CubicWebConfiguration.options) - + # read the schema from the database read_application_schema = True bootstrap_schema = True - + # check user's state at login time consider_user_state = True - + # hooks registration configuration # all hooks should be activated during normal execution core_hooks = True @@ -142,7 +142,7 @@ # should some hooks be deactivated during [pre|post]create script execution free_wheel = False - + # list of enables sources when sources restriction is necessary # (eg repository initialization at least) _enabled_sources = None @@ -150,7 +150,7 @@ def enabled_sources(self, sourceuris=None): self._enabled_sources = sourceuris clear_cache(self, 'sources') - + @classmethod def schemas_lib_dir(cls): """application schema directory""" @@ -172,17 +172,17 @@ else: # no cubes self.init_cubes(()) - + def write_bootstrap_cubes_file(self, cubes): stream = file(join(self.apphome, 'bootstrap_cubes'), 'w') stream.write('# this is a generated file only used for bootstraping\n') stream.write('# you should not have to edit this\n') stream.write('%s\n' % ','.join(cubes)) stream.close() - + def sources_file(self): return join(self.apphome, 'sources') - + # this method has to be cached since when the server is running using a # restricted user, this user usually don't have access to the sources # configuration file (#16102) @@ -196,11 +196,11 @@ return allsources return dict((uri, config) for uri, config in allsources.items() if uri in self._enabled_sources or uri == 'admin') - + def pyro_enabled(self): """pyro is always enabled in standalone repository configuration""" return True - + def load_hooks(self, vreg): hooks = {} for path in reversed([self.apphome] + self.cubes_path()): @@ -209,8 +209,8 @@ self.warning('application_hooks.py is deprecated, use dynamic ' 'objects to register hooks (%s)', hooksfile) context = {} - # Use execfile rather than `load_module_from_name` because - # the latter gets fooled by the `sys.modules` cache when + # Use execfile rather than `load_module_from_name` because + # the latter gets fooled by the `sys.modules` cache when # loading different configurations one after the other # (another fix would have been to do : # sys.modules.pop('applications_hooks') @@ -231,8 +231,8 @@ cb = hookdef.make_callback(event) hooks.setdefault(event, {}).setdefault(ertype, []).append(cb) return hooks - - def load_schema(self, expand_cubes=False, construction_mode='strict'): + + def load_schema(self, expand_cubes=False, **kwargs): from cubicweb.schema import CubicWebSchemaLoader if expand_cubes: # in case some new dependencies have been introduced, we have to @@ -240,18 +240,18 @@ origcubes = self.cubes() self._cubes = None self.init_cubes(self.expand_cubes(origcubes)) - schema = CubicWebSchemaLoader().load(self, construction_mode=construction_mode) + schema = CubicWebSchemaLoader().load(self, **kwargs) if expand_cubes: # restaure original value self._cubes = origcubes return schema - + def load_bootstrap_schema(self): from cubicweb.schema import BootstrapSchemaLoader schema = BootstrapSchemaLoader().load(self) schema.name = 'bootstrap' return schema - + def set_sources_mode(self, sources): if 'migration' in sources: from cubicweb.server.sources import source_adapter @@ -274,7 +274,7 @@ enabled_sources = sources self._enabled_sources = enabled_sources clear_cache(self, 'sources') - + def migration_handler(self, schema=None, interactive=True, cnx=None, repo=None, connect=True): """return a migration handler instance""" diff -r a721966779be -r cba9f175da2d server/serverctl.py --- a/server/serverctl.py Thu May 07 16:33:22 2009 +0200 +++ b/server/serverctl.py Thu May 07 16:42:34 2009 +0200 @@ -139,6 +139,8 @@ return in_memory_cnx(config, login, pwd) except AuthenticationError: print 'wrong user/password' + # reset cubes else we'll have an assertion error on next retry + config._cubes = None login, pwd = manager_userpasswd() # repository specific command handlers ######################################## diff -r a721966779be -r cba9f175da2d server/session.py --- a/server/session.py Thu May 07 16:33:22 2009 +0200 +++ b/server/session.py Thu May 07 16:42:34 2009 +0200 @@ -13,7 +13,7 @@ from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj from yams import BASE_TYPES -from cubicweb import RequestSessionMixIn, Binary +from cubicweb import RequestSessionMixIn, Binary, UnknownEid from cubicweb.dbapi import ConnectionProperties from cubicweb.utils import make_uid from cubicweb.server.rqlrewrite import RQLRewriter @@ -29,7 +29,7 @@ continue if etype in BASE_TYPES: return True - return False + return False def _make_description(selected, args, solution): """return a description for a result set""" @@ -45,7 +45,7 @@ """tie session id, user, connections pool and other session data all together """ - + def __init__(self, user, repo, cnxprops=None, _id=None): super(Session, self).__init__(repo.vreg) self.id = _id or make_uid(user.login.encode('UTF8')) @@ -65,7 +65,7 @@ # i18n initialization self.set_language(cnxprops.lang) self._threaddata = threading.local() - + def get_mode(self): return getattr(self._threaddata, 'mode', 'read') def set_mode(self, value): @@ -78,12 +78,12 @@ def set_commit_state(self, value): self._threaddata.commit_state = value commit_state = property(get_commit_state, set_commit_state) - + # set according to transaction mode for each query @property def pool(self): return getattr(self._threaddata, 'pool', None) - + # pending transaction operations @property def pending_operations(self): @@ -92,7 +92,7 @@ except AttributeError: self._threaddata.pending_operations = [] return self._threaddata.pending_operations - + # rql rewriter @property def rql_rewriter(self): @@ -101,7 +101,7 @@ except AttributeError: self._threaddata._rewriter = RQLRewriter(self.repo.querier, self) return self._threaddata._rewriter - + # transaction queries data @property def _query_data(self): @@ -110,7 +110,7 @@ except AttributeError: self._threaddata._query_data = {} return self._threaddata._query_data - + def set_language(self, language): """i18n configuration for translation""" vreg = self.vreg @@ -124,42 +124,42 @@ except KeyError: self._ = self.__ = unicode self.lang = language - + def change_property(self, prop, value): assert prop == 'lang' # this is the only one changeable property for now self.set_language(value) def __str__(self): - return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login, + return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login, self.id, id(self)) def etype_class(self, etype): """return an entity class for the given entity type""" return self.vreg.etype_class(etype) - + def entity(self, eid): """return a result set for the given eid""" return self.eid_rset(eid).get_entity(0, 0) - + def _touch(self): """update latest session usage timestamp and reset mode to read """ self.timestamp = time() self.local_perm_cache.clear() self._threaddata.mode = 'read' - + def set_pool(self): """the session need a pool to execute some queries""" if self.pool is None: self._threaddata.pool = self.repo._get_pool() - try: + try: self._threaddata.pool.pool_set(self) except: self.repo._free_pool(self.pool) self._threaddata.pool = None raise return self._threaddata.pool - + def reset_pool(self): """the session has no longer using its pool, at least for some time """ @@ -170,7 +170,7 @@ self.repo._free_pool(self.pool) self.pool.pool_reset(self) self._threaddata.pool = None - + def system_sql(self, sql, args=None): """return a sql cursor on the system database""" if not sql.split(None, 1)[0].upper() == 'SELECT': @@ -181,26 +181,26 @@ def actual_session(self): """return the original parent session if any, else self""" - return self + return self # shared data handling ################################################### - + def get_shared_data(self, key, default=None, pop=False): """return value associated to `key` in session data""" if pop: return self.data.pop(key, default) else: return self.data.get(key, default) - + def set_shared_data(self, key, value, querydata=False): """set value associated to `key` in session data""" if querydata: self.set_query_data(key, value) else: self.data[key] = value - + # request interface ####################################################### - + def set_entity_cache(self, entity): # no entity cache in the server, too high risk of inconsistency # between pre/post hooks @@ -211,20 +211,20 @@ def base_url(self): return self.repo.config['base-url'] or u'' - + def from_controller(self): """return the id (string) of the controller issuing the request (no sense here, always return 'view') """ return 'view' - + def source_defs(self): return self.repo.source_defs() def describe(self, eid): """return a tuple (type, sourceuri, extid) for the entity with id """ return self.repo.type_and_source_from_eid(eid, self) - + # db-api like interface ################################################### def source_from_eid(self, eid): @@ -249,7 +249,7 @@ # need shared pool set self.set_pool() return csession - + def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=False, propagate=False): """like .execute but with security checking disabled (this method is @@ -266,7 +266,7 @@ def cursor(self): """return a rql cursor""" return self - + def execute(self, rql, kwargs=None, eid_key=None, build_descr=True, propagate=False): """db-api like method directly linked to the querier execute method @@ -276,7 +276,7 @@ """ rset = self._execute(self, rql, kwargs, eid_key, build_descr) return self.decorate_rset(rset, propagate) - + def commit(self, reset_pool=True): """commit the current session's transaction""" if self.pool is None: @@ -317,7 +317,7 @@ self._query_data.clear() if reset_pool: self.reset_pool() - + def rollback(self, reset_pool=True): """rollback the current session's transaction""" if self.pool is None: @@ -340,19 +340,19 @@ self._query_data.clear() if reset_pool: self.reset_pool() - + def close(self): """do not close pool on session close, since they are shared now""" self.rollback() - + # transaction data/operations management ################################## - + def add_query_data(self, key, value): self._query_data.setdefault(key, []).append(value) - + def set_query_data(self, key, value): self._query_data[key] = value - + def query_data(self, key, default=None, setdefault=False, pop=False): if setdefault: assert not pop @@ -361,7 +361,7 @@ return self._query_data.pop(key, default) else: return self._query_data.get(key, default) - + def add_operation(self, operation, index=None): """add an observer""" assert self.commit_state != 'commit' @@ -369,9 +369,9 @@ self.pending_operations.insert(index, operation) else: self.pending_operations.append(operation) - + # querier helpers ######################################################### - + def build_description(self, rqlst, args, result): """build a description for a given result""" if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1: @@ -385,7 +385,7 @@ def manual_build_descr(self, rqlst, args, result): """build a description for a given result by analysing each row - + XXX could probably be done more efficiently during execution of query """ # not so easy, looks for variable which changes from one solution @@ -410,7 +410,7 @@ if not todetermine: return [tuple(basedescription)] * len(result) return self._build_descr(result, basedescription, todetermine) - + def _build_descr(self, result, basedescription, todetermine): description = [] etype_from_eid = self.describe @@ -422,23 +422,23 @@ # None value inserted by an outer join, no type row_descr[index] = None continue - try: - if isfinal: - row_descr[index] = etype_from_pyobj(value) - else: + if isfinal: + row_descr[index] = etype_from_pyobj(value) + else: + try: row_descr[index] = etype_from_eid(value)[0] - except UnknownEid: - self.critical('wrong eid in repository, should check database') - row_descr[index] = row[index] = None + except UnknownEid: + self.critical('wrong eid %s in repository, should check database' % value) + row_descr[index] = row[index] = None description.append(tuple(row_descr)) return description - + class ChildSession(Session): """child (or internal) session are used to hijack the security system """ cnxtype = 'inmemory' - + def __init__(self, parent_session): self.id = None self.is_internal_session = False @@ -454,7 +454,7 @@ self._ = self.__ = parent_session._ # short cut to querier .execute method self._execute = self.repo.querier.execute - + @property def super_session(self): return self @@ -470,7 +470,7 @@ def set_commit_state(self, value): self.parent_session.set_commit_state(value) commit_state = property(get_commit_state, set_commit_state) - + @property def pool(self): return self.parent_session.pool @@ -480,11 +480,11 @@ @property def _query_data(self): return self.parent_session._query_data - + def set_pool(self): """the session need a pool to execute some queries""" self.parent_session.set_pool() - + def reset_pool(self): """the session has no longer using its pool, at least for some time """ @@ -493,19 +493,19 @@ def actual_session(self): """return the original parent session if any, else self""" return self.parent_session - + def commit(self, reset_pool=True): """commit the current session's transaction""" self.parent_session.commit(reset_pool) - + def rollback(self, reset_pool=True): """rollback the current session's transaction""" self.parent_session.rollback(reset_pool) - + def close(self): """do not close pool on session close, since they are shared now""" self.rollback() - + def user_data(self): """returns a dictionnary with this user's information""" return self.parent_session.user_data() @@ -513,14 +513,14 @@ class InternalSession(Session): """special session created internaly by the repository""" - + def __init__(self, repo, cnxprops=None): super(InternalSession, self).__init__(_IMANAGER, repo, cnxprops, _id='internal') self.cnxtype = 'inmemory' self.is_internal_session = True self.is_super_session = True - + @property def super_session(self): return self @@ -544,7 +544,7 @@ def owns(self, eid): return True - + def has_permission(self, pname, contexteid=None): return True diff -r a721966779be -r cba9f175da2d server/sources/rql2sql.py --- a/server/sources/rql2sql.py Thu May 07 16:33:22 2009 +0200 +++ b/server/sources/rql2sql.py Thu May 07 16:42:34 2009 +0200 @@ -951,6 +951,8 @@ if rel is not None: rel._q_needcast = value return self.keyword_map[value]() + if constant.type == 'Boolean': + value = self.dbms_helper.boolean_value(value) if constant.type == 'Substitute': _id = constant.value if isinstance(_id, unicode): diff -r a721966779be -r cba9f175da2d server/sqlutils.py --- a/server/sqlutils.py Thu May 07 16:33:22 2009 +0200 +++ b/server/sqlutils.py Thu May 07 16:42:34 2009 +0200 @@ -7,7 +7,7 @@ __docformat__ = "restructuredtext en" from warnings import warn -from datetime import datetime, timedelta +from datetime import datetime, date, timedelta from logilab.common.shellutils import ProgressBar from logilab.common import db @@ -17,6 +17,7 @@ from indexer import get_indexer from cubicweb import Binary, ConfigurationError +from cubicweb.utils import todate, todatetime from cubicweb.common.uilib import remove_html_tags from cubicweb.server import SQL_CONNECT_HOOKS from cubicweb.server.utils import crypt_password @@ -68,8 +69,8 @@ w(grant_schema(schema, user, set_owner, skip_entities=skip_entities, prefix=SQL_PREFIX)) return '\n'.join(output) - -def sqlschema(schema, driver, text_index=True, + +def sqlschema(schema, driver, text_index=True, user=None, set_owner=False, skip_relations=('has_text', 'identity'), skip_entities=()): """return the system sql schema, according to the given parameters""" @@ -86,7 +87,7 @@ w(indexer.sql_init_fti()) w('') dbhelper = get_adv_func_helper(driver) - w(schema2sql(dbhelper, schema, prefix=SQL_PREFIX, + w(schema2sql(dbhelper, schema, prefix=SQL_PREFIX, skip_entities=skip_entities, skip_relations=skip_relations)) if dbhelper.users_support and user: w('') @@ -94,8 +95,8 @@ skip_relations, skip_entities)) return '\n'.join(output) - -def sqldropschema(schema, driver, text_index=True, + +def sqldropschema(schema, driver, text_index=True, skip_relations=('has_text', 'identity'), skip_entities=()): """return the sql to drop the schema, according to the given parameters""" from yams.schema2sql import dropschema2sql @@ -121,7 +122,7 @@ """Mixin for SQL data sources, getting a connection from a configuration dictionary and handling connection locking """ - + def __init__(self, source_config): try: self.dbdriver = source_config['db-driver'].lower() @@ -138,7 +139,7 @@ self.binary = self.dbapi_module.Binary self.dbhelper = self.dbapi_module.adv_func_helper self.sqlgen = SQLGenerator() - + def get_connection(self, user=None, password=None): """open and return a connection to the database""" if user or self.dbuser: @@ -216,6 +217,11 @@ value = value.getvalue() else: value = crypt_password(value) + # XXX needed for sqlite but I don't think it is for other backends + elif atype == 'Datetime' and isinstance(value, date): + value = todatetime(value) + elif atype == 'Date' and isinstance(value, datetime): + value = todate(value) elif isinstance(value, Binary): value = self.binary(value.getvalue()) # XXX <3.2 bw compat @@ -254,7 +260,7 @@ # some time cnx.create_aggregate("CONCAT_STRINGS", 1, concat_strings) cnx.create_aggregate("GROUP_CONCAT", 1, concat_strings) - + def _limit_size(text, maxsize, format='text/plain'): if len(text) < maxsize: return text diff -r a721966779be -r cba9f175da2d skeleton/MANIFEST.in --- a/skeleton/MANIFEST.in Thu May 07 16:33:22 2009 +0200 +++ b/skeleton/MANIFEST.in Thu May 07 16:42:34 2009 +0200 @@ -2,4 +2,4 @@ recursive-include data external_resources *.gif *.png *.css *.ico *.js recursive-include i18n *.pot *.po -recursive-include migration *.sql *.py depends.map +recursive-include migration *.py diff -r a721966779be -r cba9f175da2d skeleton/debian/rules.tmpl --- a/skeleton/debian/rules.tmpl Thu May 07 16:33:22 2009 +0200 +++ b/skeleton/debian/rules.tmpl Thu May 07 16:42:34 2009 +0200 @@ -25,7 +25,7 @@ dh_installdirs -i python setup.py -q install --no-compile --prefix=debian/%(distname)s/usr/ # remove generated .egg-info file - rm -rf debian/cubicweb-comment/usr/lib/python* + rm -rf debian/%(distname)s/usr/lib/python* # Build architecture-independent files here. diff -r a721966779be -r cba9f175da2d test/data/bootstrap_cubes --- a/test/data/bootstrap_cubes Thu May 07 16:33:22 2009 +0200 +++ b/test/data/bootstrap_cubes Thu May 07 16:42:34 2009 +0200 @@ -1,1 +1,1 @@ -file, tag +card, file, tag diff -r a721966779be -r cba9f175da2d test/data/cubes/mycube/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/data/cubes/mycube/__init__.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,1 @@ +"""mycube's __init__""" diff -r a721966779be -r cba9f175da2d test/data/cubes/mycube/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/data/cubes/mycube/__pkginfo__.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,1 @@ +distname = 'cubicweb-mycube' diff -r a721966779be -r cba9f175da2d test/unittest_cwconfig.py --- a/test/unittest_cwconfig.py Thu May 07 16:33:22 2009 +0200 +++ b/test/unittest_cwconfig.py Thu May 07 16:42:34 2009 +0200 @@ -14,7 +14,7 @@ if part.startswith('cubicweb') or part == 'cubes': return '/'.join(parts[i+1:]) raise Exception('duh? %s' % path) - + class CubicWebConfigurationTC(TestCase): def setUp(self): self.config = ApptestConfiguration('data') @@ -38,7 +38,7 @@ ('jpl', 'email', 'file')) self.assertEquals(self.config.reorder_cubes(('jpl', 'email', 'file')), ('jpl', 'email', 'file')) - + def test_reorder_cubes_recommends(self): from cubes.comment import __pkginfo__ as comment_pkginfo comment_pkginfo.__recommend__ = ('file',) @@ -55,8 +55,8 @@ ('jpl', 'email', 'comment', 'file')) finally: comment_pkginfo.__use__ = () - - + + # def test_vc_config(self): # vcconf = self.config.vc_config() # self.assertIsInstance(vcconf['EEMAIL'], Version) @@ -64,7 +64,7 @@ # self.assertEquals(vcconf['CW'], (2, 31, 2)) # self.assertRaises(KeyError, vcconf.__getitem__, 'CW_VERSION') # self.assertRaises(KeyError, vcconf.__getitem__, 'CRM') - + def test_expand_cubes(self): self.assertEquals(self.config.expand_cubes(('email', 'eblog')), ['email', 'eblog', 'file']) @@ -104,7 +104,7 @@ del cubes.file from cubes import file self.assertEquals(file.__path__, [abspath(join(dirname(__file__), 'data', 'cubes', 'file'))]) - - + + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d test/unittest_entity.py --- a/test/unittest_entity.py Thu May 07 16:33:22 2009 +0200 +++ b/test/unittest_entity.py Thu May 07 16:42:34 2009 +0200 @@ -15,7 +15,7 @@ ## self.add_entity('Tag', name=u'x') ## self.add_entity('Link', title=u'perdu', url=u'http://www.perdu.com', ## embed=False) - + def test_boolean_value(self): e = self.etype_instance('CWUser') self.failUnless(e) @@ -37,7 +37,7 @@ self.assertEquals(e.has_eid(), True) e.eid = 2 self.assertEquals(e.has_eid(), True) - + def test_copy(self): self.add_entity('Tag', name=u'x') p = self.add_entity('Personne', nom=u'toto') @@ -50,7 +50,7 @@ self.assertEquals(len(e.ecrit_par), 1) self.assertEquals(e.ecrit_par[0].eid, p.eid) self.assertEquals(len(e.reverse_tags), 0) - + def test_copy_with_nonmeta_composite_inlined(self): p = self.add_entity('Personne', nom=u'toto') oe = self.add_entity('Note', type=u'x') @@ -61,7 +61,7 @@ e.copy_relations(oe.eid) self.failIf(e.ecrit_par) self.failUnless(oe.ecrit_par) - + def test_copy_with_composite(self): user = self.user() adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0] @@ -74,7 +74,7 @@ e.copy_relations(user.eid) self.failIf(e.use_email) self.failIf(e.primary_email) - + def test_copy_with_non_initial_state(self): user = self.user() eid = self.execute('INSERT CWUser X: X login "toto", X upassword %(pwd)s, X in_group G WHERE G name "users"', @@ -110,7 +110,7 @@ self.assertEquals(len(p.related('tags', 'object', limit=2)), 2) self.assertEquals(len(p.related('tags', 'object')), 4) - + def test_fetch_rql(self): user = self.user() Personne = self.vreg.etype_class('Personne') @@ -130,7 +130,7 @@ try: # testing unknown attributes Personne.fetch_attrs = ('bloug', 'beep') - self.assertEquals(Personne.fetch_rql(user), 'Any X WHERE X is Personne') + self.assertEquals(Personne.fetch_rql(user), 'Any X WHERE X is Personne') # testing one non final relation Personne.fetch_attrs = ('nom', 'prenom', 'travaille') self.assertEquals(Personne.fetch_rql(user), @@ -179,7 +179,7 @@ # XXX self.assertEquals(aff.related_rql('evaluee'), 'Any X,AA ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E evaluee X, X modification_date AA') - + def test_entity_unrelated(self): p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien') e = self.add_entity('Tag', name=u'x') @@ -198,7 +198,7 @@ self.add_entity('Personne', nom=u'di mascio', prenom=u'gwen') self.assertEquals(len(e.unrelated('tags', 'Personne', 'subject', limit=1)), 1) - + def test_new_entity_unrelated(self): e = self.etype_instance('CWUser') unrelated = [r[0] for r in e.unrelated('in_group', 'CWGroup', 'subject')] @@ -209,7 +209,7 @@ e = self.add_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`', content_format=u'text/rest') self.assertEquals(e.printable_value('content'), - '

du *ReST*

\n') + '

du *ReST*

\n') e['content'] = 'du html users' e['content_format'] = 'text/html' self.assertEquals(e.printable_value('content'), @@ -237,7 +237,7 @@ e['content_format'] = 'text/cubicweb-page-template' self.assertEquals(e.printable_value('content'), '

zou

') - + def test_printable_value_bytes(self): e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python', @@ -254,7 +254,7 @@ lambda x: 1 ''') - + e = self.add_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest', data_encoding=u'utf-8', name=u'toto.txt') self.assertEquals(e.printable_value('data'), @@ -290,8 +290,8 @@ self.assertEquals(e.printable_value('content'), e['content']) e['content'] = u'été' self.assertEquals(e.printable_value('content'), e['content']) - - + + def test_fulltextindex(self): e = self.etype_instance('File') e['name'] = 'an html file' @@ -300,17 +300,17 @@ e['data'] = Binary('some data') e['data_format'] = 'text/html' e['data_encoding'] = 'ascii' - self.assertEquals(set(e.get_words()), + self.assertEquals(set(e.get_words()), set(['an', 'html', 'file', 'du', 'html', 'some', 'data'])) - + def test_nonregr_relation_cache(self): p1 = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien') p2 = self.add_entity('Personne', nom=u'toto') self.execute('SET X evaluee Y WHERE X nom "di mascio", Y nom "toto"') self.assertEquals(p1.evaluee[0].nom, "toto") self.failUnless(not p1.reverse_evaluee) - + def test_complete_relation(self): self.execute('SET RT add_permission G WHERE RT name "wf_info_for", G name "managers"') self.commit() @@ -382,7 +382,7 @@ metainf['source'] = metainf['source'].copy() metainf['source']['base-url'] = 'http://cubicweb2.com/' self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/%s' % note.eid) - + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r a721966779be -r cba9f175da2d test/unittest_rset.py --- a/test/unittest_rset.py Thu May 07 16:33:22 2009 +0200 +++ b/test/unittest_rset.py Thu May 07 16:42:34 2009 +0200 @@ -12,17 +12,17 @@ from cubicweb.rset import NotAnEntity, ResultSet, attr_desc_iterator - + def pprelcachedict(d): res = {} for k, (rset, related) in d.items(): res[k] = sorted(v.eid for v in related) return sorted(res.items()) - + class AttrDescIteratorTC(TestCase): """TestCase for cubicweb.rset.attr_desc_iterator""" - + def test_relations_description(self): """tests relations_description() function""" queries = { @@ -35,7 +35,7 @@ for rql, relations in queries.items(): result = list(attr_desc_iterator(parse(rql).children[0])) self.assertEquals((rql, result), (rql, relations)) - + def test_relations_description_indexed(self): """tests relations_description() function""" queries = { @@ -49,7 +49,7 @@ -class ResultSetTC(EnvBasedTC): +class ResultSetTC(EnvBasedTC): def setUp(self): super(ResultSetTC, self).setUp() @@ -67,7 +67,7 @@ params2 = dict(pair.split('=') for pair in info1[3].split('&')) self.assertDictEquals(params1, params2) - + def test_build_url(self): req = self.request() baseurl = req.base_url() @@ -79,8 +79,8 @@ # '%stask/title/go' % baseurl) # empty _restpath should not crash self.compare_urls(req.build_url('view', _restpath=''), baseurl) - - + + def test_resultset_build(self): """test basic build of a ResultSet""" rs = ResultSet([1,2,3], 'CWGroup X', description=['CWGroup', 'CWGroup', 'CWGroup']) @@ -102,7 +102,7 @@ self.assertEquals(rs2.get_entity(0, 0).row, 0) self.assertEquals(rs.limit(2, offset=2).rows, [[14000, 'nico']]) self.assertEquals(rs.limit(2, offset=3).rows, []) - + def test_resultset_filter(self): rs = ResultSet([[12000, 'adim'], [13000, 'syt'], [14000, 'nico']], @@ -112,11 +112,11 @@ rs.vreg = self.env.vreg def test_filter(entity): return entity.login != 'nico' - + rs2 = rs.filtered_rset(test_filter) self.assertEquals(len(rs2), 2) self.assertEquals([login for _, login in rs2], ['adim', 'syt']) - + def test_resultset_transform(self): rs = ResultSet([[12, 'adim'], [13, 'syt'], [14, 'nico']], 'Any U,L where U is CWUser, U login L', @@ -128,20 +128,20 @@ self.assertEquals(len(rs2), 3) self.assertEquals(list(rs2), [['adim'],['syt'],['nico']]) - + def test_resultset_sort(self): rs = ResultSet([[12000, 'adim'], [13000, 'syt'], [14000, 'nico']], 'Any U,L where U is CWUser, U login L', description=[['CWUser', 'String']] * 3) rs.req = self.request() rs.vreg = self.env.vreg - + rs2 = rs.sorted_rset(lambda e:e['login']) self.assertEquals(len(rs2), 3) self.assertEquals([login for _, login in rs2], ['adim', 'nico', 'syt']) # make sure rs is unchanged self.assertEquals([login for _, login in rs], ['adim', 'syt', 'nico']) - + rs2 = rs.sorted_rset(lambda e:e['login'], reverse=True) self.assertEquals(len(rs2), 3) self.assertEquals([login for _, login in rs2], ['syt', 'nico', 'adim']) @@ -165,7 +165,7 @@ description=[['CWUser', 'String', 'String']] * 5) rs.req = self.request() rs.vreg = self.env.vreg - + rsets = rs.split_rset(lambda e:e['login']) self.assertEquals(len(rsets), 3) self.assertEquals([login for _, login,_ in rsets[0]], ['adim', 'adim']) @@ -173,7 +173,7 @@ self.assertEquals([login for _, login,_ in rsets[2]], ['nico', 'nico']) # make sure rs is unchanged self.assertEquals([login for _, login,_ in rs], ['adim', 'adim', 'syt', 'nico', 'nico']) - + rsets = rs.split_rset(lambda e:e['login'], return_dict=True) self.assertEquals(len(rsets), 3) self.assertEquals([login for _, login,_ in rsets['nico']], ['nico', 'nico']) @@ -198,7 +198,7 @@ u'Le carrelage en 42 leçons', u'La tarte tatin en 15 minutes', u"L'épluchage du castor commun"]) - + def test_cached_syntax_tree(self): """make sure syntax tree is cached""" rqlst1 = self.rset.syntax_tree() @@ -216,21 +216,20 @@ e.complete() self.assertEquals(e['firstname'], 'adrien') self.assertEquals(pprelcachedict(e._related_cache), []) - + def test_get_entity_advanced(self): self.add_entity('Bookmark', title=u'zou', path=u'/view') self.execute('SET X bookmarked_by Y WHERE X is Bookmark, Y login "anon"') rset = self.execute('Any X,Y,XT,YN WHERE X bookmarked_by Y, X title XT, Y login YN') - + e = rset.get_entity(0, 0) self.assertEquals(e.row, 0) self.assertEquals(e.col, 0) self.assertEquals(e['title'], 'zou') self.assertRaises(KeyError, e.__getitem__, 'path') - with traced_selection(): - self.assertEquals(e.view('text'), 'zou') + self.assertEquals(e.view('text'), 'zou') self.assertEquals(pprelcachedict(e._related_cache), []) - + e = rset.get_entity(0, 1) self.assertEquals(e.row, 0) self.assertEquals(e.col, 1) @@ -243,7 +242,7 @@ self.assertEquals(e.view('text'), 'anon') self.assertEquals(pprelcachedict(e._related_cache), []) - + self.assertRaises(NotAnEntity, rset.get_entity, 0, 2) self.assertRaises(NotAnEntity, rset.get_entity, 0, 3) @@ -274,7 +273,7 @@ self.assertEquals(s['name'], 'activated') self.assertRaises(KeyError, s.__getitem__, 'description') - + def test_get_entity_cache_with_left_outer_join(self): eid = self.execute('INSERT CWUser E: E login "joe", E upassword "joe", E in_group G ' 'WHERE G name "users"')[0][0] @@ -287,7 +286,7 @@ cached = e.related_cache('primary_email', 'subject', False) self.assertIsInstance(cached, ResultSet) self.assertEquals(cached.rowcount, 0) - + def test_get_entity_union(self): e = self.add_entity('Bookmark', title=u'manger', path=u'path') @@ -303,14 +302,14 @@ self.assertEquals(entity.id, etype) attr = etype == 'Bookmark' and 'title' or 'name' self.assertEquals(entity[attr], n) - + def test_related_entity_optional(self): e = self.add_entity('Bookmark', title=u'aaaa', path=u'path') rset = self.execute('Any B,U,L WHERE B bookmarked_by U?, U login L') entity, rtype = rset.related_entity(0, 2) self.assertEquals(entity, None) self.assertEquals(rtype, None) - + def test_related_entity_union_subquery(self): e = self.add_entity('Bookmark', title=u'aaaa', path=u'path') rset = self.execute('Any X,N ORDERBY N WITH X,N BEING ' @@ -330,7 +329,7 @@ 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') # make sure we have at least one element @@ -340,7 +339,7 @@ self.assertEquals(set(e.e_schema.type for e in rset.entities(1)), set(['CWGroup',])) - def test_printable_rql(self): + def test_printable_rql(self): rset = self.execute(u'CWEType X WHERE X final FALSE, X meta FALSE') self.assertEquals(rset.printable_rql(), 'Any X WHERE X final FALSE, X meta FALSE, X is CWEType') @@ -351,7 +350,7 @@ self.assertEquals(rset.searched_text(), 'foobar') rset = self.execute(u'Any X WHERE X has_text %(text)s', {'text' : 'foo'}) self.assertEquals(rset.searched_text(), 'foo') - - + + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d test/unittest_rtags.py --- a/test/unittest_rtags.py Thu May 07 16:33:22 2009 +0200 +++ b/test/unittest_rtags.py Thu May 07 16:42:34 2009 +0200 @@ -1,19 +1,19 @@ from logilab.common.testlib import TestCase, unittest_main -from cubicweb.rtags import RelationTags +from cubicweb.rtags import RelationTags, RelationTagsSet class RelationTagsTC(TestCase): - + def test_rtags_expansion(self): rtags = RelationTags() - rtags.set_rtag('primary', 'travaille', 'subject', 'Societe') - rtags.set_rtag('secondary', 'evaluee', 'subject') - rtags.set_rtag('generated', 'tags', 'object') - self.assertEquals(rtags.rtag('evaluee', 'subject', 'Note'), 'secondary') - self.assertEquals(rtags.rtag('travaille', 'subject', 'Societe'), 'primary') - self.assertEquals(rtags.rtag('travaille', 'subject', 'Note'), None) - self.assertEquals(rtags.rtag('tags', 'subject', 'Note'), None) - self.assertEquals(rtags.rtag('tags', 'object', 'Note'), 'generated') - + rtags.tag_relation('primary', ('Societe', 'travaille', '*'), 'subject', ) + rtags.tag_relation('secondary', ('*', 'evaluee', '*'), 'subject') + rtags.tag_relation('generated', ('*', 'tags', '*'), 'object') + self.assertEquals(rtags.get('evaluee', 'subject', 'Note'), 'secondary') + self.assertEquals(rtags.get('travaille', 'subject', 'Societe'), 'primary') + self.assertEquals(rtags.get('travaille', 'subject', 'Note'), None) + self.assertEquals(rtags.get('tags', 'subject', 'Note'), None) + self.assertEquals(rtags.get('tags', 'object', 'Note'), 'generated') + # self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('secondary', 'link'))) # self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False) # self.assertEquals(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link'))) @@ -30,7 +30,16 @@ # self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link'))) # self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True) # self.assertEquals(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link'))) -# self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False) +# self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False) + + + def test_rtagset_expansion(self): + rtags = RelationTagsSet() + rtags.tag_relation('primary', ('Societe', 'travaille', '*'), 'subject', ) + rtags.tag_relation('secondary', ('*', 'travaille', '*'), 'subject') + self.assertEquals(rtags.get('travaille', 'subject', 'Societe'), set(('primary', 'secondary'))) + self.assertEquals(rtags.get('travaille', 'subject', 'Note'), set(('secondary',))) + self.assertEquals(rtags.get('tags', 'subject', 'Note'), set()) if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d test/unittest_selectors.py --- a/test/unittest_selectors.py Thu May 07 16:33:22 2009 +0200 +++ b/test/unittest_selectors.py Thu May 07 16:42:34 2009 +0200 @@ -9,8 +9,9 @@ from cubicweb.devtools.testlib import EnvBasedTC from cubicweb.vregistry import Selector, AndSelector, OrSelector -from cubicweb.selectors import implements +from cubicweb.selectors import implements, match_user_groups from cubicweb.interfaces import IDownloadable +from cubicweb.web import action class _1_(Selector): def __call__(self, *args, **kwargs): @@ -100,6 +101,36 @@ cls = self.vreg.etype_class('Personne') self.failIf(implements('Societe').score_class(cls, self.request())) + +class MatchUserGroupsTC(EnvBasedTC): + def test_owners_group(self): + """tests usage of 'owners' group with match_user_group""" + class SomeAction(action.Action): + id = 'yo' + category = 'foo' + __select__ = match_user_groups('owners') + self.vreg._loadedmods[__name__] = {} + self.vreg.register_vobject_class(SomeAction) + self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) + try: + # login as a simple user + self.create_user('john') + self.login('john') + # it should not be possible to use SomeAction not owned objects + rset, req = self.env.get_rset_and_req('Any G WHERE G is CWGroup, G name "managers"') + self.failIf('yo' in dict(self.pactions(req, rset))) + # insert a new card, and check that we can use SomeAction on our object + self.execute('INSERT Card C: C title "zoubidou"') + self.commit() + rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"') + self.failUnless('yo' in dict(self.pactions(req, rset)), self.pactions(req, rset)) + # make sure even managers can't use the action + self.restore_connection() + rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"') + self.failIf('yo' in dict(self.pactions(req, rset))) + finally: + del self.vreg[SomeAction.__registry__][SomeAction.id] + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d test/unittest_vregistry.py --- a/test/unittest_vregistry.py Thu May 07 16:33:22 2009 +0200 +++ b/test/unittest_vregistry.py Thu May 07 16:42:34 2009 +0200 @@ -6,15 +6,16 @@ from cubicweb.vregistry import VObject from cubicweb.cwvreg import CubicWebRegistry, UnknownProperty from cubicweb.devtools import TestServerConfiguration -from cubicweb.entities.lib import Card from cubicweb.interfaces import IMileStone +from cubes.card.entities import Card + class YesSchema: def __contains__(self, something): return True WEBVIEWSDIR = join(BASE, 'web', 'views') - + class VRegistryTC(TestCase): def setUp(self): @@ -22,24 +23,26 @@ self.vreg = CubicWebRegistry(config) config.bootstrap_cubes() self.vreg.schema = config.load_schema() - + def test_load(self): self.vreg.init_registration([WEBVIEWSDIR]) - self.vreg.load_file(join(WEBVIEWSDIR, 'euser.py'), 'cubicweb.web.views.euser') + self.vreg.load_file(join(WEBVIEWSDIR, 'cwuser.py'), 'cubicweb.web.views.cwuser') self.vreg.load_file(join(WEBVIEWSDIR, 'baseviews.py'), 'cubicweb.web.views.baseviews') fpvc = [v for v in self.vreg.registry_objects('views', 'primary') - if v.__module__ == 'cubicweb.web.views.euser'][0] + if v.__module__ == 'cubicweb.web.views.cwuser'][0] fpv = fpvc(None, None) # don't want a TypeError due to super call self.assertRaises(AttributeError, fpv.render_entity_attributes, None, None) def test_load_interface_based_vojects(self): self.vreg.init_registration([WEBVIEWSDIR]) + self.vreg.load_file(join(BASE, 'entities', '__init__.py'), 'cubicweb.entities.__init__') self.vreg.load_file(join(WEBVIEWSDIR, 'idownloadable.py'), 'cubicweb.web.views.idownloadable') - self.vreg.load_file(join(WEBVIEWSDIR, 'baseviews.py'), 'cubicweb.web.views.baseviews') - # check loading baseviews after idownloadable isn't kicking interface based views + self.vreg.load_file(join(WEBVIEWSDIR, 'primary.py'), 'cubicweb.web.views.primary') self.assertEquals(len(self.vreg['views']['primary']), 2) - + self.vreg.initialization_completed() + self.assertEquals(len(self.vreg['views']['primary']), 1) + def test___selectors__compat(self): myselector1 = lambda *args: 1 myselector2 = lambda *args: 1 @@ -63,10 +66,11 @@ self.vreg.reset() self.vreg._loadedmods[__name__] = {} self.vreg.register_vobject_class(MyCard) - self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')]) + self.vreg.register_objects([join(BASE, 'entities', '__init__.py'), + join(BASE, 'web', 'views', 'iprogress.py')]) # check progressbar isn't kicked self.assertEquals(len(self.vreg['views']['progressbar']), 1) - + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d utils.py --- a/utils.py Thu May 07 16:33:22 2009 +0200 +++ b/utils.py Thu May 07 16:42:34 2009 +0200 @@ -11,10 +11,10 @@ from datetime import datetime, timedelta, date from time import time from random import randint, seed - +from calendar import monthrange + # initialize random seed from current time seed() - try: strptime = datetime.strptime except AttributeError: # py < 2.5 @@ -26,29 +26,70 @@ """return a date from a date (leaving unchanged) or a datetime""" if isinstance(somedate, datetime): return date(somedate.year, somedate.month, somedate.day) - assert isinstance(somedate, date) - return date + assert isinstance(somedate, date), repr(somedate) + return somedate + +def todatetime(somedate): + """return a date from a date (leaving unchanged) or a datetime""" + if isinstance(somedate, date): + return datetime(somedate.year, somedate.month, somedate.day) + assert isinstance(somedate, datetime), repr(somedate) + return somedate + +ONEDAY = timedelta(days=1) +ONEWEEK = timedelta(days=7) + +def days_in_month(date_): + return monthrange(date_.year, date_.month)[1] -def date_range(begin, end, incr=1, include=None): +def previous_month(date_, nbmonth=1): + while nbmonth: + date_ = first_day(date_) - ONEDAY + nbmonth -= 1 + return date_ + +def next_month(date_, nbmonth=1): + while nbmonth: + date_ = last_day(date_) + ONEDAY + nbmonth -= 1 + return date_ + +def first_day(date_): + return date(date_.year, date_.month, 1) + +def last_day(date_): + return date(date_.year, date_.month, days_in_month(date_)) + +def date_range(begin, end, incday=None, incmonth=None): """yields each date between begin and end :param begin: the start date :param end: the end date :param incr: the step to use to iterate over dates. Default is - one day. + one day. :param include: None (means no exclusion) or a function taking a date as parameter, and returning True if the date should be included. """ - incr = timedelta(incr, 0, 0) - while begin <= end: - if include is None or include(begin): + assert not (incday and incmonth) + begin = todate(begin) + end = todate(end) + if incmonth: + while begin < end: + begin = next_month(begin, incmonth) yield begin - begin += incr + else: + if not incday: + incr = ONEDAY + else: + incr = timedelta(incday) + while begin <= end: + yield begin + begin += incr def ustrftime(date, fmt='%Y-%m-%d'): """like strftime, but returns a unicode string instead of an encoded - string which may be problematic with localized date. - + string which' may be problematic with localized date. + encoding is guessed by locale.getpreferredencoding() """ # date format may depend on the locale @@ -79,7 +120,7 @@ dict1 = dict(dict1) dict1.update(dict2) return dict1 - + class SizeConstrainedList(list): """simple list that makes sure the list does not get bigger @@ -120,12 +161,12 @@ def __nonzero__(self): return True - + def write(self, value): assert isinstance(value, unicode), u"unicode required not %s : %s"\ % (type(value).__name__, repr(value)) self.append(value) - + def getvalue(self): return u''.join(self) @@ -164,8 +205,8 @@ self.add_post_inline_script(u"""jQuery(document).ready(function () { %s });""" % jscode) - - + + def add_js(self, jsfile): """adds `jsfile` to the list of javascripts used in the webpage @@ -231,18 +272,18 @@ if skiphead: return header return u'\n%s\n' % header - + class HTMLStream(object): """represents a HTML page. This is used my main templates so that HTML headers can be added at any time during the page generation. - + HTMLStream uses the (U)StringIO interface to be compliant with existing code. """ - + def __init__(self, req): # stream for self.head = req.html_headers diff -r a721966779be -r cba9f175da2d view.py --- a/view.py Thu May 07 16:33:22 2009 +0200 +++ b/view.py Thu May 07 16:42:34 2009 +0200 @@ -480,10 +480,7 @@ """base class for components""" __registry__ = 'components' __select__ = yes() - property_defs = { - _('visible'): dict(type='Boolean', default=True, - help=_('display the component or not')), - } + property_defs = {} def div_class(self): return '%s %s' % (self.propval('htmlclass'), self.id) diff -r a721966779be -r cba9f175da2d vregistry.py diff -r a721966779be -r cba9f175da2d web/__init__.py --- a/web/__init__.py Thu May 07 16:33:22 2009 +0200 +++ b/web/__init__.py Thu May 07 16:42:34 2009 +0200 @@ -7,10 +7,15 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" +_ = unicode -from cubicweb.web._exceptions import * +from decimal import Decimal +from datetime import datetime, date, timedelta +from simplejson import dumps -_ = unicode +from cubicweb.common.uilib import urlquote +from cubicweb.web._exceptions import * + INTERNAL_FIELD_VALUE = '__cubicweb_internal_field__' @@ -37,3 +42,44 @@ # XXX deprecated FACETTES = set() + + + +def json_dumps(value): + if isinstance(value, Decimal): + value = float(value) + elif isinstance(value, (date, datetime)): + value = value.strftime('%Y-%m-%d %H:%M') + elif isinstance(value, timedelta): + value = (value.days * 24*60*60) + value.seconds + try: + return dumps(value) + except TypeError: + return dumps(repr(value)) + +def jsonize(function): + def newfunc(*args, **kwargs): + return json_dumps(function(*args, **kwargs)) + return newfunc + +def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams): + """builds a replacePageChunk-like url + >>> ajax_replace_url('foo', 'Person P') + "javascript: replacePageChunk('foo', 'Person%20P');" + >>> ajax_replace_url('foo', 'Person P', 'oneline') + "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');" + >>> ajax_replace_url('foo', 'Person P', 'oneline', name='bar', age=12) + "javascript: replacePageChunk('foo', 'Person%20P', 'oneline', {'age':12, 'name':'bar'});" + >>> ajax_replace_url('foo', 'Person P', name='bar', age=12) + "javascript: replacePageChunk('foo', 'Person%20P', 'null', {'age':12, 'name':'bar'});" + """ + params = [repr(nodeid), repr(urlquote(rql))] + if extraparams and not vid: + params.append("'null'") + elif vid: + params.append(repr(vid)) + if extraparams: + params.append(json_dumps(extraparams)) + if swap: + params.append('true') + return "javascript: replacePageChunk(%s);" % ', '.join(params) diff -r a721966779be -r cba9f175da2d web/action.py --- a/web/action.py Thu May 07 16:33:22 2009 +0200 +++ b/web/action.py Thu May 07 16:42:34 2009 +0200 @@ -20,7 +20,7 @@ request search state. """ __registry__ = 'actions' - __select__ = yes() + __select__ = match_search_state('normal') property_defs = { 'visible': dict(type='Boolean', default=True, diff -r a721966779be -r cba9f175da2d web/box.py --- a/web/box.py Thu May 07 16:33:22 2009 +0200 +++ b/web/box.py Thu May 07 16:42:34 2009 +0200 @@ -166,14 +166,15 @@ class attributes. """ - def cell_call(self, row, col, view=None): + def cell_call(self, row, col, view=None, **kwargs): self.req.add_js('cubicweb.ajax.js') entity = self.entity(row, col) box = SideBoxWidget(display_name(self.req, self.rtype), self.id) count = self.w_related(box, entity) if count: box.append(BoxSeparator()) - self.w_unrelated(box, entity) + if not self.w_unrelated(box, entity): + del box.items[-1] # remove useless separator box.render(self.w) def div_id(self): @@ -201,8 +202,11 @@ def w_unrelated(self, box, entity): """appends unrelated entities to the `box`""" rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype + i = 0 for etarget in self.unrelated_entities(entity): box.append(self.box_item(entity, etarget, rql, u'+')) + i += 1 + return i def unrelated_entities(self, entity): """returns the list of unrelated entities @@ -215,7 +219,10 @@ return entity.unrelated(self.rtype, self.etype, get_role(self)).entities() # in other cases, use vocabulary functions entities = [] - for _, eid in entity.vocabulary(self.rtype, get_role(self)): + form = self.vreg.select_object('forms', 'edition', self.req, self.rset, + row=self.row or 0) + field = form.field_by_name(self.rtype, get_role(self), entity.e_schema) + for _, eid in form.form_field_vocabulary(field): if eid is not None: rset = self.req.eid_rset(eid) entities.append(rset.get_entity(0, 0)) diff -r a721966779be -r cba9f175da2d web/component.py --- a/web/component.py Thu May 07 16:33:22 2009 +0200 +++ b/web/component.py Thu May 07 16:42:34 2009 +0200 @@ -51,7 +51,7 @@ context = 'navcontentbottom' # 'footer' | 'header' | 'incontext' def call(self, view=None): - return self.cell_call(0, 0, view) + return self.cell_call(0, 0, view=view) def cell_call(self, row, col, view=None): raise NotImplementedError() @@ -62,6 +62,11 @@ id = 'navigation' __select__ = paginated_rset() + property_defs = { + _('visible'): dict(type='Boolean', default=True, + help=_('display the component or not')), + } + page_size_property = 'navigation.page-size' start_param = '__start' stop_param = '__stop' diff -r a721966779be -r cba9f175da2d web/controller.py --- a/web/controller.py Thu May 07 16:33:22 2009 +0200 +++ b/web/controller.py Thu May 07 16:42:34 2009 +0200 @@ -10,7 +10,7 @@ import datetime from cubicweb import typed_eid -from cubicweb.utils import strptime +from cubicweb.utils import strptime, todate, todatetime from cubicweb.selectors import yes, require_group_compat from cubicweb.appobject import AppObject from cubicweb.web import LOGGER, Redirect, RequestError @@ -116,7 +116,7 @@ if etype == 'Datetime': format = self.req.property_value('ui.datetime-format') try: - return strptime(value, format) + return todatetime(strptime(value, format)) except: pass elif etype == 'Time': @@ -124,13 +124,15 @@ try: # (adim) I can't find a way to parse a Time with a custom format date = strptime(value, format) # this returns a DateTime - return datetime.timedelta(0, date.hour *60*60 + date.minute*60 + date.second, 0) + return datetime.time(date.hour, date.minute, date.second) except: raise ValueError('can\'t parse %r (expected %s)' % (value, format)) try: format = self.req.property_value('ui.date-format') dt = strptime(value, format) - return datetime.date(dt.year, dt.month, dt.day) + if etype == 'Datetime': + return todatetime(dt) + return todate(dt) except: raise ValueError('can\'t parse %r (expected %s)' % (value, format)) diff -r a721966779be -r cba9f175da2d web/data/cubicweb.acl.css --- a/web/data/cubicweb.acl.css Thu May 07 16:33:22 2009 +0200 +++ b/web/data/cubicweb.acl.css Thu May 07 16:42:34 2009 +0200 @@ -9,19 +9,90 @@ /* security edition form (views/management.py) */ /******************************************************************************/ +h2.schema{ + background : #ff7700; + color: #fff; + font-weight: bold; + padding : 0.1em 0.3em; +} + + +h3.schema{ + font-weight: bold; +} + +h4 a, +h4 a:link, +h4 a:visited{ + color:#000; + } + table.schemaInfo { - margin: 1ex 1em; + margin: 1em 0em; text-align: left; border: 1px solid black; border-collapse: collapse; + width:100%; } table.schemaInfo th, table.schemaInfo td { - padding: 0em 1em; - border: 1px solid black; + padding: .3em .5em; + border: 1px solid grey; + width:33%; +} + + +table.schemaInfo tr th { + padding: 0.2em 0px 0.2em 5px; + background-image:none; + background-color:#dfdfdf; +} + +table.schemaInfo thead tr { + border: 1px solid #dfdfdf; +} + +table.schemaInfo td { + padding: 3px 10px 3px 5px; + } +.users{ + color : #00CC33; + font-weight: bold } + +.guests{ + color : #ff7700; + font-weight: bold; +} + +.staff{ + color : #0083ab; + font-weight: bold; +} + +.owners{ + color : #8b0000; + font-weight: bold; +} + +.discret, +a.grey{ + color:#666; +} + +a.grey:hover{ + color:#000; +} + +.red{ + color : #ff7700; + } + +div#schema_security{ + width:780px; + } /******************************************************************************/ /* user groups edition form (views/euser.py) */ /******************************************************************************/ diff -r a721966779be -r cba9f175da2d web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Thu May 07 16:33:22 2009 +0200 +++ b/web/data/cubicweb.ajax.js Thu May 07 16:42:34 2009 +0200 @@ -9,17 +9,37 @@ var JSON_BASE_URL = baseuri() + 'json?'; +function _loadAjaxHtmlHead(node, head, tag, srcattr) { + var loaded = []; + jQuery('head ' + tag).each(function(i) { + loaded.push(this.getAttribute(srcattr)); + }); + node.find(tag).each(function(i) { + if (!loaded.contains(this.getAttribute(srcattr))) { + jQuery(this).appendTo(head); + } + }); + node.find(tag).remove(); +} + /* * inspect dom response, search for a
node and * put its content into the real document's head. * This enables dynamic css and js loading and is used by replacePageChunk */ function loadAjaxHtmlHead(node) { - jQuery(node).find('div.ajaxHtmlHead').appendTo(jQuery('head')); + var head = jQuery('head'); + node = jQuery(node).find('div.ajaxHtmlHead'); + _loadAjaxHtmlHead(node, head, 'script', 'src'); + _loadAjaxHtmlHead(node, head, 'link', 'href'); + node.find('*').appendTo(head); +} + +function preprocessAjaxLoad(node, newdomnode) { + loadAjaxHtmlHead(newdomnode); } function postAjaxLoad(node) { - loadAjaxHtmlHead(node); // find sortable tables if there are some if (typeof(Sortable) != 'undefined') { Sortable.sortTables(node); @@ -37,6 +57,7 @@ if (typeof roundedCornersOnLoad != 'undefined') { roundedCornersOnLoad(); } + loadDynamicFragments(node); jQuery(CubicWeb).trigger('ajax-loaded'); } @@ -60,6 +81,7 @@ } ajax(url, data, function(response) { var domnode = getDomFromResponse(response); + preprocessAjaxLoad(node, domnode); if (mode == 'swap') { var origId = node.id; node = swapDOM(node, domnode); @@ -83,8 +105,12 @@ /* finds each dynamic fragment in the page and executes the * the associated RQL to build them (Async call) */ -function loadDynamicFragments() { - var fragments = jQuery('div.dynamicFragment'); +function loadDynamicFragments(node) { + if (node) { + var fragments = jQuery(node).find('div.dynamicFragment'); + } else { + var fragments = jQuery('div.dynamicFragment'); + } if (fragments.length == 0) { return; } @@ -106,7 +132,7 @@ } } -jQuery(document).ready(loadDynamicFragments); +jQuery(document).ready(function() {loadDynamicFragments();}); //============= base AJAX functions to make remote calls =====================// @@ -118,18 +144,6 @@ } } -/* - * This function is the equivalent of MochiKit's loadJSONDoc but - * uses POST instead of GET - */ -function loadJSONDocUsingPOST(url, data) { - setProgressCursor(); - var deferred = loadJSON(url, data, 'POST'); - deferred = deferred.addErrback(remoteCallFailed); - deferred = deferred.addCallback(resetCursor); - return deferred; -} - /* * This function will call **synchronously** a remote method on the cubicweb server @@ -161,10 +175,16 @@ * * It looks at http headers to guess the response type. */ + function asyncRemoteExec(fname /* ... */) { + setProgressCursor(); var props = {'fname' : fname, 'pageid' : pageid, 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; - return loadJSONDocUsingPOST(JSON_BASE_URL, props); + var deferred = loadRemote(JSON_BASE_URL, props, 'POST'); + deferred = deferred.addErrback(remoteCallFailed); + deferred = deferred.addErrback(resetCursor); + deferred = deferred.addCallback(resetCursor); + return deferred; } diff -r a721966779be -r cba9f175da2d web/data/cubicweb.compat.js --- a/web/data/cubicweb.compat.js Thu May 07 16:33:22 2009 +0200 +++ b/web/data/cubicweb.compat.js Thu May 07 16:42:34 2009 +0200 @@ -365,11 +365,16 @@ }; -function loadJSON(url, data, type) { +/* + * Asynchronously load an url and return a deferred + * whose callbacks args are decoded according to + * the Content-Type response header + */ +function loadRemote(url, data, reqtype) { var d = new Deferred(); jQuery.ajax({ url: url, - type: type, + type: reqtype, data: data, beforeSend: function(xhr) { @@ -377,6 +382,9 @@ }, success: function(data, status) { + if (d.req.getResponseHeader("content-type") == 'application/json') { + data = evalJSON(data); + } d.success(data); }, @@ -506,23 +514,7 @@ var KEYS = { KEY_ESC: 27, KEY_ENTER: 13 -} +}; -// XHR = null; -// function test() { -// var d = loadJSON('http://crater:9876/json?mode=remote&fname=i18n&pageid=xxx&arg=' + jQuery.toJSON(['modify'])); -// d = d.addCallback(function (result, xhr) { -// XHR = xhr; -// log('got ajax result 1' + result + xhr); -// log('got ajax result 1' + xhr); -// log('got ajax result 1' + xhr + 'arguments =', arguments.length); -// }); -// d.addCallback(function (x, req, y, z) { -// log('callback 2 x =' + x, ' req=', req, 'y =', y, 'z=',z); -// }, 12, 13) -// d.addErrback(function (error, xhr) { -// XHR = xhr; -// log('got err', error, ' code =', xhr.status, 'arguments length=', arguments.length); -// }) -// } + diff -r a721966779be -r cba9f175da2d web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Thu May 07 16:33:22 2009 +0200 +++ b/web/data/cubicweb.edition.js Thu May 07 16:42:34 2009 +0200 @@ -166,12 +166,14 @@ if (comboId) { // re-insert option in combobox if it was taken from there var selectNode = getNode(comboId); + // XXX what on object relation if (selectNode){ var options = selectNode.options; var node_id = elementId.substring(0, elementId.indexOf(':')); options[options.length] = OPTION({'id' : elementId, 'value' : node_id}, entityView); } } + elementId = elementId.substring(2, elementId.length); remoteExec('remove_pending_insert', elementId.split(':')); } diff -r a721966779be -r cba9f175da2d web/data/cubicweb.preferences.css --- a/web/data/cubicweb.preferences.css Thu May 07 16:33:22 2009 +0200 +++ b/web/data/cubicweb.preferences.css Thu May 07 16:42:34 2009 +0200 @@ -5,12 +5,46 @@ * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */ -.componentTitle{ + +table.preferences td{ + padding: 0 0.5em 1em; + } + +fieldset.preferences{ + border : 1px solid #CFCEB7; + margin:1em 0; + padding:0 1em 1em; +} + +div.preffield { + margin-bottom: 0.8em ; +} + +/* +div.preffield label{ + font-size:110% + } +*/ + +div.prefinput{ + margin:.3em 0; +} + +div.componentLink{ + margin-top:0.3em; + } + +a.componentTitle{ font-weight:bold; - color: #ff7700; - padding:0px 4px; + color: #000; + } + +a.componentTitle:visited{ + color: #000; } + + h2.propertiesform a{ display:block; margin: 10px 0px 6px 0px; @@ -26,3 +60,56 @@ background-color:#cfceb7; text-decoration:none; } + +div.prefinput select.changed, +div.prefinput input.changed{ + background:#eeedd9; + border: 1px solid #eeedd9; +} + +div.prefinput select, +div.prefinput input{ + background:#fff; + border: 1px solid #CFCEB7; +} + +.prefinput input.error { + background:transparent url(error.png) no-repeat scroll 100% 50% !important; +} + + +div.formsg{ + font-weight:bold; + margin:0.5em 0px; + } + + +div.formsg .critical{ + color:red; + padding-left:20px; + background:#fff url(critical.png) no-repeat; + } + +div.formsg .message{ + color : green; +} + +.helper{ + font-size: 96%; + color: #555544; + padding:0; +} + +div.prefinput .helper:hover { + color: #000; + cursor: default; +} + +.error{ + color:red; + padding-right:1em; + } + +div.openlink{ + display:inline; + } \ No newline at end of file diff -r a721966779be -r cba9f175da2d web/data/cubicweb.preferences.js --- a/web/data/cubicweb.preferences.js Thu May 07 16:33:22 2009 +0200 +++ b/web/data/cubicweb.preferences.js Thu May 07 16:42:34 2009 +0200 @@ -3,8 +3,176 @@ * XXX whenever used outside of preferences, don't forget to * move me in a more appropriate place */ -function toggle_and_remember_visibility(elemId, cookiename) { + +function toggleVisibility(elemId, cookiename) { + _clearPreviousMessages(); jqNode(elemId).toggleClass('hidden'); asyncRemoteExec('set_cookie', cookiename, jQuery('#' + elemId).attr('class')); } + +function closeFieldset(fieldsetid){ + var linklabel = _('open all'); + var linkhref = 'javascript:openFieldset("' +fieldsetid + '")' + _toggleFieldset(fieldsetid, 1, linklabel, linkhref) +} + +function openFieldset(fieldsetid){ + var linklabel = _('close all'); + var linkhref = 'javascript:closeFieldset("'+ fieldsetid + '")' + _toggleFieldset(fieldsetid, 0, linklabel, linkhref) +} + + +function _toggleFieldset(fieldsetid, closeaction, linklabel, linkhref){ + jQuery('#'+fieldsetid).find('div.openlink').each(function(){ + var link = A({'href' : "javascript:noop();", + 'onclick' : linkhref}, + linklabel) + jQuery(this).empty().append(link); + }); + jQuery('#'+fieldsetid).find('fieldset[id]').each(function(){ + var fieldset = jQuery(this); + if(closeaction){ + fieldset.addClass('hidden') + }else{ + fieldset.removeClass('hidden'); + linkLabel = (_('open all')); + } + }); +} + +function validatePrefsForm(formid){ + var form = getNode(formid); + freezeFormButtons(formid); + try { + var d = _sendForm(formid, null); + } catch (ex) { + log('got exception', ex); + return false; + } + function _callback(result, req) { + _clearPreviousMessages(); + _clearPreviousErrors(formid); + // success + if(result[0]){ + return submitSucces(formid) + } + // Failures + unfreezeFormButtons(formid); + var descr = result[1]; + if (!isArrayLike(descr) || descr.length != 2) { + log('got strange error :', descr); + updateMessage(descr); + return ; + } + _displayValidationerrors(formid, descr[0], descr[1]); + var dom = DIV({'class':'critical'}, + _("please correct errors below")); + jQuery(form).find('div.formsg').empty().append(dom); + updateMessage(_("")); + return false; + } + d.addCallback(_callback); + return false; +} + +function submitSucces(formid){ + var form = jQuery('#'+formid); + setCurrentValues(form); + var dom = DIV({'class':'message'}, + _("changes applied")); + jQuery(form).find('div.formsg').empty().append(dom); + jQuery(form).find('input').removeClass('changed'); + checkValues(form, true); + return; +} + +function _clearPreviousMessages() { + jQuery('div#appMsg').addClass('hidden'); + jQuery('div.formsg').empty(); +} + +function _clearPreviousErrors(formid) { + jQuery('#' + formid + ' span.error').remove(); +} + + +function checkValues(form, success){ + var unfreezeButtons = false; + jQuery(form).find('select').each(function () { + unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons); + }); + jQuery(form).find('[type=text]').each(function () { + unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons); + }); + jQuery(form).find('input[type=radio]').each(function () { + if (jQuery(this).attr('checked')){ + unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons); + } + }); + + if (unfreezeButtons){ + unfreezeFormButtons(form.attr('id')); + }else{ + if (!success){ + _clearPreviousMessages(); + } + _clearPreviousErrors(form.attr('id')); + freezeFormButtons(form.attr('id')); + } +} + +function _checkValue(input, unfreezeButtons){ + var currentValueInput = jQuery("input[id=current-" + input.attr('name') + "]"); + if (currentValueInput.attr('value') != input.attr('value')){ + input.addClass('changed'); + unfreezeButtons = true; + }else{ + input.removeClass('changed'); + jQuery("span[id=err-" + input.attr('id') + "]").remove(); + } + input.removeClass('error'); + return unfreezeButtons +} + + +function setCurrentValues(form){ + jQuery(form).find('input[id^=current-value]').each(function () { + var currentValueInput = jQuery(this); + var name = currentValueInput.attr('id').split('-')[1]; + jQuery(form).find("[name=" + name + "]").each(function (){ + var input = jQuery(this); + if(input.attr('type')=='radio'){ + if(input.attr('checked')){ + log(input.attr('value')); + currentValueInput.attr('value', input.attr('value')); + } + }else{ + currentValueInput.attr('value', input.attr('value')); + } + }); + }); +} + + +function initEvents(){ + jQuery('form').each(function() { + var form = jQuery(this); + freezeFormButtons(form.attr('id')); + form.find('input[type=text]').keyup(function(){ + checkValues(form); + }); + form.find('input[type=radio]').change(function(){ + checkValues(form); + }); + form.find('select').change(function(){ + checkValues(form); + }); + }); +} + +$(document).ready(function() { + initEvents(); +}); + diff -r a721966779be -r cba9f175da2d web/data/external_resources --- a/web/data/external_resources Thu May 07 16:33:22 2009 +0200 +++ b/web/data/external_resources Thu May 07 16:42:34 2009 +0200 @@ -52,3 +52,4 @@ DOWNLOAD_ICON = DATADIR/download.gif UPLOAD_ICON = DATADIR/upload.gif GMARKER_ICON = DATADIR/gmap_blue_marker.png +UP_ICON = DATADIR/up.gif diff -r a721966779be -r cba9f175da2d web/data/up.gif Binary file web/data/up.gif has changed diff -r a721966779be -r cba9f175da2d web/form.py --- a/web/form.py Thu May 07 16:33:22 2009 +0200 +++ b/web/form.py Thu May 07 16:42:34 2009 +0200 @@ -20,7 +20,7 @@ from cubicweb.web.controller import NAV_FORM_PARAMETERS from cubicweb.web.formfields import (Field, StringField, RelationField, HiddenInitialValueField) -from cubicweb.web.formrenderers import FormRenderer +from cubicweb.web import formrenderers from cubicweb.web import formwidgets as fwdgs class FormViewMixIn(object): @@ -30,28 +30,6 @@ http_cache_manager = NoHTTPCacheManager add_to_breadcrumbs = False - def __init__(self, req, rset, **kwargs): - super(FormViewMixIn, self).__init__(req, rset, **kwargs) - # get validation session data which may have been previously set. - # deleting validation errors here breaks form reloading (errors are - # no more available), they have to be deleted by application's publish - # method on successful commit - formurl = req.url() - forminfo = req.get_session_data(formurl) - if forminfo: - req.data['formvalues'] = forminfo['values'] - req.data['formerrors'] = errex = forminfo['errors'] - req.data['displayederrors'] = set() - # if some validation error occured on entity creation, we have to - # get the original variable name from its attributed eid - foreid = errex.entity - for var, eid in forminfo['eidmap'].items(): - if foreid == eid: - errex.eid = var - break - else: - errex.eid = foreid - def html_headers(self): """return a list of html headers (eg something to be inserted between and of the returned page @@ -80,6 +58,40 @@ self.req.set_page_data('rql_varmaker', varmaker) self.varmaker = varmaker + def session_key(self): + """return the key that may be used to store / retreive data about a + previous post which failed because of a validation error + """ + return '%s#%s' % (self.req.url(), self.domid) + + def __init__(self, req, rset, **kwargs): + super(FormMixIn, self).__init__(req, rset, **kwargs) + self.restore_previous_post(self.session_key()) + + def restore_previous_post(self, sessionkey): + # get validation session data which may have been previously set. + # deleting validation errors here breaks form reloading (errors are + # no more available), they have to be deleted by application's publish + # method on successful commit + forminfo = self.req.get_session_data(sessionkey, pop=True) + if forminfo: + # XXX remove req.data assigment once cw.web.widget is killed + self.req.data['formvalues'] = self.form_previous_values = forminfo['values'] + self.req.data['formerrors'] = self.form_valerror = forminfo['errors'] + self.req.data['displayederrors'] = self.form_displayed_errors = set() + # if some validation error occured on entity creation, we have to + # get the original variable name from its attributed eid + foreid = self.form_valerror.entity + for var, eid in forminfo['eidmap'].items(): + if foreid == eid: + self.form_valerror.eid = var + break + else: + self.form_valerror.eid = foreid + else: + self.form_previous_values = {} + self.form_valerror = None + # XXX deprecated with new form system. Should disappear domid = 'entityForm' @@ -88,28 +100,6 @@ http_cache_manager = NoHTTPCacheManager add_to_breadcrumbs = False - def __init__(self, req, rset, **kwargs): - super(FormMixIn, self).__init__(req, rset, **kwargs) - # get validation session data which may have been previously set. - # deleting validation errors here breaks form reloading (errors are - # no more available), they have to be deleted by application's publish - # method on successful commit - formurl = req.url() - forminfo = req.get_session_data(formurl) - if forminfo: - req.data['formvalues'] = forminfo['values'] - req.data['formerrors'] = errex = forminfo['errors'] - req.data['displayederrors'] = set() - # if some validation error occured on entity creation, we have to - # get the original variable name from its attributed eid - foreid = errex.entity - for var, eid in forminfo['eidmap'].items(): - if foreid == eid: - errex.eid = var - break - else: - errex.eid = foreid - def html_headers(self): """return a list of html headers (eg something to be inserted between and of the returned page @@ -180,11 +170,11 @@ This method should be called once inlined field errors has been consumed """ - errex = self.req.data.get('formerrors') + errex = self.req.data.get('formerrors') or self.form_valerror # get extra errors if errex is not None: errormsg = self.req._('please correct the following errors:') - displayed = self.req.data['displayederrors'] + displayed = self.req.data.get('displayederrors') or self.form_displayed_errors errors = sorted((field, err) for field, err in errex.errors.items() if not field in displayed) if errors: @@ -234,6 +224,7 @@ __registry__ = 'forms' __select__ = yes() + renderer_cls = formrenderers.FormRenderer is_subform = False # attributes overrideable through __init__ @@ -263,7 +254,7 @@ assert hasattr(self.__class__, key) and not key[0] == '_', key setattr(self, key, val) if self.set_error_url: - self.form_add_hidden('__errorurl', req.url()) + self.form_add_hidden('__errorurl', self.session_key()) if self.copy_nav_params: for param in NAV_FORM_PARAMETERS: if not param in kwargs: @@ -273,6 +264,8 @@ if submitmsg is not None: self.form_add_hidden('__message', submitmsg) self.context = None + if 'domid' in kwargs:# session key changed + self.restore_previous_post(self.session_key()) @iclassmethod def field_by_name(cls_or_self, name, role='subject'): @@ -331,7 +324,7 @@ """render this form, using the renderer given in args or the default FormRenderer() """ - renderer = values.pop('renderer', FormRenderer()) + renderer = values.pop('renderer', self.renderer_cls()) return renderer.render(self, values) def form_build_context(self, rendervalues=None): @@ -344,9 +337,6 @@ form_render() """ self.context = context = {} - # on validation error, we get a dictionary of previously submitted - # values - self._previous_values = self.req.data.get('formvalues', {}) # ensure rendervalues is a dict if rendervalues is None: rendervalues = {} @@ -371,10 +361,11 @@ values found in 1. and 2. are expected te be already some 'display' value while those found in 3. and 4. are expected to be correctly typed. """ - if field.name in self._previous_values: - value = self._previous_values[field.name] - elif field.name in self.req.form: - value = self.req.form[field.name] + qname = self.form_field_name(field) + if qname in self.form_previous_values: + value = self.form_previous_values[qname] + elif qname in self.req.form: + value = self.req.form[qname] else: if field.name in rendervalues: value = rendervalues[field.name] @@ -395,10 +386,9 @@ def form_field_error(self, field): """return validation error for widget's field, if any""" - errex = self.req.data.get('formerrors') - if errex and self._errex_match_field(errex, field): - self.req.data['displayederrors'].add(field.name) - return u'%s' % errex.errors[field.name] + if self._field_has_error(field): + self.form_displayed_errors.add(field.name) + return u'%s' % self.form_valerror.errors[field.name] return u'' def form_field_format(self, field): @@ -423,16 +413,16 @@ """ raise NotImplementedError - def _errex_match_field(self, errex, field): + def _field_has_error(self, field): """return true if the field has some error in given validation exception """ - return field.name in errex.errors + return self.form_valerror and field.name in self.form_valerror.errors class EntityFieldsForm(FieldsForm): __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity())) - internal_fields = FieldsForm.internal_fields + ('__type', 'eid') + internal_fields = FieldsForm.internal_fields + ('__type', 'eid', '__maineid') domid = 'entityForm' def __init__(self, *args, **kwargs): @@ -440,7 +430,7 @@ msg = kwargs.pop('submitmsg', None) super(EntityFieldsForm, self).__init__(*args, **kwargs) if self.edited_entity is None: - self.edited_entity = self.complete_entity(self.row, self.col) + self.edited_entity = self.complete_entity(self.row or 0, self.col or 0) self.form_add_hidden('__type', eidparam=True) self.form_add_hidden('eid') if msg is not None: @@ -449,11 +439,15 @@ self.form_add_hidden('__linkto', linkto) msg = '%s %s' % (msg, self.req._('and linked')) self.form_add_hidden('__message', msg) + # in case of direct instanciation + self.schema = self.edited_entity.schema + self.vreg = self.edited_entity.vreg - def _errex_match_field(self, errex, field): + def _field_has_error(self, field): """return true if the field has some error in given validation exception """ - return errex.eid == self.edited_entity.eid and field.name in errex.errors + return super(EntityFieldsForm, self)._field_has_error(field) \ + and self.form_valerror.eid == self.edited_entity.eid def _relation_vocabulary(self, rtype, targettype, role, limit=None, done=None): @@ -489,7 +483,7 @@ """overriden to add edit[s|o] hidden fields and to ensure schema fields have eidparam set to True - edit[s|o] hidden fields are used t o indicate the value for the + edit[s|o] hidden fields are used to indicate the value for the associated field before the (potential) modification made when submitting the form. """ @@ -527,9 +521,7 @@ return INTERNAL_FIELD_VALUE if attr == '__type': return entity.id - if field.role == 'object': - attr = 'reverse_' + attr - elif entity.e_schema.subject_relation(attr).is_final(): + if self.schema.rschema(attr).is_final(): attrtype = entity.e_schema.destination(attr) if attrtype == 'Password': return entity.has_eid() and INTERNAL_FIELD_VALUE or '' @@ -540,14 +532,14 @@ # XXX value should reflect if some file is already attached return True return False - if entity.has_eid(): + if entity.has_eid() or attr in entity: value = getattr(entity, attr) else: value = self._form_field_default_value(field, load_bytes) return value # non final relation field - if entity.has_eid(): - value = [ent.eid for ent in getattr(entity, attr)] + if entity.has_eid() or entity.relation_cached(attr, field.role): + value = [r[0] for r in entity.related(attr, field.role)] else: value = self._form_field_default_value(field, load_bytes) return value @@ -608,7 +600,7 @@ """ entity = self.edited_entity if isinstance(rtype, basestring): - rtype = self.schema.rschema(rtype) + rtype = entity.schema.rschema(rtype) done = None assert not rtype.is_final(), rtype if entity.has_eid(): @@ -630,7 +622,7 @@ """ entity = self.edited_entity if isinstance(rtype, basestring): - rtype = self.schema.rschema(rtype) + rtype = entity.schema.rschema(rtype) done = None if entity.has_eid(): done = set(e.eid for e in getattr(entity, 'reverse_%s' % rtype)) diff -r a721966779be -r cba9f175da2d web/formfields.py --- a/web/formfields.py Thu May 07 16:33:22 2009 +0200 +++ b/web/formfields.py Thu May 07 16:42:34 2009 +0200 @@ -94,9 +94,9 @@ Field.__creation_rank += 1 def __unicode__(self): - return u'<%s name=%r label=%r id=%r initial=%r @%x>' % ( + return u'<%s name=%r label=%r id=%r initial=%r visible=%r @%x>' % ( self.__class__.__name__, self.name, self.label, - self.id, self.initial, id(self)) + self.id, self.initial, self.is_visible(), id(self)) def __repr__(self): return self.__unicode__().encode('utf-8') @@ -180,17 +180,16 @@ def __init__(self, max_length=None, **kwargs): super(StringField, self).__init__(**kwargs) self.max_length = max_length + if isinstance(self.widget, TextArea): + self.init_text_area(self.widget) + + def init_text_area(self, widget): + if self.max_length < 513: + widget.attrs.setdefault('cols', 60) + widget.attrs.setdefault('rows', 5) -class TextField(Field): - widget = TextArea - def __init__(self, rows=10, cols=80, **kwargs): - super(TextField, self).__init__(**kwargs) - self.rows = rows - self.cols = cols - - -class RichTextField(TextField): +class RichTextField(StringField): widget = None def __init__(self, format_field=None, **kwargs): super(RichTextField, self).__init__(**kwargs) @@ -200,7 +199,9 @@ if self.widget is None: if self.use_fckeditor(form): return FCKEditor() - return TextArea() + widget = TextArea() + self.init_text_area(widget) + return widget return self.widget def get_format_field(self, form): @@ -220,9 +221,10 @@ else: # else we want a format selector # XXX compute vocabulary - widget = Select + widget = Select() fcstr = FormatConstraint() choices = [(req._(fmt), fmt) for fmt in fcstr.vocabulary(req=req)] + widget.attrs['size'] = 1 field = StringField(name=self.name + '_format', widget=widget, choices=choices) req.data[self] = field @@ -370,7 +372,7 @@ class TimeField(DateField): format_prop = 'ui.datetime-format' - + widget = TextInput class HiddenInitialValueField(Field): def __init__(self, visible_field): @@ -406,36 +408,15 @@ relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()] else: relatedvocab = [] - return res + form.form_field_vocabulary(self) + relatedvocab + vocab = res + form.form_field_vocabulary(self) + relatedvocab + if self.sort: + vocab = sorted(vocab) + return vocab def format_single_value(self, req, value): return value -def stringfield_from_constraints(constraints, **kwargs): - field = None - for cstr in constraints: - if isinstance(cstr, StaticVocabularyConstraint): - kwargs.setdefault('widget', Select()) - return StringField(choices=cstr.vocabulary, **kwargs) - if isinstance(cstr, SizeConstraint) and cstr.max is not None: - if cstr.max > 257: - rows_cols_from_constraint(cstr, kwargs) - field = TextField(**kwargs) - else: - field = StringField(max_length=cstr.max, **kwargs) - return field or TextField(**kwargs) - - -def rows_cols_from_constraint(constraint, kwargs): - if constraint.max < 513: - rows, cols = 5, 60 - else: - rows, cols = 10, 80 - kwargs.setdefault('rows', rows) - kwargs.setdefault('cols', cols) - - def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs): """return the most adapated widget to edit the relation 'subjschema rschema objschema' according to information found in the schema @@ -448,7 +429,9 @@ if rschema.is_final(): if rschema.rproperty(eschema, targetschema, 'internationalizable'): kwargs['internationalizable'] = True - kwargs['initial'] = rschema.rproperty(eschema, targetschema, 'default') + def get_default(form, es=eschema, rs=rschema): + return es.default(rs) + kwargs['initial'] = get_default else: targetschema = rschema.subjects(eschema)[0] card = rschema.rproperty(targetschema, eschema, 'cardinality')[1] @@ -473,12 +456,20 @@ for cstr in constraints: if isinstance(cstr, StaticVocabularyConstraint): raise Exception('rich text field with static vocabulary') - if isinstance(cstr, SizeConstraint) and cstr.max is not None: - rows_cols_from_constraint(cstr, kwargs) return RichTextField(**kwargs) - # return StringField or TextField according to constraints constraints = rschema.rproperty(eschema, targetschema, 'constraints') - return stringfield_from_constraints(constraints, **kwargs) + # init StringField parameters according to constraints + for cstr in constraints: + if isinstance(cstr, StaticVocabularyConstraint): + kwargs.setdefault('widget', Select()) + kwargs.setdefault('choices', cstr.vocabulary) + if card in '?1': + kwargs['widget'].attrs.setdefault('size', 1) + if isinstance(cstr, SizeConstraint) and cstr.max is not None: + if cstr.max > 257: + kwargs.setdefault('widget', TextArea) + kwargs['max_length'] = cstr.max + return StringField(**kwargs) if fieldclass is FileField: for metadata in ('format', 'encoding'): metaschema = eschema.has_metadata(rschema, metadata) diff -r a721966779be -r cba9f175da2d web/formrenderers.py --- a/web/formrenderers.py Thu May 07 16:33:22 2009 +0200 +++ b/web/formrenderers.py Thu May 07 16:42:34 2009 +0200 @@ -15,6 +15,7 @@ from cubicweb.web import eid_param from cubicweb.web import formwidgets as fwdgs from cubicweb.web.widgets import checkbox +from cubicweb.web.formfields import HiddenInitialValueField class FormRenderer(object): @@ -30,11 +31,12 @@ +---------+ """ _options = ('display_fields', 'display_label', 'display_help', - 'display_progress_div', 'button_bar_class') + 'display_progress_div', 'table_class', 'button_bar_class') display_fields = None # None -> all fields display_label = True display_help = True display_progress_div = True + table_class = u'attributeForm' button_bar_class = u'formButtonBar' def __init__(self, **kwargs): @@ -99,11 +101,11 @@ This method should be called once inlined field errors has been consumed """ req = form.req - errex = req.data.get('formerrors') + errex = form.form_valerror # get extra errors if errex is not None: errormsg = req._('please correct the following errors:') - displayed = req.data['displayederrors'] + displayed = form.form_displayed_errors errors = sorted((field, err) for field, err in errex.errors.items() if not field in displayed) if errors: @@ -145,9 +147,12 @@ return tag + '>' def display_field(self, form, field): + if isinstance(field, HiddenInitialValueField): + field = field.visible_field return (self.display_fields is None - or field.name in self.display_fields - or field.name in form.internal_fields) + or field.name in form.internal_fields + or (field.name, field.role) in self.display_fields + or (field.name, field.role) in form.internal_fields) def render_fields(self, w, form, values): form.form_build_context(values) @@ -172,7 +177,7 @@ return fields def _render_fields(self, fields, w, form): - w(u'
%s
   
') + w(u'
' % self.table_class) for field in fields: w(u'') if self.display_label: @@ -251,7 +256,7 @@ def _render_fields(self, fields, w, form): if form.is_subform: entity = form.edited_entity - values = form._previous_values + values = form.form_previous_values qeid = eid_param('eid', entity.eid) cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid)) w(u'' % (entity.row % 2 and u'even' or u'odd')) @@ -269,7 +274,7 @@ elif isinstance(field.widget, fwdgs.Input): field.widget.attrs['onkeypress'] = cbsetstate w(u'
%s
' % field.render(form, self)) - w(u'/') else: # main form, display table headers w(u'') @@ -324,7 +329,7 @@ super(EntityFormRenderer, self).render_buttons(w, form) def relations_form(self, w, form): - srels_by_cat = form.srelations_by_category(('generic', 'metadata'), 'add') + srels_by_cat = form.srelations_by_category('generic', 'add') if not srels_by_cat: return u'' req = form.req diff -r a721966779be -r cba9f175da2d web/formwidgets.py --- a/web/formwidgets.py Thu May 07 16:33:22 2009 +0200 +++ b/web/formwidgets.py Thu May 07 16:42:34 2009 +0200 @@ -9,7 +9,7 @@ from datetime import date from cubicweb.common import tags -from cubicweb.web import stdmsgs +from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs class FieldWidget(object): @@ -137,6 +137,8 @@ def render(self, form, field): name, values, attrs = self._render_attrs(form, field) attrs.setdefault('onkeypress', 'autogrow(this)') + attrs.setdefault('cols', 80) + attrs.setdefault('rows', 20) if not values: value = u'' elif len(values) == 1: @@ -161,11 +163,11 @@ """ + self._test_richtextfield('''''') + +''') + self._test_richtextfield('''' % {'eid': file.eid}) +''' % {'eid': file.eid}) def test_passwordfield(self): @@ -170,12 +171,12 @@ upassword = StringField(widget=PasswordInput) form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity) self.assertTextEquals(self._render_entity_field('upassword', form), - ''' + '''
- +   confirm password''' % {'eid': self.entity.eid}) - + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d web/test/unittest_formfields.py --- a/web/test/unittest_formfields.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_formfields.py Thu May 07 16:42:34 2009 +0200 @@ -2,47 +2,48 @@ from logilab.common.testlib import TestCase, unittest_main from cubicweb.devtools import TestServerConfiguration -from cubicweb.web.formwidgets import PasswordInput +from cubicweb.web.formwidgets import PasswordInput, TextArea from cubicweb.web.formfields import * -from cubicweb.entities.lib import Card +from cubicweb.entities.wfobjs import State from cubicweb.entities.authobjs import CWUser from cubes.file.entities import File config = TestServerConfiguration('data') config.bootstrap_cubes() schema = config.load_schema() -card_schema = schema['Card'] +state_schema = schema['State'] cwuser_schema = schema['CWUser'] -file_schema.schema = schema['File'] +file_schema = schema['File'] class GuessFieldTC(TestCase): - def test_card_fields(self): - title_field = guess_field(card_schema, schema['title']) + def test_state_fields(self): + title_field = guess_field(state_schema, schema['name']) self.assertIsInstance(title_field, StringField) self.assertEquals(title_field.required, True) - synopsis_field = guess_field(card_schema, schema['synopsis']) - self.assertIsInstance(synopsis_field, TextField) - self.assertEquals(synopsis_field.required, False) - self.assertEquals(synopsis_field.help, 'an abstract for this card') +# synopsis_field = guess_field(state_schema, schema['synopsis']) +# self.assertIsInstance(synopsis_field, StringField) +# self.assertIsInstance(synopsis_field.widget, TextArea) +# self.assertEquals(synopsis_field.required, False) +# self.assertEquals(synopsis_field.help, 'an abstract for this state') - content_field = guess_field(card_schema, schema['content']) - self.assertIsInstance(content_field, RichTextField) - self.assertEquals(content_field.required, False) - self.assertEquals(content_field.format_field, None) + description_field = guess_field(state_schema, schema['description']) + self.assertIsInstance(description_field, RichTextField) + self.assertEquals(description_field.required, False) + self.assertEquals(description_field.format_field, None) - content_format_field = guess_field(card_schema, schema['content_format']) - self.assertEquals(content_format_field, None) + description_format_field = guess_field(state_schema, schema['description_format']) + self.assertEquals(description_format_field, None) - content_format_field = guess_field(card_schema, schema['content_format'], skip_meta_attr=False) - self.assertEquals(content_format_field.internationalizable, True) - self.assertEquals(content_format_field.sort, True) - self.assertEquals(content_format_field.initial, 'text/rest') + description_format_field = guess_field(state_schema, schema['description_format'], skip_meta_attr=False) + self.assertEquals(description_format_field.internationalizable, True) + self.assertEquals(description_format_field.sort, True) + self.assertEquals(description_format_field.initial, 'text/rest') - wikiid_field = guess_field(card_schema, schema['wikiid']) - self.assertIsInstance(wikiid_field, StringField) - self.assertEquals(wikiid_field.required, False) +# wikiid_field = guess_field(state_schema, schema['wikiid']) +# self.assertIsInstance(wikiid_field, StringField) +# self.assertEquals(wikiid_field.required, False) def test_euser_fields(self): diff -r a721966779be -r cba9f175da2d web/test/unittest_magicsearch.py --- a/web/test/unittest_magicsearch.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_magicsearch.py Thu May 07 16:42:34 2009 +0200 @@ -33,7 +33,7 @@ class QueryTranslatorTC(EnvBasedTC): """test suite for QueryTranslatorTC""" - + def setUp(self): super(QueryTranslatorTC, self).setUp() self.req = self.env.create_request() @@ -83,7 +83,7 @@ self.assertEquals(translate(u'prénom', eschema), "firstname") self.assertEquals(translate(u'nom', eschema), 'surname') #self.assert_(translate(u'nom') in ('name', 'surname')) - eschema = self.schema.eschema('EmailAddress') + eschema = self.schema.eschema('EmailAddress') self.assertEquals(translate(u'adresse', eschema), "address") self.assertEquals(translate(u'nom', eschema), 'alias') # should fail if the name is not an attribute for the given entity schema @@ -96,9 +96,9 @@ self.assertEquals(transform('123'), ('Any X WHERE X eid %(x)s', {'x': 123}, 'x')) self.assertEquals(transform('CWUser'), - ('CWUser E',)) + ('CWUser C',)) self.assertEquals(transform('Utilisateur'), - ('CWUser E',)) + ('CWUser C',)) self.assertEquals(transform('Adresse'), ('EmailAddress E',)) self.assertEquals(transform('adresse'), @@ -111,9 +111,9 @@ self.assertEquals(transform('CWUser', 'E'), ("CWUser E",)) self.assertEquals(transform('CWUser', 'Smith'), - ('CWUser E WHERE E has_text %(text)s', {'text': 'Smith'})) + ('CWUser C WHERE C has_text %(text)s', {'text': 'Smith'})) self.assertEquals(transform('utilisateur', 'Smith'), - ('CWUser E WHERE E has_text %(text)s', {'text': 'Smith'})) + ('CWUser C WHERE C has_text %(text)s', {'text': 'Smith'})) self.assertEquals(transform(u'adresse', 'Logilab'), ('EmailAddress E WHERE E has_text %(text)s', {'text': 'Logilab'})) self.assertEquals(transform(u'adresse', 'Logi%'), @@ -125,22 +125,22 @@ """tests the 'three words shortcut queries'""" transform = self.proc._three_words_query self.assertEquals(transform('utilisateur', u'prénom', 'cubicweb'), - ('CWUser E WHERE E firstname %(text)s', {'text': 'cubicweb'})) + ('CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'})) self.assertEquals(transform('utilisateur', 'nom', 'cubicweb'), - ('CWUser E WHERE E surname %(text)s', {'text': 'cubicweb'})) + ('CWUser C WHERE C surname %(text)s', {'text': 'cubicweb'})) self.assertEquals(transform(u'adresse', 'nom', 'cubicweb'), ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'})) self.assertEquals(transform('EmailAddress', 'nom', 'cubicweb'), - ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'})) + ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'})) self.assertEquals(transform('utilisateur', u'prénom', 'cubicweb%'), - ('CWUser E WHERE E firstname LIKE %(text)s', {'text': 'cubicweb%'})) + ('CWUser C WHERE C firstname LIKE %(text)s', {'text': 'cubicweb%'})) # expanded shortcuts self.assertEquals(transform('CWUser', 'use_email', 'Logilab'), - ('CWUser E WHERE E use_email E1, E1 has_text %(text)s', {'text': 'Logilab'})) + ('CWUser C WHERE C use_email C1, C1 has_text %(text)s', {'text': 'Logilab'})) self.assertEquals(transform('CWUser', 'use_email', '%Logilab'), - ('CWUser E WHERE E use_email E1, E1 alias LIKE %(text)s', {'text': '%Logilab'})) + ('CWUser C WHERE C use_email C1, C1 alias LIKE %(text)s', {'text': '%Logilab'})) self.assertRaises(BadRQLQuery, transform, 'word1', 'word2', 'word3') - + def test_multiple_words_query(self): """tests multiple_words_query()""" self.assertEquals(self.proc._multiple_words_query(['a', 'b', 'c', 'd', 'e']), @@ -150,9 +150,9 @@ """tests how quoted queries are handled""" queries = [ (u'Adresse "My own EmailAddress"', ('EmailAddress E WHERE E has_text %(text)s', {'text': u'My own EmailAddress'})), - (u'Utilisateur prénom "Jean Paul"', ('CWUser E WHERE E firstname %(text)s', {'text': 'Jean Paul'})), - (u'Utilisateur firstname "Jean Paul"', ('CWUser E WHERE E firstname %(text)s', {'text': 'Jean Paul'})), - (u'CWUser firstname "Jean Paul"', ('CWUser E WHERE E firstname %(text)s', {'text': 'Jean Paul'})), + (u'Utilisateur prénom "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})), + (u'Utilisateur firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})), + (u'CWUser firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})), ] transform = self.proc._quoted_words_query for query, expected in queries: @@ -160,23 +160,23 @@ self.assertRaises(BadRQLQuery, transform, "unquoted rql") self.assertRaises(BadRQLQuery, transform, 'pers "Jean Paul"') self.assertRaises(BadRQLQuery, transform, 'CWUser firstname other "Jean Paul"') - + def test_process_query(self): """tests how queries are processed""" queries = [ - (u'Utilisateur', (u"CWUser E",)), + (u'Utilisateur', (u"CWUser C",)), (u'Utilisateur P', (u"CWUser P",)), - (u'Utilisateur cubicweb', (u'CWUser E WHERE E has_text %(text)s', {'text': u'cubicweb'})), - (u'CWUser prénom cubicweb', (u'CWUser E WHERE E firstname %(text)s', {'text': 'cubicweb'},)), + (u'Utilisateur cubicweb', (u'CWUser C WHERE C has_text %(text)s', {'text': u'cubicweb'})), + (u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)), (u'Any X WHERE X is Something', (u"Any X WHERE X is Something",)), ] for query, expected in queries: self.assertEquals(self.proc.preprocess_query(query, self.req), expected) - + ## Processor Chains tests ############################################ - + class ProcessorChainTC(EnvBasedTC): """test suite for magic_search's processor chains""" @@ -195,9 +195,9 @@ # XXX this sounds like a language translator test... # and it fail (u'Utilisateur Smith', - ('CWUser E WHERE E has_text %(text)s', {'text': u'Smith'})), + ('CWUser C WHERE C has_text %(text)s', {'text': u'Smith'})), (u'utilisateur nom Smith', - ('CWUser E WHERE E surname %(text)s', {'text': u'Smith'})), + ('CWUser C WHERE C surname %(text)s', {'text': u'Smith'})), (u'Any P WHERE P is Utilisateur, P nom "Smith"', ('Any P WHERE P is CWUser, P surname "Smith"', None)), ] @@ -219,6 +219,6 @@ rset = self.proc.process_query(u'text: utilisateur Smith', self.req) self.assertEquals(rset.rql, 'Any X WHERE X has_text %(text)s') self.assertEquals(rset.args, {'text': u'utilisateur Smith'}) - + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d web/test/unittest_urlpublisher.py --- a/web/test/unittest_urlpublisher.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_urlpublisher.py Thu May 07 16:42:34 2009 +0200 @@ -21,11 +21,11 @@ b = self.add_entity('BlogEntry', title=u'hell\'o', content=u'blabla') c = self.add_entity('Tag', name=u'yo') # take care: Tag's name normalized to lower case self.execute('SET C tags B WHERE C eid %(c)s, B eid %(b)s', {'c':c.eid, 'b':b.eid}, 'b') - + def process(self, url): req = self.req = self.request() return self.env.app.url_resolver.process(req, url) - + def test_raw_path(self): """tests raw path resolution'""" self.assertEquals(self.process('view'), ('view', None)) @@ -51,12 +51,12 @@ self.assertEquals(len(rset), 1) self.assertEquals(rset.description[0][0], 'CWUser') self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X login "admin"') - ctrl, rset = self.process('euser/admin') + ctrl, rset = self.process('cwuser/admin') self.assertEquals(ctrl, 'view') self.assertEquals(len(rset), 1) self.assertEquals(rset.description[0][0], 'CWUser') self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X login "admin"') - ctrl, rset = self.process('euser/eid/%s'%rset[0][0]) + ctrl, rset = self.process('cwuser/eid/%s'%rset[0][0]) self.assertEquals(ctrl, 'view') self.assertEquals(len(rset), 1) self.assertEquals(rset.description[0][0], 'CWUser') @@ -77,7 +77,7 @@ self.assertRaises(NotFound, self.process, 'CWUser/eid/30000') self.assertRaises(NotFound, self.process, 'Workcases') self.assertRaises(NotFound, self.process, 'CWUser/inexistant_attribute/joe') - + def test_action_path(self): """tests the action path resolution""" self.assertRaises(Redirect, self.process, '1/edit') @@ -92,7 +92,7 @@ """tests the regexp path resolution""" ctrl, rset = self.process('add/Task') self.assertEquals(ctrl, 'view') - self.assertEquals(rset, None) + self.assertEquals(rset, None) self.assertEquals(self.req.form, {'etype' : "Task", 'vid' : "creation"}) self.assertRaises(NotFound, self.process, 'add/foo/bar') @@ -103,11 +103,11 @@ try: path = str(FakeRequest().url_quote(u'été')) ctrl, rset = self.process(path) - self.assertEquals(rset, None) + self.assertEquals(rset, None) self.assertEquals(self.req.form, {'vid' : "foo"}) finally: SimpleReqRewriter.rules = oldrules - + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d web/test/unittest_views_basecontrollers.py --- a/web/test/unittest_views_basecontrollers.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_views_basecontrollers.py Thu May 07 16:42:34 2009 +0200 @@ -89,7 +89,7 @@ } path, params = self.expect_redirect_publish(req) cnx.commit() # commit to check we don't get late validation error for instance - self.assertEquals(path, 'euser/user') + self.assertEquals(path, 'cwuser/user') self.failIf('vid' in params) def testr_user_editing_itself_no_relation(self): @@ -137,7 +137,7 @@ } path, params = self.expect_redirect_publish() # should be redirected on the created person - self.assertEquals(path, 'euser/adim') + self.assertEquals(path, 'cwuser/adim') e = self.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0) self.assertEquals(e.surname, 'Di Mascio') email = e.use_email[0] @@ -329,7 +329,7 @@ self.req.form = {'eid': str(eid), '__type:%s'%eid: 'EmailAddress', '__action_delete': ''} path, params = self.expect_redirect_publish() - self.assertEquals(path, 'euser/admin') + self.assertEquals(path, 'cwuser/admin') self.assertEquals(params, {u'__message': u'entity deleted'}) eid1 = self.add_entity('BlogEntry', title=u'hop', content=u'hop').eid eid2 = self.add_entity('EmailAddress', address=u'hop@logilab.fr').eid @@ -448,7 +448,7 @@ 'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', } path, params = self.expect_redirect_publish() - self.assertEquals(path, 'euser/toto') + self.assertEquals(path, 'cwuser/toto') e = self.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0) self.assertEquals(e.login, 'toto') self.assertEquals(e.in_group[0].name, 'managers') @@ -527,16 +527,18 @@ ## tests ################################################################## def test_simple_exec(self): - ctrl = self.ctrl(self.request(rql='CWUser P WHERE P login "John"', - pageid='123')) + req = self.request(rql='CWUser P WHERE P login "John"', + pageid='123', fname='view') + ctrl = self.ctrl(req) + rset = self.john.as_rset() self.assertTextEquals(ctrl.publish(), - xhtml_wrap(self.john.view('primary'))) + xhtml_wrap(ctrl.view('primary', rset))) - def test_json_exec(self): - rql = 'Any T,N WHERE T is Tag, T name N' - ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123')) - self.assertEquals(ctrl.publish(), - simplejson.dumps(self.execute(rql).rows)) +# def test_json_exec(self): +# rql = 'Any T,N WHERE T is Tag, T name N' +# ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123')) +# self.assertEquals(ctrl.publish(), +# simplejson.dumps(self.execute(rql).rows)) def test_remote_add_existing_tag(self): self.remote_call('tag_entity', self.john.eid, ['python']) @@ -562,7 +564,7 @@ eid) self.commit() rset = self.execute('CWUser P') - # make sure we did not insert a new euser here + # make sure we did not insert a new cwuser here self.assertEquals(len(rset), nbusers) john = self.execute('Any X WHERE X eid %(x)s', {'x': self.john.eid}, 'x').get_entity(0, 0) self.assertEquals(john.eid, self.john.eid) @@ -570,12 +572,12 @@ def test_pending_insertion(self): - res, req = self.remote_call('add_pending_insert', ['12', 'tags', '13']) + res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '13']]) deletes = req.get_pending_deletes() self.assertEquals(deletes, []) inserts = req.get_pending_inserts() self.assertEquals(inserts, ['12:tags:13']) - res, req = self.remote_call('add_pending_insert', ['12', 'tags', '14']) + res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']]) deletes = req.get_pending_deletes() self.assertEquals(deletes, []) inserts = req.get_pending_inserts() @@ -609,7 +611,7 @@ def test_remove_pending_operations(self): self.remote_call('add_pending_delete', ['12', 'tags', '13']) - _, req = self.remote_call('add_pending_insert', ['12', 'tags', '14']) + _, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']]) inserts = req.get_pending_inserts() self.assertEquals(inserts, ['12:tags:14']) deletes = req.get_pending_deletes() @@ -636,7 +638,7 @@ simplejson.dumps(['bimboom'])) def test_format_date(self): - self.assertEquals(self.remote_call('format_date', '"2007-01-01 12:00:00"')[0], + self.assertEquals(self.remote_call('format_date', '2007-01-01 12:00:00')[0], simplejson.dumps('2007/01/01')) diff -r a721966779be -r cba9f175da2d web/test/unittest_views_baseviews.py --- a/web/test/unittest_views_baseviews.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_views_baseviews.py Thu May 07 16:42:34 2009 +0200 @@ -6,17 +6,17 @@ from cubicweb.devtools.apptest import EnvBasedTC from cubicweb.web.htmlwidgets import TableWidget -from cubicweb.web.views.baseviews import vid_from_rset +from cubicweb.web.views import vid_from_rset def loadjson(value): return loads(html_unescape(value)) class VidFromRsetTC(EnvBasedTC): - + def test_no_rset(self): req = self.request() self.assertEquals(vid_from_rset(req, None, self.schema), 'index') - + def test_no_entity(self): req = self.request() rset = self.execute('Any X WHERE X login "blabla"') @@ -36,24 +36,24 @@ req = self.request() rset = self.execute('Any X WHERE X eid 1') self.assertEquals(vid_from_rset(req, rset, self.schema), 'primary') - + def test_more_than_one_entity(self): req = self.request() rset = self.execute('Any X WHERE X is CWUser') self.assertEquals(vid_from_rset(req, rset, self.schema), 'list') rset = self.execute('Any X, L WHERE X login L') self.assertEquals(vid_from_rset(req, rset, self.schema), 'list') - + def test_more_than_one_entity_by_row(self): req = self.request() rset = self.execute('Any X, G WHERE X in_group G') self.assertEquals(vid_from_rset(req, rset, self.schema), 'table') - + def test_more_than_one_entity_by_row_2(self): req = self.request() rset = self.execute('Any X, GN WHERE X in_group G, G name GN') self.assertEquals(vid_from_rset(req, rset, self.schema), 'table') - + def test_aggregat(self): req = self.request() rset = self.execute('Any X, COUNT(T) GROUPBY X WHERE X is T') @@ -81,7 +81,7 @@ req = self.request() view = self.vreg.select_view('table', req, rset) return e, rset, view - + def test_headers(self): self.skip('implement me') diff -r a721966779be -r cba9f175da2d web/test/unittest_views_editforms.py --- a/web/test/unittest_views_editforms.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_views_editforms.py Thu May 07 16:42:34 2009 +0200 @@ -1,7 +1,7 @@ from logilab.common.testlib import unittest_main from cubicweb.devtools.apptest import EnvBasedTC from cubicweb.devtools.testlib import WebTest -from cubicweb.web.views.editforms import AutomaticEntityForm as AEF +from cubicweb.web.views.autoform import AutomaticEntityForm as AEF from cubicweb.web.formwidgets import AutoCompletionWidget def rbc(entity, category): return [(rschema.type, x) for rschema, tschemas, x in AEF.erelations_by_category(entity, category)] @@ -9,13 +9,14 @@ class AutomaticEntityFormTC(EnvBasedTC): def test_custom_widget(self): - AEF.rwidgets.set_rtag(AutoCompletionWidget, 'login', 'subject', 'CWUser') + AEF.rwidgets.tag_relation(AutoCompletionWidget, + ('CWUser', 'login', '*'), 'subject') form = self.vreg.select_object('forms', 'edition', self.request(), None, entity=self.user()) field = form.field_by_name('login') self.assertIsInstance(field.widget, AutoCompletionWidget) - AEF.rwidgets.del_rtag('login', 'subject', 'CWUser') - + AEF.rwidgets.del_rtag(('CWUser', 'login', '*'),'subject') + def test_euser_relations_by_category(self): #for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items(): @@ -41,16 +42,16 @@ ('modification_date', 'subject'), ('owned_by', 'subject'), ('bookmarked_by', 'object'), - ]) + ]) self.assertListEquals(rbc(e, 'generic'), - [('primary_email', 'subject'), - ('use_email', 'subject'), - ('connait', 'subject'), + [('connait', 'subject'), ('checked_by', 'object'), ]) # owned_by is defined both as subject and object relations on CWUser self.assertListEquals(rbc(e, 'generated'), - [('has_text', 'subject'), + [('primary_email', 'subject'), + ('use_email', 'subject'), + ('has_text', 'subject'), ('identity', 'subject'), ('is', 'subject'), ('is_instance_of', 'subject'), @@ -63,9 +64,9 @@ ]) def test_inlined_view(self): - self.failUnless(AEF.rinlined.etype_rtag('CWUser', 'use_email', 'subject')) - self.failIf(AEF.rinlined.etype_rtag('CWUser', 'primary_email', 'subject')) - + self.failUnless(AEF.rinlined.etype_get('CWUser', 'use_email', 'subject')) + self.failIf(AEF.rinlined.etype_get('CWUser', 'primary_email', 'subject')) + def test_personne_relations_by_category(self): e = self.etype_instance('Personne') self.assertListEquals(rbc(e, 'primary'), @@ -91,7 +92,7 @@ ('creation_date', 'subject'), ('modification_date', 'subject'), ('owned_by', 'subject'), - ]) + ]) self.assertListEquals(rbc(e, 'generic'), [('travaille', 'subject'), ('connait', 'object') @@ -103,7 +104,7 @@ ('is_instance_of', 'subject'), ('identity', 'object'), ]) - + def test_edition_form(self): rset = self.execute('CWUser X LIMIT 1') form = self.vreg.select_object('forms', 'edition', rset.req, rset, @@ -112,41 +113,41 @@ self.vreg.select_object('forms', 'edition', self.request(), None, entity=rset.get_entity(0, 0)) self.failIf(any(f for f in form.fields if f is None)) - - + + class FormViewsTC(WebTest): def test_delete_conf_formview(self): rset = self.execute('CWGroup X') self.view('deleteconf', rset, template=None).source - + def test_automatic_edition_formview(self): rset = self.execute('CWUser X') self.view('edition', rset, row=0, template=None).source - + def test_automatic_edition_formview(self): rset = self.execute('CWUser X') self.view('copy', rset, row=0, template=None).source - + def test_automatic_creation_formview(self): self.view('creation', None, etype='CWUser', template=None).source - + def test_automatic_muledit_formview(self): rset = self.execute('CWUser X') self.view('muledit', rset, template=None).source - + def test_automatic_reledit_formview(self): rset = self.execute('CWUser X') self.view('reledit', rset, row=0, rtype='login', template=None).source - + def test_automatic_inline_edit_formview(self): geid = self.execute('CWGroup X LIMIT 1')[0][0] rset = self.execute('CWUser X LIMIT 1') self.view('inline-edition', rset, row=0, rtype='in_group', peid=geid, template=None).source - + def test_automatic_inline_creation_formview(self): geid = self.execute('CWGroup X LIMIT 1')[0][0] self.view('inline-creation', None, etype='CWUser', rtype='in_group', peid=geid, template=None).source - + if __name__ == '__main__': unittest_main() diff -r a721966779be -r cba9f175da2d web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Thu May 07 16:33:22 2009 +0200 +++ b/web/test/unittest_viewselector.py Thu May 07 16:42:34 2009 +0200 @@ -12,17 +12,18 @@ traced_selection) from cubicweb.web import NoSelectableObject from cubicweb.web.action import Action -from cubicweb.web.views import (baseviews, tableview, baseforms, calendar, - management, embedding, actions, startup, - euser, schemaentities, xbel, vcard, owl, - treeview, idownloadable, wdoc, debug, eproperties) +from cubicweb.web.views import (primary, baseviews, tableview, editforms, + calendar, management, embedding, actions, + startup, cwuser, schema, xbel, vcard, owl, + treeview, idownloadable, wdoc, debug, + cwproperties, workflow, xmlrss, csvexport) USERACTIONS = [('myprefs', actions.UserPreferencesAction), ('myinfos', actions.UserInfoAction), ('logout', actions.LogoutAction)] SITEACTIONS = [('siteconfig', actions.SiteConfigurationAction), ('manage', actions.ManageAction), - ('schema', actions.ViewSchemaAction)] + ('schema', schema.ViewSchemaAction)] class ViewSelectorTC(EnvBasedTC): @@ -30,7 +31,6 @@ def setup_database(self): self.add_entity('BlogEntry', title=u"une news !", content=u"cubicweb c'est beau") self.add_entity('Bookmark', title=u"un signet !", path=u"view?vid=index") - self.add_entity('Card', title=u'mandatory', content=u"DoC !") self.add_entity('EmailAddress', address=u"devel@logilab.fr", alias=u'devel') self.add_entity('Tag', name=u'x') @@ -59,115 +59,115 @@ print 'no more', [v for v in expected if not v in content.keys()] print 'missing', [v for v in content.keys() if not v in expected] raise - - + + def test_possible_views_none_rset(self): req = self.request() self.assertListEqual(self.pviews(req, None), [('changelog', wdoc.ChangeLogView), ('debug', debug.DebugView), - ('epropertiesform', eproperties.EPropertiesForm), + ('epropertiesform', cwproperties.EPropertiesForm), ('index', startup.IndexView), ('info', management.ProcessInformationView), ('manage', startup.ManageView), ('owl', owl.OWLView), ('schema', startup.SchemaView), - ('systemepropertiesform', eproperties.SystemEPropertiesForm)]) - + ('systemepropertiesform', cwproperties.SystemEPropertiesForm)]) + def test_possible_views_noresult(self): rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999') self.assertListEqual(self.pviews(req, rset), []) - + def test_possible_views_one_egroup(self): rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"') self.assertListEqual(self.pviews(req, rset), - [('csvexport', baseviews.CSVRsetView), - ('ecsvexport', baseviews.CSVEntityView), + [('csvexport', csvexport.CSVRsetView), + ('ecsvexport', csvexport.CSVEntityView), ('editable-table', tableview.EditableTableView), ('filetree', treeview.FileTreeView), ('list', baseviews.ListView), ('oneline', baseviews.OneLineView), ('owlabox', owl.OWLABOXView), - ('primary', baseviews.PrimaryView), - ('rsetxml', baseviews.XMLRsetView), - ('rss', baseviews.RssView), + ('primary', primary.PrimaryView), + ('rsetxml', xmlrss.XMLRsetView), + ('rss', xmlrss.RSSView), ('secondary', baseviews.SecondaryView), ('security', management.SecurityManagementView), ('table', tableview.TableView), ('text', baseviews.TextView), ('treeview', treeview.TreeView), ('xbel', xbel.XbelView), - ('xml', baseviews.XmlView), + ('xml', xmlrss.XMLView), ]) - + def test_possible_views_multiple_egroups(self): rset, req = self.env.get_rset_and_req('CWGroup X') self.assertListEqual(self.pviews(req, rset), - [('csvexport', baseviews.CSVRsetView), - ('ecsvexport', baseviews.CSVEntityView), + [('csvexport', csvexport.CSVRsetView), + ('ecsvexport', csvexport.CSVEntityView), ('editable-table', tableview.EditableTableView), ('filetree', treeview.FileTreeView), ('list', baseviews.ListView), ('oneline', baseviews.OneLineView), ('owlabox', owl.OWLABOXView), - ('primary', baseviews.PrimaryView), - ('rsetxml', baseviews.XMLRsetView), - ('rss', baseviews.RssView), + ('primary', primary.PrimaryView), + ('rsetxml', xmlrss.XMLRsetView), + ('rss', xmlrss.RSSView), ('secondary', baseviews.SecondaryView), ('security', management.SecurityManagementView), ('table', tableview.TableView), ('text', baseviews.TextView), ('treeview', treeview.TreeView), ('xbel', xbel.XbelView), - ('xml', baseviews.XmlView), + ('xml', xmlrss.XMLView), ]) - + def test_possible_views_multiple_different_types(self): rset, req = self.env.get_rset_and_req('Any X') self.assertListEqual(self.pviews(req, rset), - [('csvexport', baseviews.CSVRsetView), - ('ecsvexport', baseviews.CSVEntityView), + [('csvexport', csvexport.CSVRsetView), + ('ecsvexport', csvexport.CSVEntityView), ('editable-table', tableview.EditableTableView), ('filetree', treeview.FileTreeView), ('list', baseviews.ListView), ('oneline', baseviews.OneLineView), ('owlabox', owl.OWLABOXView), - ('primary', baseviews.PrimaryView), - ('rsetxml', baseviews.XMLRsetView), - ('rss', baseviews.RssView), + ('primary', primary.PrimaryView), + ('rsetxml', xmlrss.XMLRsetView), + ('rss', xmlrss.RSSView), ('secondary', baseviews.SecondaryView), ('security', management.SecurityManagementView), ('table', tableview.TableView), ('text', baseviews.TextView), ('treeview', treeview.TreeView), ('xbel', xbel.XbelView), - ('xml', baseviews.XmlView), + ('xml', xmlrss.XMLView), ]) - + def test_possible_views_any_rset(self): rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N') self.assertListEqual(self.pviews(req, rset), - [('csvexport', baseviews.CSVRsetView), + [('csvexport', csvexport.CSVRsetView), ('editable-table', tableview.EditableTableView), - ('rsetxml', baseviews.XMLRsetView), + ('rsetxml', xmlrss.XMLRsetView), ('table', tableview.TableView), ]) def test_possible_views_multiple_eusers(self): rset, req = self.env.get_rset_and_req('CWUser X') self.assertListEqual(self.pviews(req, rset), - [('csvexport', baseviews.CSVRsetView), - ('ecsvexport', baseviews.CSVEntityView), + [('csvexport', csvexport.CSVRsetView), + ('ecsvexport', csvexport.CSVEntityView), ('editable-table', tableview.EditableTableView), ('filetree', treeview.FileTreeView), - ('foaf', euser.FoafView), + ('foaf', cwuser.FoafView), ('list', baseviews.ListView), ('oneline', baseviews.OneLineView), ('owlabox', owl.OWLABOXView), - ('primary', euser.CWUserPrimaryView), - ('rsetxml', baseviews.XMLRsetView), - ('rss', baseviews.RssView), + ('primary', cwuser.CWUserPrimaryView), + ('rsetxml', xmlrss.XMLRsetView), + ('rss', xmlrss.RSSView), ('secondary', baseviews.SecondaryView), ('security', management.SecurityManagementView), ('table', tableview.TableView), @@ -175,15 +175,15 @@ ('treeview', treeview.TreeView), ('vcard', vcard.VCardCWUserView), ('xbel', xbel.XbelView), - ('xml', baseviews.XmlView), + ('xml', xmlrss.XMLView), ]) - + def test_possible_actions_none_rset(self): req = self.request() self.assertDictEqual(self.pactions(req, None), {'useractions': USERACTIONS, 'siteactions': SITEACTIONS, - + }) def test_possible_actions_no_entity(self): rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999') @@ -191,7 +191,7 @@ {'useractions': USERACTIONS, 'siteactions': SITEACTIONS, }) - + def test_possible_actions_same_type_entities(self): rset, req = self.env.get_rset_and_req('CWGroup X') self.assertDictEqual(self.pactions(req, rset), @@ -201,7 +201,7 @@ 'moreactions': [('delete', actions.DeleteAction), ('addentity', actions.AddNewAction)], }) - + def test_possible_actions_different_types_entities(self): rset, req = self.env.get_rset_and_req('Any X') self.assertDictEqual(self.pactions(req, rset), @@ -209,25 +209,26 @@ 'siteactions': SITEACTIONS, 'moreactions': [('delete', actions.DeleteAction)], }) - + def test_possible_actions_final_entities(self): rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N') self.assertDictEqual(self.pactions(req, rset), {'useractions': USERACTIONS, 'siteactions': SITEACTIONS}) - - def test_possible_actions_eetype_euser_entity(self): + + def test_possible_actions_eetype_cwuser_entity(self): rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWUser"') self.assertDictEqual(self.pactions(req, rset), {'useractions': USERACTIONS, 'siteactions': SITEACTIONS, 'mainactions': [('edit', actions.ModifyAction), - ('workflow', schemaentities.ViewWorkflowAction),], - 'moreactions': [('delete', actions.DeleteAction), + ('workflow', workflow.ViewWorkflowAction),], + 'moreactions': [('managepermission', actions.ManagePermissionsAction), + ('delete', actions.DeleteAction), ('copy', actions.CopyAction), - ('managepermission', actions.ManagePermissionsAction)], + ], }) - + def test_select_creation_form(self): rset = None @@ -235,16 +236,17 @@ # creation form req.form['etype'] = 'CWGroup' self.assertIsInstance(self.vreg.select_view('creation', req, rset), - baseforms.CreationForm) + editforms.CreationFormView) del req.form['etype'] # custom creation form - class CWUserCreationForm(baseforms.CreationForm): + class CWUserCreationForm(editforms.CreationFormView): __select__ = specified_etype_implements('CWUser') + self.vreg._loadedmods[__name__] = {} self.vreg.register_vobject_class(CWUserCreationForm) req.form['etype'] = 'CWUser' self.assertIsInstance(self.vreg.select_view('creation', req, rset), CWUserCreationForm) - + def test_select_view(self): # no entity rset = None @@ -255,7 +257,7 @@ self.vreg.select_view, 'primary', req, rset) self.failUnlessRaises(NoSelectableObject, self.vreg.select_view, 'table', req, rset) - + # no entity rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999') self.failUnlessRaises(NoSelectableObject, @@ -269,11 +271,11 @@ # one entity rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"') self.assertIsInstance(self.vreg.select_view('primary', req, rset), - baseviews.PrimaryView) + primary.PrimaryView) self.assertIsInstance(self.vreg.select_view('list', req, rset), baseviews.ListView) self.assertIsInstance(self.vreg.select_view('edition', req, rset), - baseforms.EditionForm) + editforms.EditionFormView) self.assertIsInstance(self.vreg.select_view('table', req, rset), tableview.TableView) self.failUnlessRaises(NoSelectableObject, @@ -283,7 +285,7 @@ # list of entities of the same type rset, req = self.env.get_rset_and_req('CWGroup X') self.assertIsInstance(self.vreg.select_view('primary', req, rset), - baseviews.PrimaryView) + primary.PrimaryView) self.assertIsInstance(self.vreg.select_view('list', req, rset), baseviews.ListView) self.assertIsInstance(self.vreg.select_view('table', req, rset), @@ -293,7 +295,7 @@ # list of entities of different types rset, req = self.env.get_rset_and_req('Any X') self.assertIsInstance(self.vreg.select_view('primary', req, rset), - baseviews.PrimaryView) + primary.PrimaryView) self.assertIsInstance(self.vreg.select_view('list', req, rset), baseviews.ListView) self.assertIsInstance(self.vreg.select_view('table', req, rset), @@ -324,10 +326,10 @@ self.vreg.select_view, 'creation', req, rset) self.assertIsInstance(self.vreg.select_view('table', req, rset), tableview.TableView) - # euser primary view priority + # cwuser primary view priority rset, req = self.env.get_rset_and_req('CWUser X WHERE X login "admin"') self.assertIsInstance(self.vreg.select_view('primary', req, rset), - euser.CWUserPrimaryView) + cwuser.CWUserPrimaryView) self.assertIsInstance(self.vreg.select_view('text', req, rset), baseviews.TextView) @@ -337,8 +339,8 @@ rset, req = self.env.get_rset_and_req('Image X WHERE X name "bim.png"') self.assertIsInstance(self.vreg.select_view('primary', req, rset), idownloadable.IDownloadablePrimaryView) - - + + def test_score_entity_selector(self): image = self.add_entity('Image', name=u'bim.png', data=Binary('bim')) # image primary view priority @@ -349,9 +351,9 @@ # image primary view priority rset, req = self.env.get_rset_and_req('File X WHERE X name "bim.txt"') self.assertRaises(NoSelectableObject, self.vreg.select_view, 'image', req, rset) - - - + + + def _test_view(self, vid, rql, args): if rql is None: rset = None @@ -389,35 +391,8 @@ self.assertEquals(self.vreg.property_value('boxes.possible_views_box.visible'), False) self.assertEquals(self.vreg.property_value('boxes.possible_views_box.order'), 10) self.assertRaises(KeyError, self.vreg.property_value, 'boxes.actions_box') - - def test_owners_match_user_group(self): - """tests usage of 'owners' group with match_user_group""" - class SomeAction(Action): - id = 'yo' - category = 'foo' - __select__ = match_user_groups('owners') - self.vreg.register_vobject_class(SomeAction) - self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) - try: - # login as a simple user - self.create_user('john') - self.login('john') - # it should not be possible to use SomeAction not owned objects - rset, req = self.env.get_rset_and_req('Any G WHERE G is CWGroup, G name "managers"') - self.failIf('foo' in self.pactions(req, rset)) - # insert a new card, and check that we can use SomeAction on our object - self.execute('INSERT Card C: C title "zoubidou"') - self.commit() - rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"') - self.failUnless('foo' in self.pactions(req, rset)) - # make sure even managers can't use the action - self.restore_connection() - rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"') - self.failIf('foo' in self.pactions(req, rset)) - finally: - del self.vreg[SomeAction.__registry__][SomeAction.id] class CWETypeRQLAction(Action): @@ -426,36 +401,39 @@ title = 'bla' class RQLActionTC(ViewSelectorTC): - + def setUp(self): super(RQLActionTC, self).setUp() + self.vreg._loadedmods[__name__] = {} self.vreg.register_vobject_class(CWETypeRQLAction) - + def tearDown(self): - super(RQLActionTC, self).tearDown() + super(RQLActionTC, self).tearDown() del self.vreg._registries['actions']['testaction'] - + def test(self): rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWEType"') self.assertDictEqual(self.pactions(req, rset), {'useractions': USERACTIONS, 'siteactions': SITEACTIONS, 'mainactions': [('edit', actions.ModifyAction)], - 'moreactions': [('delete', actions.DeleteAction), + 'moreactions': [('managepermission', actions.ManagePermissionsAction), + ('delete', actions.DeleteAction), ('copy', actions.CopyAction), ('testaction', CWETypeRQLAction), - ('managepermission', actions.ManagePermissionsAction)], + ], }) rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWRType"') self.assertDictEqual(self.pactions(req, rset), {'useractions': USERACTIONS, 'siteactions': SITEACTIONS, 'mainactions': [('edit', actions.ModifyAction)], - 'moreactions': [('delete', actions.DeleteAction), + 'moreactions': [('managepermission', actions.ManagePermissionsAction), + ('delete', actions.DeleteAction), ('copy', actions.CopyAction), - ('managepermission', actions.ManagePermissionsAction)], + ], }) - + if __name__ == '__main__': diff -r a721966779be -r cba9f175da2d web/test/unittest_web.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/test/unittest_web.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,18 @@ +from logilab.common.testlib import TestCase, unittest_main +from cubicweb.web import ajax_replace_url as arurl +class AjaxReplaceUrlTC(TestCase): + + def test_ajax_replace_url(self): + # NOTE: for the simplest use cases, we could use doctest + self.assertEquals(arurl('foo', 'Person P'), + "javascript: replacePageChunk('foo', 'Person%20P');") + self.assertEquals(arurl('foo', 'Person P', 'oneline'), + "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');") + self.assertEquals(arurl('foo', 'Person P', 'oneline', name='bar', age=12), + 'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'oneline\', {"age": 12, "name": "bar"});') + self.assertEquals(arurl('foo', 'Person P', name='bar', age=12), + 'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'null\', {"age": 12, "name": "bar"});') + + +if __name__ == '__main__': + unittest_main() diff -r a721966779be -r cba9f175da2d web/uicfg.py --- a/web/uicfg.py Thu May 07 16:33:22 2009 +0200 +++ b/web/uicfg.py Thu May 07 16:42:34 2009 +0200 @@ -9,32 +9,85 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" -from cubicweb.rtags import RelationTags + +from cubicweb.rtags import RelationTags, RelationTagsSet + +# primary view configuration ################################################## + +# how to display a relation in primary view. +# values a dict with the following keys: +# +# 'where', whose value may be one of: +# * 'attributes', display in the attributes section +# * 'relations', display in the relations section (below attributes) +# * 'sideboxes', display in the side boxes (beside attributes) +# if this key is missing, the relation won't be displayed at all. +# +# 'vid' is an optional view identifier +# +# 'label' is an optional label +# +# 'limit' is a boolean telling if the results should be limited according to +# the configuration +class RDisplayRelationTags(RelationTags): + def __init__(self): + super(RDisplayRelationTags, self).__init__() + self._counter = 0 + + def tag_relation(self, values, *args, **kwargs): + super(RDisplayRelationTags, self).tag_relation(values, *args, **kwargs) + if values: + values['order'] = self.get_timestamp() + + def get_timestamp(self): + self._counter += 1 + return self._counter + +rdisplay = RDisplayRelationTags() +for rtype in ('eid', 'creation_date', 'modification_date', + 'is', 'is_instance_of', 'identity', + 'owned_by', 'created_by', + 'in_state', 'wf_info_for', 'require_permission', + 'from_entity', 'to_entity', + 'see_also'): + rdisplay.tag_relation({}, ('*', rtype, '*'), 'subject') + rdisplay.tag_relation({}, ('*', rtype, '*'), 'object') + + +# index view configuration #################################################### +# entity type category in the index/manage page. May be one of +# * 'application' +# * 'system' +# * 'schema' +# * 'subobject' (not displayed by default) + +etypecat = {'EmailAddress': 'subobject'} + # autoform.AutomaticEntityForm configuration ################################## # relations'category (eg primary/secondary/generic/metadata/generated) rcategories = RelationTags() # use primary and not generated for eid since it has to be an hidden -rcategories.set_rtag('primary', 'eid', 'subject') -rcategories.set_rtag('primary', 'in_state', 'subject') -rcategories.set_rtag('secondary', 'description', 'subject') -rcategories.set_rtag('metadata', 'creation_date', 'subject') -rcategories.set_rtag('metadata', 'modification_date', 'subject') -rcategories.set_rtag('metadata', 'owned_by', 'subject') -rcategories.set_rtag('metadata', 'created_by', 'subject') -rcategories.set_rtag('generated', 'has_text', 'subject') -rcategories.set_rtag('generated', 'is', 'subject') -rcategories.set_rtag('generated', 'is', 'object') -rcategories.set_rtag('generated', 'is_instance_of', 'subject') -rcategories.set_rtag('generated', 'is_instance_of', 'object') -rcategories.set_rtag('generated', 'identity', 'subject') -rcategories.set_rtag('generated', 'identity', 'object') -rcategories.set_rtag('generated', 'require_permission', 'subject') -rcategories.set_rtag('generated', 'wf_info_for', 'subject') -rcategories.set_rtag('generated', 'wf_info_for', 'object') -rcategories.set_rtag('generated', 'for_user', 'subject') -rcategories.set_rtag('generated', 'for_user', 'object') +rcategories.tag_relation('primary', ('*', 'eid', '*'), 'subject') +rcategories.tag_relation('primary', ('*', 'in_state', '*'), 'subject') +rcategories.tag_relation('secondary', ('*', 'description', '*'), 'subject') +rcategories.tag_relation('metadata', ('*', 'creation_date', '*'), 'subject') +rcategories.tag_relation('metadata', ('*', 'modification_date', '*'), 'subject') +rcategories.tag_relation('metadata', ('*', 'owned_by', '*'), 'subject') +rcategories.tag_relation('metadata', ('*', 'created_by', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'has_text', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'is', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'is', '*'), 'object') +rcategories.tag_relation('generated', ('*', 'is_instance_of', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'is_instance_of', '*'), 'object') +rcategories.tag_relation('generated', ('*', 'identity', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'identity', '*'), 'object') +rcategories.tag_relation('generated', ('*', 'require_permission', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'wf_info_for', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'wf_info_for', '*'), 'object') +rcategories.tag_relation('generated', ('*', 'for_user', '*'), 'subject') +rcategories.tag_relation('generated', ('*', 'for_user', '*'), 'object') # relations'field class rfields = RelationTags() @@ -46,25 +99,27 @@ # entity(ies) at the other end of the relation will be editable from the # form of the edited entity rinlined = RelationTags() +rinlined.tag_relation(True, ('*', 'use_email', '*'), 'subject') + # set of tags of the form _on_new on relations. is a # schema action (add/update/delete/read), and when such a tag is found # permissions checking is by-passed and supposed to be ok -rpermissions_overrides = RelationTags(use_set=True) +rpermissions_overrides = RelationTagsSet() # boxes.EditBox configuration ################################################# # 'link' / 'create' relation tags, used to control the "add entity" submenu rmode = RelationTags() -rmode.set_rtag('link', 'is', 'subject') -rmode.set_rtag('link', 'is', 'object') -rmode.set_rtag('link', 'is_instance_of', 'subject') -rmode.set_rtag('link', 'is_instance_of', 'object') -rmode.set_rtag('link', 'identity', 'subject') -rmode.set_rtag('link', 'identity', 'object') -rmode.set_rtag('link', 'owned_by', 'subject') -rmode.set_rtag('link', 'created_by', 'subject') -rmode.set_rtag('link', 'require_permission', 'subject') -rmode.set_rtag('link', 'wf_info_for', 'subject') -rmode.set_rtag('link', 'wf_info_for', 'object') +rmode.tag_relation('link', ('*', 'is', '*'), 'subject') +rmode.tag_relation('link', ('*', 'is', '*'), 'object') +rmode.tag_relation('link', ('*', 'is_instance_of', '*'), 'subject') +rmode.tag_relation('link', ('*', 'is_instance_of', '*'), 'object') +rmode.tag_relation('link', ('*', 'identity', '*'), 'subject') +rmode.tag_relation('link', ('*', 'identity', '*'), 'object') +rmode.tag_relation('link', ('*', 'owned_by', '*'), 'subject') +rmode.tag_relation('link', ('*', 'created_by', '*'), 'subject') +rmode.tag_relation('link', ('*', 'require_permission', '*'), 'subject') +rmode.tag_relation('link', ('*', 'wf_info_for', '*'), 'subject') +rmode.tag_relation('link', ('*', 'wf_info_for', '*'), 'object') diff -r a721966779be -r cba9f175da2d web/views/actions.py --- a/web/views/actions.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/actions.py Thu May 07 16:42:34 2009 +0200 @@ -154,7 +154,7 @@ title = _('manage permissions') category = 'moreactions' - order = 100 + order = 15 @classmethod def registered(cls, vreg): @@ -248,7 +248,7 @@ order = 20 def url(self): - return self.build_url('euser/%s'%self.req.user.login, vid='edition') + return self.build_url('cwuser/%s'%self.req.user.login, vid='edition') class LogoutAction(Action): diff -r a721966779be -r cba9f175da2d web/views/ajaxedit.py --- a/web/views/ajaxedit.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/ajaxedit.py Thu May 07 16:42:34 2009 +0200 @@ -6,6 +6,7 @@ """ __docformat__ = "restructuredtext en" +from cubicweb import role from cubicweb.selectors import match_form_params, match_kwargs from cubicweb.web.box import EditRelationBoxTemplate @@ -24,7 +25,7 @@ expected_kwargs = form_params = ('rtype', 'target') build_js = EditRelationBoxTemplate.build_reload_js_call - + def cell_call(self, row, col, rtype=None, target=None, etype=None): self.rtype = rtype or self.req.form['rtype'] self.target = target or self.req.form['target'] @@ -41,7 +42,7 @@ fakebox = [] self.w(u'
' % self.id) self.w(u'

%s

' % self.req._('relation %(relname)s of %(ent)s') - % {'relname': rschema.display_name(self.req, self.xtarget()[0]), + % {'relname': rschema.display_name(self.req, role(self)), 'ent': entity.view('incontext')}) self.w(u'
    ') self.w_unrelated(fakebox, entity) @@ -55,15 +56,16 @@ if etype is not defined on the Box's class, the default behaviour is to use the entity's appropraite vocabulary function """ - x, target = self.xtarget() # use entity.unrelated if we've been asked for a particular etype if getattr(self, 'etype', None): - rset = entity.unrelated(self.rtype, self.etype, x, ordermethod='fetch_order') + rset = entity.unrelated(self.rtype, self.etype, role(self), + ordermethod='fetch_order') self.pagination(self.req, rset, w=self.w) return rset.entities() # in other cases, use vocabulary functions entities = [] - for _, eid in entity.vocabulary(self.rtype, x): + # XXX to update for 3.2 + for _, eid in entity.vocabulary(self.rtype, role(self)): if eid is not None: rset = self.req.eid_rset(eid) entities.append(rset.get_entity(0, 0)) diff -r a721966779be -r cba9f175da2d web/views/authentication.py --- a/web/views/authentication.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/authentication.py Thu May 07 16:42:34 2009 +0200 @@ -1,7 +1,7 @@ """user authentication component :organization: Logilab -:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" @@ -12,11 +12,11 @@ from cubicweb.dbapi import repo_connect, ConnectionProperties from cubicweb.web import ExplicitLogin, InvalidSession from cubicweb.web.application import AbstractAuthenticationManager - + class RepositoryAuthenticationManager(AbstractAuthenticationManager): """authenticate user associated to a request and check session validity""" - + def __init__(self): self.repo = self.config.repository(self.vreg) self.log_queries = self.config['query-log-file'] @@ -51,10 +51,22 @@ # associate the connection to the current request req.set_connection(cnx, user) return cnx - + + def login_from_email(self, login): + # XXX should not be called from web interface + session = self.repo.internal_session() + try: + rset = session.execute('Any L WHERE U login L, U primary_email M, ' + 'M address %(login)s', {'login': login}) + if rset.rowcount == 1: + login = rset[0][0] + finally: + session.close() + return login + def authenticate(self, req, _login=None, _password=None): """authenticate user and return corresponding user object - + :raise ExplicitLogin: if authentication is required (no authentication info found or wrong user/password) @@ -66,6 +78,8 @@ login, password = _login, _password else: login, password = req.get_authorization() + if self.vreg.config['allow-email-login'] and '@' in (login or u''): + login = self.login_from_email(login) if not login: # No session and no login -> try anonymous login, password = self.vreg.config.anonymous_user() diff -r a721966779be -r cba9f175da2d web/views/autoform.py --- a/web/views/autoform.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/autoform.py Thu May 07 16:42:34 2009 +0200 @@ -5,6 +5,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" +_ = unicode from logilab.common.decorators import iclassmethod @@ -13,12 +14,13 @@ from cubicweb.web.form import FieldNotFound, EntityFieldsForm from cubicweb.web.formfields import guess_field from cubicweb.web.formwidgets import Button, SubmitButton -_ = unicode +from cubicweb.web.views.editforms import toggleable_relation_link, relation_id + class AutomaticEntityForm(EntityFieldsForm): """base automatic form to edit any entity. - Designed to be flly generated from schema but highly configurable through: + Designed to be fully generated from schema but highly configurable through: * rtags (rcategories, rfields, rwidgets, inlined, rpermissions) * various standard form parameters @@ -58,8 +60,10 @@ X, Y = tschema, eschema card = rschema.rproperty(X, Y, 'cardinality')[1] composed = rschema.rproperty(X, Y, 'composite') == 'subject' - if not cls.rcategories.rtag(rschema, role, X, Y): - if card in '1+': + if not cls.rcategories.get(rschema, role, X, Y): + if eschema.is_metadata(rschema): + category = 'generated' + elif card in '1+': if not rschema.is_final() and composed: category = 'generated' else: @@ -68,7 +72,7 @@ category = 'secondary' else: category = 'generic' - cls.rcategories.set_rtag(category, rschema, role, X, Y) + cls.rcategories.tag_relation(category, (X, rschema, Y), role) @classmethod def erelations_by_category(cls, entity, categories=None, permission=None, rtags=None): @@ -93,14 +97,14 @@ # permission which may imply rql queries if categories is not None: targetschemas = [tschema for tschema in targetschemas - if rtags.etype_rtag(eschema, rschema, role, tschema) in categories] + if rtags.etype_get(eschema, rschema, role, tschema) in categories] if not targetschemas: continue if permission is not None: # tag allowing to hijack the permission machinery when # permission is not verifiable until the entity is actually # created... - if eid is None and '%s_on_new' % permission in permsoverrides.etype_rtags(eschema, rschema, role): + if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role): yield (rschema, targetschemas, role) continue if rschema.is_final(): @@ -159,10 +163,10 @@ if eschema is None or not name in cls_or_self.schema: raise rschema = cls_or_self.schema.rschema(name) - fieldcls = cls_or_self.rfields.etype_rtag(eschema, rschema, role) + fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role) if fieldcls: return fieldcls(name=name, role=role, eidparam=True) - widget = cls_or_self.rwidgets.etype_rtag(eschema, rschema, role) + widget = cls_or_self.rwidgets.etype_get(eschema, rschema, role) if widget: field = guess_field(eschema, rschema, role, eidparam=True, widget=widget) @@ -240,8 +244,8 @@ def editable_attributes(self): """return a list of (relation schema, role) to edit for the entity""" - return [(rschema, x) for rschema, _, x in self.relations_by_category( - self.attrcategories, 'add') if rschema != 'eid'] + return [(rschema, role) for rschema, _, role in self.relations_by_category( + self.attrcategories, 'add') if rschema != 'eid'] def relations_table(self): """yiels 3-tuples (rtype, target, related_list) @@ -256,9 +260,9 @@ for label, rschema, role in self.srelations_by_category('generic', 'add'): relatedrset = entity.related(rschema, role, limit=self.related_limit) if rschema.has_perm(self.req, 'delete'): - toggable_rel_link_func = toggable_relation_link + toggleable_rel_link_func = toggleable_relation_link else: - toggable_rel_link_func = lambda x, y, z: u'' + toggleable_rel_link_func = lambda x, y, z: u'' related = [] for row in xrange(relatedrset.rowcount): nodeid = relation_id(entity.eid, rschema, role, @@ -269,7 +273,7 @@ else: status = u'' label = 'x' - dellink = toggable_rel_link_func(entity.eid, nodeid, label) + dellink = toggleable_rel_link_func(entity.eid, nodeid, label) eview = self.view('oneline', relatedrset, row=row) related.append((nodeid, dellink, status, eview)) yield (rschema, role, related) @@ -304,7 +308,7 @@ """return true if the given relation with entity has role and a targettype target should be inlined """ - return self.rinlined.etype_rtag(self.edited_entity.id, rschema, role, targettype) + return self.rinlined.etype_get(self.edited_entity.id, rschema, role, targettype) def should_display_inline_creation_form(self, rschema, existant, card): """return true if a creation form should be inlined diff -r a721966779be -r cba9f175da2d web/views/basecomponents.py --- a/web/views/basecomponents.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/basecomponents.py Thu May 07 16:42:34 2009 +0200 @@ -12,20 +12,24 @@ from rql import parse from cubicweb.selectors import yes, two_etypes_rset, match_form_params +from cubicweb.schema import display_name from cubicweb.common.uilib import html_escape, toggle_action -from cubicweb.schema import display_name - -from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink -from cubicweb.web.component import Component, RelatedObjectsVComponent +from cubicweb.web import uicfg, component +from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator, + BoxLink) _ = unicode +VISIBLE_PROP_DEF = { + _('visible'): dict(type='Boolean', default=False, + help=_('display the component or not')), + } -class RQLInputForm(Component): +class RQLInputForm(component.Component): """build the rql input form, usually displayed in the header""" id = 'rqlinput' - visible = False - + property_defs = VISIBLE_PROP_DEF + def call(self, view=None): if hasattr(view, 'filter_box_context_info'): rset = view.filter_box_context_info()[0] @@ -50,30 +54,36 @@ self.w(u'
') -class ApplLogo(Component): +class ApplLogo(component.Component): """build the application logo, usually displayed in the header""" id = 'logo' - site_wide = True # don't want user to hide this component using an eproperty + property_defs = VISIBLE_PROP_DEF + # don't want user to hide this component using an cwproperty + site_wide = True + def call(self): self.w(u'' % (self.req.base_url(), self.req.external_resource('LOGO'))) -class ApplHelp(Component): +class ApplHelp(component.Component): """build the help button, usually displayed in the header""" id = 'help' + property_defs = VISIBLE_PROP_DEF def call(self): self.w(u' ' % (self.build_url(_restpath='doc/main'), self.req._(u'help'),)) -class UserLink(Component): +class UserLink(component.Component): """if the user is the anonymous user, build a link to login else a link to the connected user object with a loggout link """ + property_defs = VISIBLE_PROP_DEF + # don't want user to hide this component using an cwproperty + site_wide = True id = 'loggeduserlink' - site_wide = True # don't want user to hide this component using an eproperty def call(self): if not self.req.cnx.anonymous_connection: @@ -93,7 +103,7 @@ box.render(w=self.w) else: self.anon_user_link() - + def anon_user_link(self): if self.config['auth-mode'] == 'cookie': self.w(self.req._('anonymous')) @@ -110,13 +120,15 @@ % (self.build_url('login'), self.req._('login'))) -class ApplicationMessage(Component): +class ApplicationMessage(component.Component): """display application's messages given using the __message parameter into a special div section """ __select__ = yes() id = 'applmessages' - site_wide = True # don't want user to hide this component using an eproperty + property_defs = VISIBLE_PROP_DEF + # don't want user to hide this component using an cwproperty + site_wide = True def call(self): msgs = [msg for msg in (self.req.get_shared_data('sources_error', pop=True), @@ -129,36 +141,43 @@ self.w(u'') -class ApplicationName(Component): +class ApplicationName(component.Component): """display the application name""" id = 'appliname' + property_defs = VISIBLE_PROP_DEF def call(self): self.w(u'%s' % (self.req.base_url(), self.req.property_value('ui.site-title'))) - + -class SeeAlsoVComponent(RelatedObjectsVComponent): +uicfg.rdisplay.tag_relation({}, ('*', 'see_also', '*'), 'subject') +uicfg.rdisplay.tag_relation({}, ('*', 'see_also', '*'), 'object') + +class SeeAlsoVComponent(component.RelatedObjectsVComponent): """display any entity's see also""" id = 'seealso' context = 'navcontentbottom' rtype = 'see_also' - target = 'object' + role = 'subject' order = 40 # register msg not generated since no entity use see_also in cubicweb itself title = _('contentnavigation_seealso') help = _('contentnavigation_seealso_description') - -class EtypeRestrictionComponent(Component): + +class EtypeRestrictionComponent(component.Component): """displays the list of entity types contained in the resultset to be able to filter accordingly. """ id = 'etypenavigation' __select__ = two_etypes_rset() | match_form_params('__restrtype', '__restrtypes', '__restrrql') + property_defs = VISIBLE_PROP_DEF + # don't want user to hide this component using an cwproperty + site_wide = True visible = False # disabled by default - + def call(self): _ = self.req._ self.w(u'
') @@ -197,7 +216,7 @@ html.insert(0, u'%s' % _('Any')) self.w(u' | '.join(html)) self.w(u'
') - + def registration_callback(vreg): vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) diff -r a721966779be -r cba9f175da2d web/views/basecontrollers.py --- a/web/views/basecontrollers.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/basecontrollers.py Thu May 07 16:42:34 2009 +0200 @@ -20,7 +20,7 @@ from cubicweb.selectors import yes, match_user_groups from cubicweb.view import STRICT_DOCTYPE from cubicweb.common.mail import format_mail -from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed +from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps from cubicweb.web.formrenderers import FormRenderer from cubicweb.web.controller import Controller from cubicweb.web.views import vid_from_rset @@ -42,8 +42,8 @@ """ def wrapper(self, *args, **kwargs): self.req.set_content_type('application/json') - result = func(self, *args, **kwargs) - return simplejson.dumps(result) + return json_dumps(func(self, *args, **kwargs)) + wrapper.__name__ = func.__name__ return wrapper def xhtmlize(func): @@ -52,6 +52,7 @@ self.req.set_content_type(self.req.html_content_type()) result = func(self, *args, **kwargs) return xhtml_wrap(result) + wrapper.__name__ = func.__name__ return wrapper def check_pageid(func): @@ -360,6 +361,9 @@ @jsonize def js_validate_form(self, action, names, values): + return self.validate_form(action, names, values) + + def validate_form(self, action, names, values): # XXX this method (and correspoding js calls) should use the new # `RemoteCallFailed` mechansim self.req.form = self._rebuild_posted_form(names, values, action) @@ -388,8 +392,10 @@ @jsonize def js_edit_field(self, action, names, values, rtype, eid): - success, args = self.js_validate_form(action, names, values) + success, args = self.validate_form(action, names, values) if success: + # Any X,N where we don't seem to use N is an optimisation + # printable_value won't need to query N again rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype, {'x': eid}, 'x') entity = rset.get_entity(0, 0) @@ -397,9 +403,17 @@ else: return (success, args, None) -# def js_rql(self, rql): -# rset = self._exec(rql) -# return rset and rset.rows or [] + @jsonize + def js_edit_relation(self, action, names, values, + rtype, eid, role='subject', vid='list'): + # XXX validate_form + success, args = self.validate_form(action, names, values) + if success: + entity = self.req.eid_rset(eid).get_entity(0, 0) + rset = entity.related('person_in_charge', role) + return (success, args, self.view(vid, rset)) + else: + return (success, args, None) @jsonize def js_i18n(self, msgids): @@ -481,13 +495,9 @@ def _remove_pending(self, eidfrom, rel, eidto, kind): key = 'pending_%s' % kind - try: - pendings = self.req.get_session_data(key) - pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) - except: - self.exception('while removing pending eids') - else: - self.req.set_session_data(key, pendings) + pendings = self.req.get_session_data(key) + pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) + self.req.set_session_data(key, pendings) def js_remove_pending_insert(self, (eidfrom, rel, eidto)): self._remove_pending(eidfrom, rel, eidto, 'insert') diff -r a721966779be -r cba9f175da2d web/views/baseforms.py --- a/web/views/baseforms.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/baseforms.py Thu May 07 16:42:34 2009 +0200 @@ -320,8 +320,8 @@ # should_* method extracted to allow overriding def should_inline_relation_form(self, entity, rschema, targettype, role): - return AutomaticEntityForm.rinlined.etype_rtag(entity.id, rschema, role, - targettype) + return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role, + targettype) def should_display_inline_relation_form(self, rschema, existant, card): return not existant and card in '1+' @@ -423,8 +423,8 @@ def should_inline_relation_form(self, entity, rschema, targettype, role): if rschema == self.rschema: return False - return AutomaticEntityForm.rinlined.etype_rtag(entity.id, rschema, role, - targettype) + return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role, + targettype) @cached def keep_entity(self, entity): diff -r a721966779be -r cba9f175da2d web/views/baseviews.py --- a/web/views/baseviews.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/baseviews.py Thu May 07 16:42:34 2009 +0200 @@ -16,7 +16,7 @@ from rql import nodes -from logilab.mtconverter import TransformError, html_escape +from logilab.mtconverter import TransformError, html_escape, xml_escape from cubicweb import NoSelectableObject from cubicweb.selectors import yes, empty_rset @@ -68,6 +68,7 @@ def cell_call(self, row, col, props=None, displaytime=False, format='text/html'): etype = self.rset.description[row][col] value = self.rset.rows[row][col] + if etype == 'String': entity, rtype = self.rset.related_entity(row, col) if entity is not None: @@ -89,10 +90,10 @@ self.w(self.req.__('%%d%sweeks' % space) % (value.days // 7)) elif value.days > 2: self.w(self.req.__('%%d%sdays' % space) % int(value.days)) - elif value.hours > 2: - self.w(self.req.__('%%d%shours' % space) % int(value.hours)) - elif value.minutes >= 2: - self.w(self.req.__('%%d%sminutes' % space) % int(value.minutes)) + elif value.seconds > 3600: + self.w(self.req.__('%%d%shours' % space) % int(value.seconds // 3600)) + elif value.seconds >= 120: + self.w(self.req.__('%%d%sminutes' % space) % int(value.seconds // 60)) else: self.w(self.req.__('%%d%sseconds' % space) % int(value.seconds)) return @@ -165,7 +166,7 @@ self.w(u'#%s - ' % entity.eid) if entity.modification_date != entity.creation_date: self.w(u'%s ' % _('latest update on')) - self.w(u'%s, ;' + self.w(u'%s, ' % self.format_date(entity.modification_date)) # entities from external source may not have a creation date (eg ldap) if entity.creation_date: @@ -338,7 +339,7 @@ highlighted = '%s' % searched for attr in entity.e_schema.indexable_attributes(): try: - value = html_escape(entity.printable_value(attr, format='text/plain').lower()) + value = xml_escape(entity.printable_value(attr, format='text/plain').lower()) except TransformError, ex: continue except: @@ -373,11 +374,10 @@ from cubicweb.web.views import boxes, xmlrss, primary PrimaryView = class_moved(primary.PrimaryView) -PRIMARY_SKIP_RELS = primary.PRIMARY_SKIP_RELS SideBoxView = class_moved(boxes.SideBoxView) -XmlView = class_moved(xmlrss.XmlView) -XmlItemView = class_moved(xmlrss.XmlItemView) -XmlRsetView = class_moved(xmlrss.XmlRsetView) -RssView = class_moved(xmlrss.RssView) -RssItemView = class_moved(xmlrss.RssItemView) +XmlView = class_moved(xmlrss.XMLView) +XmlItemView = class_moved(xmlrss.XMLItemView) +XmlRsetView = class_moved(xmlrss.XMLRsetView) +RssView = class_moved(xmlrss.RSSView) +RssItemView = class_moved(xmlrss.RSSItemView) diff -r a721966779be -r cba9f175da2d web/views/bookmark.py --- a/web/views/bookmark.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/bookmark.py Thu May 07 16:42:34 2009 +0200 @@ -12,10 +12,10 @@ from cubicweb.selectors import implements from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem from cubicweb.web import uicfg, action, box, formwidgets -from cubicweb.web.views.baseviews import PrimaryView +from cubicweb.web.views import primary -uicfg.rcategories.set_rtag('primary', 'path', 'subject', 'Bookmark') -uicfg.rwidgets.set_rtag(formwidgets.TextInput, 'path', 'subject', 'Bookmark') +uicfg.rcategories.tag_relation('primary', ('Bookmark', 'path', '*'), 'subject') +uicfg.rwidgets.tag_relation(formwidgets.TextInput, ('Bookmark', 'path', '*'), 'subject') class FollowAction(action.Action): @@ -29,7 +29,7 @@ return self.rset.get_entity(self.row or 0, self.col or 0).actual_url() -class BookmarkPrimaryView(PrimaryView): +class BookmarkPrimaryView(primary.PrimaryView): __select__ = implements('Bookmark') def cell_call(self, row, col): diff -r a721966779be -r cba9f175da2d web/views/boxes.py --- a/web/views/boxes.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/boxes.py Thu May 07 16:42:34 2009 +0200 @@ -53,7 +53,7 @@ else: X, Y = tschema, eschema card = rschema.rproperty(X, Y, 'cardinality')[1] - if not cls.rmode.rtag(rschema, role, X, Y): + if not cls.rmode.get(rschema, role, X, Y): if card in '?1': # by default, suppose link mode if cardinality doesn't allow # more than one relation @@ -64,7 +64,7 @@ else: # link mode by default mode = 'link' - cls.rmode.set_rtag(mode, rschema, role, X, Y) + cls.rmode.tag_relation(mode, (X, rschema, Y), role) @classmethod def relation_mode(cls, rtype, etype, targettype, role='subject'): @@ -72,8 +72,8 @@ to a new entity ('create' mode) or to an existant entity ('link' mode) """ if role == 'subject': - return cls.rmode.rtag(rtype, role, etype, targettype) - return cls.rmode.rtag(rtype, role, targettype, etype) + return cls.rmode.get(rtype, role, etype, targettype) + return cls.rmode.get(rtype, role, targettype, etype) def call(self, view=None, **kwargs): @@ -136,10 +136,10 @@ eschema = entity.e_schema for rschema, teschema, x in self.add_related_schemas(entity): if x == 'subject': - label = 'add %s %s %s %s' % (eschema, rschema, teschema, x) + label = '%s %s %s %s' % (eschema, rschema, teschema, x) url = self.linkto_url(entity, rschema, teschema, 'object') else: - label = 'add %s %s %s %s' % (teschema, rschema, eschema, x) + label = '%s %s %s %s' % (teschema, rschema, eschema, x) url = self.linkto_url(entity, rschema, teschema, 'subject') actions.append(self.mk_action(_(label), url)) return actions diff -r a721966779be -r cba9f175da2d web/views/calendar.py --- a/web/views/calendar.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/calendar.py Thu May 07 16:42:34 2009 +0200 @@ -7,15 +7,13 @@ from datetime import datetime, date, timedelta -from vobject import iCalendar - from logilab.mtconverter import html_escape from cubicweb.interfaces import ICalendarable from cubicweb.selectors import implements from cubicweb.utils import strptime, date_range, todate from cubicweb.view import EntityView -from cubicweb.common.uilib import ajax_replace_url +from cubicweb.web import ajax_replace_url _ = unicode @@ -29,38 +27,43 @@ _('june'), _('july'), _('august'), _('september'), _('october'), _('november'), _('december') ) - + # Calendar views ############################################################## +try: + from vobject import iCalendar -class iCalView(EntityView): - """A calendar view that generates a iCalendar file (RFC 2445) + class iCalView(EntityView): + """A calendar view that generates a iCalendar file (RFC 2445) - Does apply to ICalendarable compatible entities - """ - __select__ = implements(ICalendarable) - need_navigation = False - content_type = 'text/calendar' - title = _('iCalendar') - templatable = False - id = 'ical' + Does apply to ICalendarable compatible entities + """ + __select__ = implements(ICalendarable) + need_navigation = False + content_type = 'text/calendar' + title = _('iCalendar') + templatable = False + id = 'ical' - def call(self): - ical = iCalendar() - for i in range(len(self.rset.rows)): - task = self.complete_entity(i) - event = ical.add('vevent') - event.add('summary').value = task.dc_title() - event.add('description').value = task.dc_description() - if task.start: - event.add('dtstart').value = task.start - if task.stop: - event.add('dtend').value = task.stop + def call(self): + ical = iCalendar() + for i in range(len(self.rset.rows)): + task = self.complete_entity(i) + event = ical.add('vevent') + event.add('summary').value = task.dc_title() + event.add('description').value = task.dc_description() + if task.start: + event.add('dtstart').value = task.start + if task.stop: + event.add('dtend').value = task.stop - buff = ical.serialize() - if not isinstance(buff, unicode): - buff = unicode(buff, self.req.encoding) - self.w(buff) + buff = ical.serialize() + if not isinstance(buff, unicode): + buff = unicode(buff, self.req.encoding) + self.w(buff) + +except ImportError: + pass class hCalView(EntityView): """A calendar view that generates a hCalendar file @@ -99,11 +102,11 @@ self.w('
' % self.req._('from %(date)s' % {'date': self.format_date(task.start)})) self.w('
' % self.req._('to %(date)s' % {'date': self.format_date(task.stop)})) self.w('
to %s'%self.format_date(task.stop)) - + class CalendarLargeItemView(CalendarItemView): id = 'calendarlargeitem' - + class _TaskEntry(object): def __init__(self, task, color, index=0): self.task = task @@ -116,11 +119,12 @@ if self.task.start.hour > 7 and self.task.stop.hour < 20: return True return False - + def is_one_day_task(self): task = self.task return task.start and task.stop and task.start.isocalendar() == task.stop.isocalendar() - + + class OneMonthCal(EntityView): """At some point, this view will probably replace ampm calendars""" id = 'onemonthcal' @@ -179,7 +183,7 @@ min(tstop, lastday)) if not the_dates: continue - + for d in the_dates: d_tasks = dates.setdefault((d.year, d.month, d.day), {}) t_users = d_tasks.setdefault(task, set()) @@ -242,7 +246,7 @@ # output header self.w(u'
' % tuple(self.req._(day) for day in WEEKDAYS)) - + # build calendar for mdate, task_rows in zip(month_dates, days): if mdate.weekday() == 0: @@ -272,7 +276,7 @@ self.w(u'%s%s' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row))) + current_row = [] + current_row.extend([NO_CELL] * (6-day.weekday())) + rql = self.rset.printable_rql() + if day.weekday() != 6: + rows.append(u'%s%s' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row))) + url = self.build_url(rql=rql, vid='calendarmonth', + year=first_day.year, month=first_day.month) + monthlink = u'%s' % (html_escape(url), umonth) + return CALENDAR(self.req) % (monthlink, '\n'.join(rows)) + + def _mk_schedule(self, begin, end, itemvid='calendaritem'): + """private method that gathers information from resultset + and builds calendars according to it + + :param begin: begin of date range + :param end: end of date rangs + :param itemvid: which view to call to render elements in cells + + returns { day1 : { hour : [views] }, + day2 : { hour : [views] } ... } + """ + # put this here since all sub views are calling this method + self.req.add_css('cubicweb.calendar.css') + schedule = {} + for row in xrange(len(self.rset.rows)): + entity = self.entity(row) + infos = u'
' + infos += self.view(itemvid, self.rset, row=row) + infos += u'
' + for date_ in entity.matching_dates(begin, end): + day = date(date_.year, date_.month, date_.day) + try: + dt = time(date_.hour, date_.minute, date_.second) + except AttributeError: + # date instance + dt = time(0, 0, 0) + schedule.setdefault(day, {}) + schedule[day].setdefault(dt, []).append(infos) + return schedule + + + @staticmethod + def get_date_range(day, shift=4): + """returns a couple (begin, end) + + is the first day of current_month - shift + is the last day of current_month + (shift+1) + """ + begin = first_day(previous_month(day, shift)) + end = last_day(next_month(day, shift)) + return begin, end + + def _build_ampm_cells(self, daynum, events): + """create a view without any hourly details. + + :param daynum: day of the built cell + :param events: dictionnary with all events classified by hours""" + # split events according am/pm + am_events = [event for e_time, e_list in events.iteritems() + if 0 <= e_time.hour < 12 + for event in e_list] + pm_events = [event for e_time, e_list in events.iteritems() + if 12 <= e_time.hour < 24 + for event in e_list] + # format each am/pm cell + if am_events: + am_content = AMPM_CONTENT % ("amCell", "am", '\n'.join(am_events)) + else: + am_content = AMPM_EMPTY % ("amCell", "am") + if pm_events: + pm_content = AMPM_CONTENT % ("pmCell", "pm", '\n'.join(pm_events)) + else: + pm_content = AMPM_EMPTY % ("pmCell", "pm") + return am_content, pm_content + + + +class YearCalendarView(_CalendarView): + id = 'calendaryear' + title = _('calendar (year)') + + def call(self, year=None, month=None): + """this view renders a 3x3 calendars' table""" + year = year or int(self.req.form.get('year', date.today().year)) + month = month or int(self.req.form.get('month', date.today().month)) + center_date = date(year, month, 1) + begin, end = self.get_date_range(day=center_date) + schedule = self._mk_schedule(begin, end) + self.w(self.nav_header(center_date)) + calendars = tuple(self.build_calendars(schedule, begin, end)) + self.w(SMALL_CALENDARS_PAGE % calendars) + + +class SemesterCalendarView(_CalendarView): + """this view renders three semesters as three rows of six columns, + one column per month + """ + id = 'calendarsemester' + title = _('calendar (semester)') + + def call(self, year=None, month=None): + year = year or int(self.req.form.get('year', date.today().year)) + month = month or int(self.req.form.get('month', date.today().month)) + begin = previous_month(date(year, month, 1), 2) + end = next_month(date(year, month, 1), 3) + schedule = self._mk_schedule(begin, end) + self.w(self.nav_header(date(year, month, 1), 1, 6)) + self.w(u'
') + w(u'
%s%s%s%s%s%s%s
' % classes) self.w(u'
' % classes) self.w(u'
%s
' % celldate.day) - + if len(self.rset.column_types(0)) == 1: etype = list(self.rset.column_types(0))[0] url = self.build_url(vid='creation', etype=etype, @@ -314,7 +318,7 @@ __select__ = implements(ICalendarable) need_navigation = False title = _('one week') - + def call(self): self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') ) self.req.add_css('cubicweb.calendar.css') @@ -327,7 +331,7 @@ if 'week' in self.req.form: week = int(self.req.form['week']) else: - week = _today.isocalendar()[1] + week = _today.isocalendar()[1] # week - 1 since we get week number > 0 while we want it to start from 0 first_day_of_week = todate(strptime('%s-%s-1' % (year, week - 1), '%Y-%U-%w')) lastday = first_day_of_week + timedelta(6) @@ -359,16 +363,16 @@ min(tstop, lastday)) if not the_dates: continue - + if task not in task_colors: task_colors[task] = colors[next_color_index] next_color_index = (next_color_index+1) % len(colors) - + for d in the_dates: day = d.weekday() - task_descr = _TaskEntry(task, task_colors[task]) + task_descr = _TaskEntry(task, task_colors[task]) dates[day].append(task_descr) - + self.w(u'
') # build schedule self.w(u'') @@ -391,7 +395,7 @@ else: self.w(u'' % (self.req._(day), self.format_date(wdate))) self.w(u'') - + # build week calendar self.w(u'') self.w(u'') - + for i, day in enumerate(WEEKDAYS): wdate = first_day_of_week + timedelta(i) classes = "" @@ -423,7 +427,7 @@ self.w(u'
'% extra) for h in range(8, 20): self.w(u'
'%((h-7)*8)) - self.w(u'
') + self.w(u'
') if dates[i]: self._build_calendar_cell(wdate, dates[i]) self.w(u'') @@ -432,7 +436,7 @@ self.w(u'
%s
%s
') # column for hours @@ -399,9 +403,9 @@ for h in range(8, 20): self.w(u'
'%extra) self.w(u'%02d:00'%h) - self.w(u'
') + self.w(u'') self.w(u'
') self.w(u'
') self.w(u'
 
') - + 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()] wholeday_tasks = [t for t in task_descrs if not t.is_one_day_task()] @@ -474,7 +478,7 @@ stop_hour = min(20, task.stop.hour) if stop_hour < 20: stop_min = task.stop.minute - + height = 100.0*(stop_hour+stop_min/60.0-start_hour-start_min/60.0)/(20-8) top = 100.0*(start_hour+start_min/60.0-8)/(20-8) left = width*task_desc.index @@ -504,7 +508,7 @@ self.w(u'
') self.w(u'') - + def _prevnext_links(self, curdate): prevdate = curdate - timedelta(7) nextdate = curdate + timedelta(7) diff -r a721966779be -r cba9f175da2d web/views/cwproperties.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/cwproperties.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,343 @@ +"""Specific views for CWProperty + +:organization: Logilab +:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" +_ = unicode + +from logilab.mtconverter import html_escape + +from logilab.common.decorators import cached + +from cubicweb import UnknownProperty +from cubicweb.selectors import (one_line_rset, none_rset, implements, + match_user_groups) +from cubicweb.view import StartupView +from cubicweb.web import uicfg, stdmsgs +from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn +from cubicweb.web.formfields import FIELDS, StringField +from cubicweb.web.formwidgets import Select, Button, SubmitButton +from cubicweb.web.views import primary + + +# some string we want to be internationalizable for nicer display of eproperty +# groups +_('navigation') +_('ui') +_('actions') +_('boxes') +_('components') +_('contentnavigation') +_('navigation.combobox-limit') +_('navigation.page-size') +_('navigation.related-limit') +_('navigation.short-line-size') +_('ui.date-format') +_('ui.datetime-format') +_('ui.default-text-format') +_('ui.fckeditor') +_('ui.float-format') +_('ui.language') +_('ui.time-format') +_('open all') +_('ui.main-template') +_('ui.site-title') +_('ui.encoding') +_('category') + + +def make_togglable_link(nodeid, label, cookiename): + """builds a HTML link that switches the visibility & remembers it""" + action = u"javascript: toggleVisibility('%s', '%s')" % \ + (nodeid, cookiename) + return u'%s' % (action, label) + +def css_class(someclass): + return someclass and 'class="%s"' % someclass or '' + + +class CWPropertyPrimaryView(primary.PrimaryView): + __select__ = implements('CWProperty') + skip_none = False + + +class SystemEPropertiesForm(FormViewMixIn, StartupView): + id = 'systemepropertiesform' + __select__ = none_rset() & match_user_groups('managers') + + title = _('site configuration') + category = 'startupview' + + def linkable(self): + return True + + def url(self): + """return the url associated with this view. We can omit rql here""" + return self.build_url('view', vid=self.id) + + def _cookie_name(self, somestr): + return str('%s_property_%s' % (self.config.appid, somestr)) + + def _group_status(self, group, default=u'hidden'): + """return css class name 'hidden' (collapsed), or '' (open)""" + cookies = self.req.get_cookie() + cookiename = self._cookie_name(group) + cookie = cookies.get(cookiename) + if cookie is None: + cookies[cookiename] = default + self.req.set_cookie(cookies, cookiename, maxage=None) + status = default + else: + status = cookie.value + return status + + def call(self, **kwargs): + """The default view representing the application's index""" + self.req.add_js('cubicweb.preferences.js') + self.req.add_css('cubicweb.preferences.css') + vreg = self.vreg + values = self.defined_keys + groupedopts = {} + mainopts = {} + # "self.id=='systemepropertiesform'" to skip site wide properties on + # user's preference but not site's configuration + for key in vreg.user_property_keys(self.id=='systemepropertiesform'): + parts = key.split('.') + if parts[0] in vreg: + # appobject configuration + reg, oid, propid = parts + groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key) + else: + mainopts.setdefault(parts[0], []).append(key) + # precompute form to consume error message + for group, keys in mainopts.items(): + mainopts[group] = self.form(keys, True) + for group, objects in groupedopts.items(): + for oid, keys in objects.items(): + groupedopts[group][oid] = self.form(keys, True) + w = self.w + req = self.req + _ = req._ + w(u'

%s

\n' % _(self.title)) + # we don't want this in each sub-forms + w(u'
%s
' % self.req._('validating...')) + for label, group, form in sorted((_(g), g, f) + for g, f in mainopts.iteritems()): + status = css_class(self._group_status(group)) + w(u'

%s

\n' % + (make_togglable_link('fieldset_' + group, label, + self._cookie_name(group)))) + w(u'
' % (group, status)) + w(form) + w(u'
') + for label, group, objects in sorted((_(g), g, o) + for g, o in groupedopts.iteritems()): + status = css_class(self._group_status(group)) + w(u'

%s

\n' % + (make_togglable_link('fieldset_' + group, label, + self._cookie_name(group)))) + w(u'
' % (group, status)) + for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f) + for o, f in objects.iteritems()): + w(u'
') + w(u'%s\n' % label) + docmsgid = '%s_%s_description' % (group, oid) + doc = _(docmsgid) + if doc != docmsgid: + w(u'

%s

' % html_escape(doc)) + w(form) + w(u'
') + w(u'
') + + @property + @cached + def eprops_rset(self): + return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, ' + 'P value V, NOT P for_user U') + + @property + def defined_keys(self): + values = {} + for i, entity in enumerate(self.eprops_rset.entities()): + values[entity.pkey] = i + return values + + def entity_for_key(self, key): + values = self.defined_keys + if key in values: + entity = self.eprops_rset.get_entity(values[key], 0) + else: + entity = self.vreg.etype_class('CWProperty')(self.req, None, None) + entity.eid = self.req.varmaker.next() + entity['pkey'] = key + entity['value'] = self.vreg.property_value(key) + return entity + + def form(self, keys, splitlabel=False): + buttons = [SubmitButton(), + Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] + form = CompositeForm(self.req, domid=None, action=self.build_url(), + form_buttons=buttons, + submitmsg=self.req._('changes applied')) + path = self.req.relative_path() + if '?' in path: + path, params = path.split('?', 1) + form.form_add_hidden('__redirectparams', params) + form.form_add_hidden('__redirectpath', path) + for key in keys: + self.form_row(form, key, splitlabel) + return form.form_render(display_progress_div=False) + + def form_row(self, form, key, splitlabel): + entity = self.entity_for_key(key) + if splitlabel: + label = key.split('.')[-1] + else: + label = key + subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False) + subform.append_field(PropertyValueField(name='value', label=label, + eidparam=True)) + subform.vreg = self.vreg + subform.form_add_hidden('pkey', key, eidparam=True) + form.form_add_subform(subform) + return subform + + +def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs): + return req.user.eid == rset[row or 0][col] + + +class EPropertiesForm(SystemEPropertiesForm): + id = 'epropertiesform' + __select__ = ( + # we don't want guests to be able to come here + match_user_groups('users', 'managers') & + (none_rset() | ((one_line_rset() & is_user_prefs) & + (one_line_rset() & match_user_groups('managers')))) + ) + + title = _('preferences') + + @property + def user(self): + if self.rset is None: + return self.req.user + return self.rset.get_entity(self.row or 0, self.col or 0) + + @property + @cached + def eprops_rset(self): + return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,' + 'P for_user U, U eid %(x)s', {'x': self.user.eid}) + + def form_row(self, form, key, splitlabel): + subform = super(EPropertiesForm, self).form_row(form, key, splitlabel) + # if user is in the managers group and the property is being created, + # we have to set for_user explicitly + if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'): + subform.form_add_hidden('for_user', self.user.eid, eidparam=True) + + +# eproperty form objects ###################################################### + +class PlaceHolderWidget(object): + + def render(self, form, field): + domid = form.context[field]['id'] + # empty span as well else html validation fail (label is refering to + # this id) + return '
%s
' % ( + domid, domid, form.req._('select a key first')) + + +class NotEditableWidget(object): + def __init__(self, value, msg=None): + self.value = value + self.msg = msg + + def render(self, form, field): + domid = form.context[field]['id'] + value = '%s' % (domid, self.value) + if self.msg: + value + '
%s
' % self.msg + return value + + +class PropertyKeyField(StringField): + """specific field for CWProperty.pkey to set the value widget according to + the selected key + """ + widget = Select + + def render(self, form, renderer): + wdg = self.get_widget(form) + wdg.attrs['tabindex'] = form.req.next_tabindex() + wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( + form.edited_entity.eid, form.req.next_tabindex()) + return wdg.render(form, self) + + def vocabulary(self, form): + entity = form.edited_entity + _ = form.req._ + if entity.has_eid(): + return [(_(entity.pkey), entity.pkey)] + # key beginning with 'system.' should usually not be edited by hand + choices = entity.vreg.user_property_keys() + return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices)) + + +class PropertyValueField(StringField): + """specific field for CWProperty.value which will be different according to + the selected key type and vocabulary information + """ + widget = PlaceHolderWidget + + def render(self, form, renderer=None, tabindex=None): + wdg = self.get_widget(form) + if tabindex is not None: + wdg.attrs['tabindex'] = tabindex + return wdg.render(form, self) + + def form_init(self, form): + entity = form.edited_entity + if not (entity.has_eid() or 'pkey' in entity): + # no key set yet, just include an empty div which will be filled + # on key selection + return + try: + pdef = form.vreg.property_info(entity.pkey) + except UnknownProperty, ex: + self.warning('%s (you should probably delete that property ' + 'from the database)', ex) + msg = form.req._('you should probably delete that property') + self.widget = NotEditableWidget(entity.printable_value('value'), + '%s (%s)' % (msg, ex)) + if entity.pkey.startswith('system.'): + msg = form.req._('value associated to this key is not editable ' + 'manually') + self.widget = NotEditableWidget(entity.printable_value('value'), msg) + # XXX race condition when used from CWPropertyForm, should not rely on + # instance attributes + self.initial = pdef['default'] + self.help = pdef['help'] + vocab = pdef['vocabulary'] + if vocab is not None: + if callable(vocab): + # list() just in case its a generator function + self.choices = list(vocab(form.req)) + else: + self.choices = vocab + wdg = Select() + else: + wdg = FIELDS[pdef['type']].widget() + if pdef['type'] == 'Boolean': + self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')] + elif pdef['type'] in ('Float', 'Int'): + wdg.attrs.setdefault('size', 3) + self.widget = wdg + + +uicfg.rfields.tag_relation(PropertyKeyField, ('CWProperty', 'pkey', '*'), 'subject') +uicfg.rfields.tag_relation(PropertyValueField, ('CWProperty', 'value', '*'), 'subject') diff -r a721966779be -r cba9f175da2d web/views/cwuser.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/cwuser.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,89 @@ +"""Specific views for users + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from logilab.mtconverter import html_escape + +from cubicweb.selectors import one_line_rset, implements, match_user_groups +from cubicweb.view import EntityView +from cubicweb.web import uicfg, action +from cubicweb.web.views import primary + + +uicfg.rcategories.tag_relation('secondary', ('CWUser', 'firstname', '*'), 'subject') +uicfg.rcategories.tag_relation('secondary', ('CWUser', 'surname', '*'), 'subject') +uicfg.rcategories.tag_relation('metadata', ('CWUser', 'last_login_time', '*'), 'subject') +uicfg.rcategories.tag_relation('primary', ('CWUser', 'in_group', '*'), 'subject') +uicfg.rcategories.tag_relation('generated', ('*', 'owned_by', 'CWUser'), 'object') +uicfg.rcategories.tag_relation('generated', ('*', 'created_by', 'CWUser'), 'object') +uicfg.rcategories.tag_relation('metadata', ('*', 'bookmarked_by', 'CWUser'), 'object') +uicfg.rmode.tag_relation('create', ('*', 'in_group', 'CWGroup'), 'object') +uicfg.rmode.tag_relation('link', ('*', 'owned_by', 'CWUser'), 'object') +uicfg.rmode.tag_relation('link', ('*', 'created_by', 'CWUser'), 'object') +uicfg.rmode.tag_relation('create', ('*', 'bookmarked_by', 'CWUser'), 'object') +uicfg.rdisplay.tag_attribute({}, 'CWUser', 'firstname') +uicfg.rdisplay.tag_attribute({}, 'CWUser', 'surname') + + +class UserPreferencesEntityAction(action.Action): + id = 'prefs' + __select__ = (one_line_rset() & implements('CWUser') & + match_user_groups('owners', 'managers')) + + title = _('preferences') + category = 'mainactions' + + def url(self): + login = self.rset.get_entity(self.row or 0, self.col or 0).login + return self.build_url('cwuser/%s'%login, vid='epropertiesform') + + +class CWUserPrimaryView(primary.PrimaryView): + __select__ = implements('CWUser') + + def content_title(self, entity): + return entity.name() + + +class FoafView(EntityView): + id = 'foaf' + __select__ = implements('CWUser') + + title = _('foaf') + templatable = False + content_type = 'text/xml' + + def call(self): + self.w(u''' + '''% self.req.encoding) + for i in xrange(self.rset.rowcount): + self.cell_call(i, 0) + self.w(u'\n') + + def cell_call(self, row, col): + entity = self.complete_entity(row, col) + self.w(u''' + + + ''' % (entity.absolute_url(), entity.absolute_url())) + self.w(u'\n' % entity.eid) + self.w(u'%s\n' % html_escape(entity.dc_long_title())) + if entity.surname: + self.w(u'%s\n' + % html_escape(entity.surname)) + if entity.firstname: + self.w(u'%s\n' + % html_escape(entity.firstname)) + emailaddr = entity.get_email() + if emailaddr: + self.w(u'%s\n' % html_escape(emailaddr)) + self.w(u'\n') + +from logilab.common.deprecation import class_renamed +EUserPrimaryView = class_renamed('EUserPrimaryView', CWUserPrimaryView) diff -r a721966779be -r cba9f175da2d web/views/editforms.py --- a/web/views/editforms.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/editforms.py Thu May 07 16:42:34 2009 +0200 @@ -18,7 +18,7 @@ from cubicweb.utils import make_uid from cubicweb.view import EntityView from cubicweb.common import tags -from cubicweb.web import stdmsgs +from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer, @@ -33,7 +33,7 @@ return u'%s:%s:%s' % (eid, rtype, reid) return u'%s:%s:%s' % (reid, rtype, eid) -def toggable_relation_link(eid, nodeid, label='x'): +def toggleable_relation_link(eid, nodeid, label='x'): """return javascript snippet to delete/undelete a relation between two entities """ @@ -92,33 +92,42 @@ "'%(eid)s', '%(divid)s', %(reload)s);") ondblclick = "showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')" - def cell_call(self, row, col, rtype=None, role='subject', reload=False): + def cell_call(self, row, col, rtype=None, role='subject', reload=False, + vid='autolimited'): """display field to edit entity's `rtype` relation on double-click""" + rschema = self.schema.rschema(rtype) entity = self.entity(row, col) - if getattr(entity, rtype) is None: - value = self.req._('not specified') + if rschema.is_final(): + if getattr(entity, rtype) is None: + value = self.req._('not specified') + else: + value = entity.printable_value(rtype) else: - value = entity.printable_value(rtype) + rset = entity.related(rtype, role) + value = self.view(vid, rset, 'null') if not entity.has_perm('update'): self.w(value) return eid = entity.eid - edit_key = make_uid('%s-%s' % (rtype, eid)) - divid = 'd%s' % edit_key - reload = dumps(reload) - buttons = [SubmitButton(stdmsgs.BUTTON_OK, cwaction='apply'), + divid = 'd%s' % make_uid('%s-%s' % (rtype, eid)) + event_data = {'divid' : divid, 'eid' : eid, 'rtype' : rtype, + 'reload' : dumps(reload)} + buttons = [SubmitButton(stdmsgs.BUTTON_OK), Button(stdmsgs.BUTTON_CANCEL, - onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (eid, rtype, divid))] + onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % ( + eid, rtype, divid))] form = self.vreg.select_object('forms', 'edition', self.req, self.rset, row=row, col=col, form_buttons=buttons, domid='%s-form' % divid, action='#', cssstyle='display: none', - onsubmit=self.onsubmit % locals()) + onsubmit=self.onsubmit % event_data) + form.form_add_hidden(u'__maineid', entity.eid) renderer = FormRenderer(display_label=False, display_help=False, - display_fields=(rtype,), button_bar_class='buttonbar', + display_fields=[(rtype, role)], + table_class='', button_bar_class='buttonbar', display_progress_div=False) self.w(tags.div(value, klass='editableField', id=divid, - ondblclick=self.ondblclick % locals())) + ondblclick=self.ondblclick % event_data)) self.w(form.form_render(renderer=renderer)) @@ -219,19 +228,28 @@ """fetch and render the form""" # make a copy of entity to avoid altering the entity in the # request's cache. + entity.complete() self.newentity = copy(entity) - self.copying = self.newentity.eid - self.newentity.eid = None + self.copying = entity + self.initialize_varmaker() + self.newentity.eid = self.varmaker.next() self.w(u'\n' % self.req._('Please note that this is only a shallow copy')) - super(CopyFormView, self).render_form(entity) + super(CopyFormView, self).render_form(self.newentity) del self.newentity def init_form(self, form, entity): """customize your form before rendering here""" super(CopyFormView, self).init_form(form, entity) if entity.eid == self.newentity.eid: - form.form_add_hidden('__cloned_eid', self.copying, eidparam=True) + form.form_add_hidden(eid_param('__cloned_eid', entity.eid), + self.copying.eid) + for rschema, _, role in form.relations_by_category(form.attrcategories, + 'add'): + if not rschema.is_final(): + # ensure relation cache is filed + rset = self.copying.related(rschema, role) + self.newentity.set_related_cache(rschema, role, rset) def submited_message(self): """return the message that will be displayed on successful edition""" @@ -240,12 +258,14 @@ class TableEditForm(CompositeForm): id = 'muledit' - onsubmit = "return validateForm('entityForm', null);" + domid = 'entityForm' + onsubmit = "return validateForm('%s', null);" % domid form_buttons = [SubmitButton(_('validate modifications on selected items')), ResetButton(_('revert changes'))] - def __init__(self, *args, **kwargs): - super(TableEditForm, self).__init__(*args, **kwargs) + def __init__(self, req, rset, **kwargs): + kwargs.setdefault('__redirectrql', rset.printable_rql()) + super(TableEditForm, self).__init__(req, rset, **kwargs) for row in xrange(len(self.rset)): form = self.vreg.select_object('forms', 'edition', self.req, self.rset, row=row, attrcategories=('primary',), @@ -307,7 +327,7 @@ def add_hiddens(self, form, entity, peid, rtype, role): # to ease overriding (see cubes.vcsfile.views.forms for instance) - if self.keep_entity(entity, peid, rtype): + if self.keep_entity(form, entity, peid, rtype): if entity.has_eid(): rval = entity.eid else: @@ -316,14 +336,13 @@ form.form_add_hidden(name='%s:%s' % (rtype, peid), value=entity.eid, id='rel-%s-%s-%s' % (peid, rtype, entity.eid)) - def keep_entity(self, entity, peid, rtype): + def keep_entity(self, form, entity, peid, rtype): if not entity.has_eid(): return True # are we regenerating form because of a validation error ? - erroneous_post = self.req.data.get('formvalues') if erroneous_post: - cdvalues = self.req.list_form_param('%s:%s' % (rtype, peid), - erroneous_post) + cdvalues = self.req.list_form_param(eid_param(rtype, peid), + form.form_previous_values) if unicode(entity.eid) not in cdvalues: return False return True diff -r a721966779be -r cba9f175da2d web/views/editviews.py --- a/web/views/editviews.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/editviews.py Thu May 07 16:42:34 2009 +0200 @@ -120,7 +120,7 @@ pending_inserts = self.req.get_pending_inserts(eid) rtype = rschema.type form = self.vreg.select_object('forms', 'edition', self.req, - entity=entity) + self.rset, entity=entity) field = form.field_by_name(rschema, target, entity.e_schema) limit = self.req.property_value('navigation.combobox-limit') for eview, reid in form.form_field_vocabulary(field, limit): diff -r a721966779be -r cba9f175da2d web/views/emailaddress.py --- a/web/views/emailaddress.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/emailaddress.py Thu May 07 16:42:34 2009 +0200 @@ -10,17 +10,16 @@ from cubicweb.selectors import implements from cubicweb.common import Unauthorized -from cubicweb.web.views import baseviews +from cubicweb.web.views import baseviews, primary - -class EmailAddressPrimaryView(baseviews.PrimaryView): +class EmailAddressPrimaryView(primary.PrimaryView): __select__ = implements('EmailAddress') def cell_call(self, row, col, skipeids=None): self.skipeids = skipeids super(EmailAddressPrimaryView, self).cell_call(row, col) - def render_entity_attributes(self, entity, siderelations): + def render_entity_attributes(self, entity): self.w(u'

') entity.view('oneline', w=self.w) if not entity.canonical: @@ -53,7 +52,7 @@ emailofstr = ', '.join(e.view('oneline') for e in emailof) self.field(display_name(self.req, 'use_email', 'object'), emailofstr) - def render_entity_relations(self, entity, siderelations): + def render_entity_relations(self, entity): for i, email in enumerate(entity.related_emails(self.skipeids)): self.w(u'
' % (i%2 and 'even' or 'odd')) email.view('oneline', w=self.w, contexteid=entity.eid) @@ -64,7 +63,8 @@ __select__ = implements('EmailAddress') id = 'shortprimary' title = None # hidden view - def render_entity_attributes(self, entity, siderelations): + + def render_entity_attributes(self, entity): self.w(u'
') entity.view('oneline', w=self.w) self.w(u'
') diff -r a721966779be -r cba9f175da2d web/views/eproperties.py --- a/web/views/eproperties.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,329 +0,0 @@ -"""Specific views for CWProperty - -:organization: Logilab -:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" -__docformat__ = "restructuredtext en" - -from logilab.mtconverter import html_escape - -from logilab.common.decorators import cached - -from cubicweb import UnknownProperty -from cubicweb.selectors import (one_line_rset, none_rset, implements, - match_user_groups, entity_implements) -from cubicweb.utils import UStringIO -from cubicweb.view import StartupView -from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, uicfg -from cubicweb.web.views import baseviews -from cubicweb.web import stdmsgs -from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn -from cubicweb.web.formfields import FIELDS, StringField -from cubicweb.web.formwidgets import Select, Button, SubmitButton - -_ = unicode - -# some string we want to be internationalizable for nicer display of eproperty -# groups -_('navigation') -_('ui') -_('actions') -_('boxes') -_('components') -_('contentnavigation') - - -def make_togglable_link(nodeid, label, cookiename): - """builds a HTML link that switches the visibility & remembers it""" - action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \ - (nodeid, cookiename) - return u'%s' % (action, label) - -def css_class(someclass): - return someclass and 'class="%s"' % someclass or '' - - -class CWPropertyPrimaryView(baseviews.PrimaryView): - __select__ = implements('CWProperty') - skip_none = False - - -class SystemEPropertiesForm(FormViewMixIn, StartupView): - id = 'systemepropertiesform' - __select__ = none_rset() & match_user_groups('managers') - - title = _('site configuration') - category = 'startupview' - - def linkable(self): - return True - - def url(self): - """return the url associated with this view. We can omit rql here""" - return self.build_url('view', vid=self.id) - - def _cookie_name(self, somestr): - return str('%s_property_%s' % (self.config.appid, somestr)) - - def _group_status(self, group, default=u'hidden'): - """return css class name 'hidden' (collapsed), or '' (open)""" - cookies = self.req.get_cookie() - cookiename = self._cookie_name(group) - cookie = cookies.get(cookiename) - if cookie is None: - cookies[cookiename] = default - self.req.set_cookie(cookies, cookiename, maxage=None) - status = default - else: - status = cookie.value - return status - - def call(self, **kwargs): - """The default view representing the application's index""" - self.req.add_js('cubicweb.preferences.js') - self.req.add_css('cubicweb.preferences.css') - vreg = self.vreg - values = self.defined_keys - groupedopts = {} - mainopts = {} - # "self.id=='systemepropertiesform'" to skip site wide properties on - # user's preference but not site's configuration - for key in vreg.user_property_keys(self.id=='systemepropertiesform'): - parts = key.split('.') - if parts[0] in vreg: - # appobject configuration - reg, oid, propid = parts - groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key) - else: - mainopts.setdefault(parts[0], []).append(key) - # precompute form to consume error message - for group, keys in mainopts.items(): - mainopts[group] = self.form(keys, True) - for group, objects in groupedopts.items(): - for oid, keys in objects.items(): - groupedopts[group][oid] = self.form(keys, True) - w = self.w - req = self.req - _ = req._ - w(u'

%s

\n' % _(self.title)) - # we don't want this in each sub-forms - w(u'
%s
' % self.req._('validating...')) - for label, group, form in sorted((_(g), g, f) - for g, f in mainopts.iteritems()): - status = css_class(self._group_status(group)) - w(u'

%s

\n' % - (make_togglable_link('fieldset_' + group, label, - self._cookie_name(group)))) - w(u'
' % (group, status)) - w(form) - w(u'
') - for label, group, objects in sorted((_(g), g, o) - for g, o in groupedopts.iteritems()): - status = css_class(self._group_status(group)) - w(u'

%s

\n' % - (make_togglable_link('fieldset_' + group, label, - self._cookie_name(group)))) - w(u'
' % (group, status)) - for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f) - for o, f in objects.iteritems()): - w(u'
') - w(u'%s\n' % label) - docmsgid = '%s_%s_description' % (group, oid) - doc = _(docmsgid) - if doc != docmsgid: - w(u'

%s

' % html_escape(doc)) - w(form) - w(u'
') - w(u'
') - - @property - @cached - def eprops_rset(self): - return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, ' - 'P value V, NOT P for_user U') - - @property - def defined_keys(self): - values = {} - for i, entity in enumerate(self.eprops_rset.entities()): - values[entity.pkey] = i - return values - - def entity_for_key(self, key): - values = self.defined_keys - if key in values: - entity = self.eprops_rset.get_entity(values[key], 0) - else: - entity = self.vreg.etype_class('CWProperty')(self.req, None, None) - entity.eid = self.req.varmaker.next() - entity['pkey'] = key - entity['value'] = self.vreg.property_value(key) - return entity - - def form(self, keys, splitlabel=False): - buttons = [SubmitButton(), - Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] - form = CompositeForm(self.req, domid=None, action=self.build_url(), - form_buttons=buttons, - submitmsg=self.req._('changes applied')) - path = self.req.relative_path() - if '?' in path: - path, params = path.split('?', 1) - form.form_add_hidden('__redirectparams', params) - form.form_add_hidden('__redirectpath', path) - for key in keys: - self.form_row(form, key, splitlabel) - return form.form_render(display_progress_div=False) - - def form_row(self, form, key, splitlabel): - entity = self.entity_for_key(key) - if splitlabel: - label = key.split('.')[-1] - else: - label = key - subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False) - subform.append_field(PropertyValueField(name='value', label=label, - eidparam=True)) - subform.vreg = self.vreg - subform.form_add_hidden('pkey', key, eidparam=True) - form.form_add_subform(subform) - return subform - - -def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs): - return req.user.eid == rset[row or 0][col] - - -class EPropertiesForm(SystemEPropertiesForm): - id = 'epropertiesform' - __select__ = ( - # we don't want guests to be able to come here - match_user_groups('users', 'managers') & - (none_rset() | ((one_line_rset() & is_user_prefs) & - (one_line_rset() & match_user_groups('managers')))) - ) - - title = _('preferences') - - @property - def user(self): - if self.rset is None: - return self.req.user - return self.rset.get_entity(self.row or 0, self.col or 0) - - @property - @cached - def eprops_rset(self): - return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,' - 'P for_user U, U eid %(x)s', {'x': self.user.eid}) - - def form_row(self, form, key, splitlabel): - subform = super(EPropertiesForm, self).form_row(form, key, splitlabel) - # if user is in the managers group and the property is being created, - # we have to set for_user explicitly - if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'): - subform.form_add_hidden('for_user', self.user.eid, eidparam=True) - - -# eproperty form objects ###################################################### - -class PlaceHolderWidget(object): - - def render(self, form, field): - domid = form.context[field]['id'] - # empty span as well else html validation fail (label is refering to - # this id) - return '
%s
' % ( - domid, domid, form.req._('select a key first')) - - -class NotEditableWidget(object): - def __init__(self, value, msg=None): - self.value = value - self.msg = msg - - def render(self, form, field): - domid = form.context[field]['id'] - value = '%s' % (domid, self.value) - if self.msg: - value + '
%s
' % self.msg - return value - - -class PropertyKeyField(StringField): - """specific field for CWProperty.pkey to set the value widget according to - the selected key - """ - widget = Select - - def render(self, form, renderer): - wdg = self.get_widget(form) - wdg.attrs['tabindex'] = form.req.next_tabindex() - wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( - form.edited_entity.eid, form.req.next_tabindex()) - return wdg.render(form, self) - - def vocabulary(self, form): - entity = form.edited_entity - _ = form.req._ - if entity.has_eid(): - return [(_(entity.pkey), entity.pkey)] - # key beginning with 'system.' should usually not be edited by hand - choices = entity.vreg.user_property_keys() - return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices)) - - -class PropertyValueField(StringField): - """specific field for CWProperty.value which will be different according to - the selected key type and vocabulary information - """ - widget = PlaceHolderWidget - - def render(self, form, renderer=None, tabindex=None): - wdg = self.get_widget(form) - if tabindex is not None: - wdg.attrs['tabindex'] = tabindex - return wdg.render(form, self) - - def form_init(self, form): - entity = form.edited_entity - if not (entity.has_eid() or 'pkey' in entity): - # no key set yet, just include an empty div which will be filled - # on key selection - return - try: - pdef = form.vreg.property_info(entity.pkey) - except UnknownProperty, ex: - self.warning('%s (you should probably delete that property ' - 'from the database)', ex) - msg = form.req._('you should probably delete that property') - self.widget = NotEditableWidget(entity.printable_value('value'), - '%s (%s)' % (msg, ex)) - if entity.pkey.startswith('system.'): - msg = form.req._('value associated to this key is not editable ' - 'manually') - self.widget = NotEditableWidget(entity.printable_value('value'), msg) - # XXX race condition when used from CWPropertyForm, should not rely on - # instance attributes - self.initial = pdef['default'] - self.help = pdef['help'] - vocab = pdef['vocabulary'] - if vocab is not None: - if callable(vocab): - # list() just in case its a generator function - self.choices = list(vocab(form.req)) - else: - self.choices = vocab - wdg = Select() - else: - wdg = FIELDS[pdef['type']].widget() - if pdef['type'] == 'Boolean': - self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')] - elif pdef['type'] in ('Float', 'Int'): - wdg.attrs.setdefault('size', 3) - self.widget = wdg - -uicfg.rfields.set_rtag(PropertyKeyField, 'pkey', 'subject', 'CWProperty') -uicfg.rfields.set_rtag(PropertyValueField, 'value', 'subject', 'CWProperty') - diff -r a721966779be -r cba9f175da2d web/views/euser.py --- a/web/views/euser.py Thu May 07 16:33:22 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -"""Specific views for users - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" -__docformat__ = "restructuredtext en" - -from logilab.mtconverter import html_escape - -from cubicweb.selectors import one_line_rset, implements, match_user_groups -from cubicweb.view import EntityView -from cubicweb.web import uicfg, action -from cubicweb.web.views.baseviews import PrimaryView - - -uicfg.rcategories.set_rtag('secondary', 'firstname', 'subject', 'CWUser') -uicfg.rcategories.set_rtag('secondary', 'surname', 'subject', 'CWUser') -uicfg.rcategories.set_rtag('metadata', 'last_login_time', 'subject', 'CWUser') -uicfg.rcategories.set_rtag('primary', 'in_group', 'subject', 'CWUser') -uicfg.rcategories.set_rtag('generated', 'owned_by', 'object', otype='CWUser') -uicfg.rcategories.set_rtag('generated', 'created_by', 'object', otype='CWUser') -uicfg.rcategories.set_rtag('metadata', 'bookmarked_by', 'object', otype='CWUser') -uicfg.rinlined.set_rtag(True, 'use_email', 'subject', 'CWUser') -uicfg.rmode.set_rtag('create', 'in_group', 'subject', 'CWGroup') -uicfg.rmode.set_rtag('link', 'owned_by', 'object', 'CWUser') -uicfg.rmode.set_rtag('link', 'created_by', 'object', 'CWUser') -uicfg.rmode.set_rtag('create', 'bookmarked_by', 'object', 'CWUser') - - -class UserPreferencesEntityAction(action.Action): - id = 'prefs' - __select__ = (one_line_rset() & implements('CWUser') & - match_user_groups('owners', 'managers')) - - title = _('preferences') - category = 'mainactions' - - def url(self): - login = self.rset.get_entity(self.row or 0, self.col or 0).login - return self.build_url('euser/%s'%login, vid='epropertiesform') - - -class CWUserPrimaryView(PrimaryView): - __select__ = implements('CWUser') - - skip_attrs = ('firstname', 'surname') - - def iter_relations(self, entity): - # don't want to display user's entities - for rschema, targetschemas, x in super(CWUserPrimaryView, self).iter_relations(entity): - if x == 'object' and rschema.type in ('owned_by', 'for_user'): - continue - yield rschema, targetschemas, x - - def content_title(self, entity): - return entity.name() - - def is_side_related(self, rschema, eschema): - return rschema.type in ['interested_in', 'tags', - 'todo_by', 'bookmarked_by', - - ] -class FoafView(EntityView): - id = 'foaf' - __select__ = implements('CWUser') - - title = _('foaf') - templatable = False - content_type = 'text/xml' - - def call(self): - self.w(u''' - '''% self.req.encoding) - for i in xrange(self.rset.rowcount): - self.cell_call(i, 0) - self.w(u'\n') - - def cell_call(self, row, col): - entity = self.complete_entity(row, col) - self.w(u''' - - - ''' % (entity.absolute_url(), entity.absolute_url())) - self.w(u'\n' % entity.eid) - self.w(u'%s\n' % html_escape(entity.dc_long_title())) - if entity.surname: - self.w(u'%s\n' - % html_escape(entity.surname)) - if entity.firstname: - self.w(u'%s\n' - % html_escape(entity.firstname)) - emailaddr = entity.get_email() - if emailaddr: - self.w(u'%s\n' % html_escape(emailaddr)) - self.w(u'\n') - -from logilab.common.deprecation import class_renamed -EUserPrimaryView = class_renamed('EUserPrimaryView', CWUserPrimaryView) diff -r a721966779be -r cba9f175da2d web/views/idownloadable.py --- a/web/views/idownloadable.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/idownloadable.py Thu May 07 16:42:34 2009 +0200 @@ -5,17 +5,18 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" +_ = unicode from logilab.mtconverter import BINARY_ENCODINGS, TransformError, html_escape +from cubicweb.view import EntityView from cubicweb.selectors import (one_line_rset, score_entity, implements, match_context_prop) from cubicweb.interfaces import IDownloadable from cubicweb.common.mttransforms import ENGINE from cubicweb.web.box import EntityBoxTemplate -from cubicweb.web.views import baseviews +from cubicweb.web.views import primary, baseviews -_ = unicode def is_image(entity): mt = entity.download_content_type() @@ -52,7 +53,7 @@ download_box(self.w, entity, title, label) -class DownloadView(baseviews.EntityView): +class DownloadView(EntityView): """this view is replacing the deprecated 'download' controller and allow downloading of entities providing the necessary interface """ @@ -81,7 +82,7 @@ self.w(self.complete_entity(0).download_data()) -class DownloadLinkView(baseviews.EntityView): +class DownloadLinkView(EntityView): """view displaying a link to download the file""" id = 'downloadlink' __select__ = implements(IDownloadable) @@ -94,19 +95,11 @@ self.w(u'%s' % (url, html_escape(title or entity.dc_title()))) - -class IDownloadablePrimaryView(baseviews.PrimaryView): +class IDownloadablePrimaryView(primary.PrimaryView): __select__ = implements(IDownloadable) - # XXX File/Image attributes but this is not specified in the IDownloadable interface - skip_attrs = baseviews.PrimaryView.skip_attrs + ('data', 'name') - def render_entity_title(self, entity): - self.w(u'

%s %s

' - % (entity.dc_type().capitalize(), - html_escape(entity.dc_title()))) - - def render_entity_attributes(self, entity, siderelations): - super(IDownloadablePrimaryView, self).render_entity_attributes(entity, siderelations) + def render_entity_attributes(self, entity): + super(IDownloadablePrimaryView, self).render_entity_attributes(entity) self.w(u'
') contenttype = entity.download_content_type() if contenttype.startswith('image/'): @@ -123,10 +116,6 @@ self.w('
%s
' % msg) self.w(u'
') - def is_side_related(self, rschema, eschema): - """display all relations as side related""" - return True - class IDownloadableLineView(baseviews.OneLineView): __select__ = implements(IDownloadable) @@ -141,7 +130,7 @@ (url, name, durl, self.req._('download'))) -class ImageView(baseviews.EntityView): +class ImageView(EntityView): id = 'image' __select__ = implements(IDownloadable) & score_entity(is_image) diff -r a721966779be -r cba9f175da2d web/views/igeocodable.py diff -r a721966779be -r cba9f175da2d web/views/isioc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/isioc.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,93 @@ +"""Specific views for SIOC interfaces + +:organization: Logilab +:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from logilab.mtconverter import html_escape + +from cubicweb.view import EntityView +from cubicweb.selectors import implements +from cubicweb.interfaces import ISiocItem, ISiocContainer + +class SIOCView(EntityView): + id = 'sioc' + __select__ = EntityView.__select__ & implements(ISiocItem, ISiocContainer) + title = _('sioc') + templatable = False + content_type = 'text/xml' + + def call(self): + self.w(u'\n' % self.req.encoding) + self.w(u'''\n''') + for i in xrange(self.rset.rowcount): + self.cell_call(i, 0) + self.w(u'\n') + + def cell_call(self, row, col): + self.wview('sioc_element', self.rset, row=row, col=col) + +class SIOCContainerView(EntityView): + id = 'sioc_element' + __select__ = EntityView.__select__ & implements(ISiocContainer) + templatable = False + content_type = 'text/xml' + + def cell_call(self, row, col): + entity = self.complete_entity(row, col) + sioct = html_escape(entity.isioc_type()) + self.w(u'\n' + % (sioct, html_escape(entity.absolute_url()))) + self.w(u'%s' + % html_escape(entity.dc_title())) + self.w(u'%s' + % entity.creation_date) + self.w(u'%s' + % entity.modification_date) + self.w(u'')#entity.isioc_items() + self.w(u'\n' % sioct) + + +class SIOCItemView(EntityView): + id = 'sioc_element' + __select__ = EntityView.__select__ & implements(ISiocItem) + templatable = False + content_type = 'text/xml' + + def cell_call(self, row, col): + entity = self.complete_entity(row, col) + sioct = html_escape(entity.isioc_type()) + self.w(u'\n' + % (sioct, html_escape(entity.absolute_url()))) + self.w(u'%s' + % html_escape(entity.dc_title())) + self.w(u'%s' + % entity.creation_date) + self.w(u'%s' + % entity.modification_date) + if entity.content: + self.w(u'%s''' + % html_escape(entity.isioc_content())) + if entity.related('entry_of'): + self.w(u'\n' + % html_escape(entity.isioc_container().absolute_url())) + if entity.creator: + self.w(u'\n') + self.w(u'\n' + % html_escape(entity.creator.absolute_url())) + self.w(entity.creator.view('foaf')) + self.w(u'\n') + self.w(u'\n') + self.w(u'')#entity.isioc_topics() + self.w(u'')#entity.isioc_replies() + self.w(u' \n' % sioct) + diff -r a721966779be -r cba9f175da2d web/views/management.py --- a/web/views/management.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/management.py Thu May 07 16:42:34 2009 +0200 @@ -6,6 +6,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" +_ = unicode from logilab.mtconverter import html_escape @@ -17,16 +18,57 @@ from cubicweb.web.formfields import guess_field from cubicweb.web.formrenderers import HTableFormRenderer -_ = unicode - SUBMIT_MSGID = _('Submit bug report') MAIL_SUBMIT_MSGID = _('Submit bug report by mail') +class SecurityViewMixIn(object): + """display security information for a given schema """ + def schema_definition(self, eschema, link=True, access_types=None): + w = self.w + _ = self.req._ + if not access_types: + access_types = eschema.ACTIONS + w(u'') + w(u'' % ( + _("permission"), _('granted to groups'), _('rql expressions'))) + for access_type in access_types: + w(u'') + w(u'' % _('%s_perm' % access_type)) + groups = eschema.get_groups(access_type) + l = [] + groups = [(_(group), group) for group in groups] + for trad, group in sorted(groups): + if link: + l.append(u'%s
' % ( + self.build_url('egroup/%s' % group), group, trad)) + else: + l.append(u'
%s
' % (group, trad)) + w(u'' % u''.join(l)) + rqlexprs = eschema.get_rqlexprs(access_type) + w(u'' % u'

'.join(expr.expression for expr in rqlexprs)) + w(u'\n') + w(u'
%s%s%s
%s%s%s
') -class SecurityManagementView(EntityView): + def has_schema_modified_permissions(self, eschema, access_types): + """ return True if eschema's actual permissions are diffrents + from the default ones + """ + for access_type in access_types: + if eschema.get_rqlexprs(access_type): + return True + if eschema.get_groups(access_type) != \ + frozenset(eschema.get_default_groups()[access_type]): + return True + return False + + +class SecurityManagementView(EntityView, SecurityViewMixIn): """display security information for a given entity""" id = 'security' title = _('security') + def call(self): + self.w(u'
%s
' % self.req._('validating...')) + super(SecurityManagementView, self).call() def cell_call(self, row, col): self.req.add_js('cubicweb.edition.js') @@ -40,7 +82,7 @@ html_escape(entity.dc_title()))) # first show permissions defined by the schema self.w('

%s

' % _('schema\'s permissions definitions')) - self.schema_definition(entity) + self.schema_definition(entity.e_schema) self.w('

%s

' % _('manage security')) # ownership information if self.schema.rschema('owned_by').has_perm(self.req, 'add', @@ -48,7 +90,7 @@ self.owned_by_edit_form(entity) else: self.owned_by_information(entity) - # epermissions + # cwpermissions if 'require_permission' in entity.e_schema.subject_relations(): w('

%s

' % _('permissions for this entity')) reqpermschema = self.schema.rschema('require_permission') @@ -56,36 +98,17 @@ if reqpermschema.has_perm(self.req, 'add', fromeid=entity.eid): self.require_permission_edit_form(entity) - def schema_definition(self, entity): - w = self.w - _ = self.req._ - w(u'') - w(u'' % ( - _("access type"), _('granted to groups'), _('rql expressions'))) - for access_type in ('read', 'add', 'update', 'delete'): - w(u'') - w(u'' % self.req.__('%s_permission' % access_type)) - groups = entity.e_schema.get_groups(access_type) - l = [] - for group in groups: - l.append(u'%s' % ( - self.build_url('egroup/%s' % group), _(group))) - w(u'' % u', '.join(l)) - rqlexprs = entity.e_schema.get_rqlexprs(access_type) - w(u'' % u'
'.join(expr.expression for expr in rqlexprs)) - w(u'\n') - w(u'
%s%s%s
%s%s%s
') - def owned_by_edit_form(self, entity): self.w('

%s

' % self.req._('ownership')) msg = self.req._('ownerships have been changed') form = EntityFieldsForm(self.req, None, entity=entity, submitmsg=msg, form_buttons=[formwidgets.SubmitButton()], + domid='ownership%s' % entity.eid, __redirectvid='security', __redirectpath=entity.rest_path()) field = guess_field(entity.e_schema, self.schema.rschema('owned_by')) form.append_field(field) - self.w(form.form_render()) + self.w(form.form_render(display_progress_div=False)) def owned_by_information(self, entity): ownersrset = entity.related('owned_by') @@ -116,18 +139,18 @@ w(u'') w(u'' % (_("permission"), _('granted to groups'))) - for eperm in entity.require_permission: + for cwperm in entity.require_permission: w(u'') if dellinktempl: - w(u'' % (dellinktempl % eperm.eid, - eperm.view('oneline'))) + w(u'' % (dellinktempl % cwperm.eid, + cwperm.view('oneline'))) else: - w(u'' % eperm.view('oneline')) - w(u'' % self.view('csv', eperm.related('require_group'), 'null')) + w(u'' % cwperm.view('oneline')) + w(u'' % self.view('csv', cwperm.related('require_group'), 'null')) w(u'\n') w(u'
%s%s
%s%s%s%s%s%s%s%s
') else: - self.w(self.req._('no associated epermissions')) + self.w(self.req._('no associated permissions')) def require_permission_edit_form(self, entity): w = self.w @@ -137,14 +160,17 @@ w(u'

%s

' % _('add a new permission')) form = EntityFieldsForm(self.req, None, entity=newperm, form_buttons=[formwidgets.SubmitButton()], + domid='reqperm%s' % entity.eid, __redirectvid='security', __redirectpath=entity.rest_path()) - form.form_add_hidden('require_permission', entity.eid, role='object', eidparam=True) + form.form_add_hidden('require_permission', entity.eid, role='object', + eidparam=True) permnames = getattr(entity, '__permissions__', None) cwpermschema = newperm.e_schema if permnames is not None: field = guess_field(cwpermschema, self.schema.rschema('name'), - widget=formwidgets.Select, choices=permnames) + widget=formwidgets.Select({'size': 1}), + choices=permnames) else: field = guess_field(cwpermschema, self.schema.rschema('name')) form.append_field(field) @@ -152,7 +178,7 @@ form.append_field(field) field = guess_field(cwpermschema, self.schema.rschema('require_group')) form.append_field(field) - self.w(form.form_render(renderer=HTableFormRenderer())) + self.w(form.form_render(renderer=HTableFormRenderer(display_progress_div=False))) @@ -245,7 +271,6 @@ binfo += '\n' return binfo - class ProcessInformationView(StartupView): id = 'info' __select__ = none_rset() & match_user_groups('managers') diff -r a721966779be -r cba9f175da2d web/views/massmailing.py --- a/web/views/massmailing.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/massmailing.py Thu May 07 16:42:34 2009 +0200 @@ -13,7 +13,8 @@ from cubicweb.view import EntityView from cubicweb.web import stdmsgs from cubicweb.web.action import Action -from cubicweb.web.form import FieldsForm, FormRenderer, FormViewMixIn +from cubicweb.web.form import FieldsForm, FormViewMixIn +from cubicweb.web.formrenderers import FormRenderer from cubicweb.web.formfields import StringField from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton diff -r a721966779be -r cba9f175da2d web/views/navigation.py --- a/web/views/navigation.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/navigation.py Thu May 07 16:42:34 2009 +0200 @@ -44,7 +44,7 @@ w(u'[ %s ]' % u' | '.join(blocklist)) w(u' %s' % self.next_link(params)) w(u'
') - + def index_display(self, start, stop): return u'%s - %s' % (start+1, stop+1) @@ -53,10 +53,10 @@ and if the result set is sorted """ __select__ = paginated_rset() & sorted_rset() - + # number of considered chars to build page links nb_chars = 5 - + def display_func(self, rset, col, attrname): req = self.req if attrname is not None: @@ -70,7 +70,7 @@ def index_display(row): return rset.get_entity(row, col).view('text') return index_display - + def call(self): """displays links to navigate accross pages of a result set @@ -167,12 +167,12 @@ # monkey patch base View class to add a .pagination(req, rset, w, forcedisplay) # method to be called on view's result set and printing pages index in the view from cubicweb.view import View -# XXX deprecated, use paginate View.pagination = obsolete('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav) -def paginate(view, show_all_option=True, w=None): +def paginate(view, show_all_option=True, w=None, page_size=None): limit_rset_using_paged_nav(view, view.req, view.rset, w or view.w, - not view.need_navigation, show_all_option) + not view.need_navigation, show_all_option, + page_size=page_size) View.paginate = paginate class NextPrevNavigationComponent(EntityVComponent): @@ -212,7 +212,7 @@ html_escape(previous.absolute_url()), self.req._('i18nprevnext_previous'), html_escape(cut(previous.dc_title(), textsize))) - + def next_link(self, next, textsize): return u'%s >>' % ( html_escape(next.absolute_url()), diff -r a721966779be -r cba9f175da2d web/views/old_calendar.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/old_calendar.py Thu May 07 16:42:34 2009 +0200 @@ -0,0 +1,541 @@ +"""html calendar views + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +from datetime import date, time, timedelta + +from logilab.mtconverter import html_escape + +from cubicweb.interfaces import ICalendarViews +from cubicweb.utils import ONEDAY, ONEWEEK, date_range, first_day, last_day, previous_month, next_month, days_in_month +from cubicweb.selectors import implements +from cubicweb.view import EntityView + +# used by i18n tools +WEEKDAYS = [_("monday"), _("tuesday"), _("wednesday"), _("thursday"), + _("friday"), _("saturday"), _("sunday")] +MONTHNAMES = [ _('january'), _('february'), _('march'), _('april'), _('may'), + _('june'), _('july'), _('august'), _('september'), _('october'), + _('november'), _('december') + ] + +class _CalendarView(EntityView): + """base calendar view containing helpful methods to build calendar views""" + __select__ = implements(ICalendarViews,) + need_navigation = False + + # Navigation building methods / views #################################### + + PREV = u'<<  <' + NEXT = u'>  >>' + NAV_HEADER = u""" + +
%s%s
+""" % (PREV, NEXT) + + def nav_header(self, date, smallshift=3, bigshift=9): + """prints shortcut links to go to previous/next steps (month|week)""" + prev1 = next1 = prev2 = nex2 = date + prev1 = previous_month(date, smallshift) + next1 = next_month(date, smallshift) + prev2 = previous_month(date, bigshift) + next2 = next_month(date, bigshift) + rql = self.rset.printable_rql() + return self.NAV_HEADER % ( + html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, + month=prev2.month)), + html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, + month=prev1.month)), + html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, + month=next1.month)), + html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, + month=next2.month))) + + + # Calendar building methods ############################################## + + def build_calendars(self, schedule, begin, end): + """build several HTML calendars at once, one for each month + between begin and end + """ + return [self.build_calendar(schedule, date) + for date in date_range(begin, end, incmonth=1)] + + def build_calendar(self, schedule, first_day): + """method responsible for building *one* HTML calendar""" + # FIXME iterates between [first_day-first_day.day_of_week ; + # last_day+6-last_day.day_of_week] + umonth = self.format_date(first_day, '%B %Y') # localized month name + rows = [] + current_row = [NO_CELL] * first_day.weekday() + for daynum in xrange(0, days_in_month(first_day)): + # build cell day + day = first_day + timedelta(daynum) + events = schedule.get(day) + if events: + events = [u'\n'.join(event) for event in events.values()] + current_row.append(CELL % (daynum+1, '\n'.join(events))) + else: + current_row.append(EMPTY_CELL % (daynum+1)) + # store & reset current row on Sundays + if day.weekday() == 6: + rows.append(u'

') + self.build_calendars(schedule, begin, end) + self.w(u'
') + self.w(self.nav_header(date(year, month, 1), 1, 6)) + + def build_calendars(self, schedule, begin, end): + self.w(u'') + 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) + url = self.build_url(rql=rql, vid=self.id, + year=cur_month.year, month=cur_month.month) + self.w(u'%s' % (html_escape(url), + umonth)) + self.w(u'') + _ = self.req._ + for day_num in xrange(31): + self.w(u'') + for cur_month in date_range(begin, end, incmonth=1): + if day_num >= days_in_month(cur_month): + self.w(u'%s%s' % (NO_CELL, NO_CELL)) + else: + day = date(cur_month.year, cur_month.month, day_num+1) + events = schedule.get(day) + self.w(u'%s %s\n' % (_(WEEKDAYS[day.weekday()])[0].upper(), day_num+1)) + self.format_day_events(day, events) + self.w(u'') + + def format_day_events(self, day, events): + if events: + events = ['\n'.join(event) for event in events.values()] + self.w(WEEK_CELL % '\n'.join(events)) + else: + self.w(WEEK_EMPTY_CELL) + + +class MonthCalendarView(_CalendarView): + """this view renders a 3x1 calendars' table""" + id = 'calendarmonth' + title = _('calendar (month)') + + def call(self, year=None, month=None): + year = year or int(self.req.form.get('year', date.today().year)) + month = month or int(self.req.form.get('month', date.today().month)) + center_date = date(year, month, 1) + begin, end = self.get_date_range(day=center_date, shift=1) + schedule = self._mk_schedule(begin, end) + calendars = self.build_calendars(schedule, begin, end) + self.w(self.nav_header(center_date, 1, 3)) + self.w(BIG_CALENDARS_PAGE % tuple(calendars)) + self.w(self.nav_header(center_date, 1, 3)) + + +class WeekCalendarView(_CalendarView): + """this view renders a calendar for week events""" + id = 'calendarweek' + title = _('calendar (week)') + + def call(self, year=None, week=None): + year = year or int(self.req.form.get('year', date.today().year)) + week = week or int(self.req.form.get('week', date.today().isocalendar()[1])) + day0 = date(year, 1, 1) + first_day_of_week = day0 - day0.weekday()*ONEDAY + ONEWEEK + begin, end = first_day_of_week- ONEWEEK, first_day_of_week + 2*ONEWEEK + schedule = self._mk_schedule(begin, end, itemvid='calendarlargeitem') + self.w(self.nav_header(first_day_of_week)) + self.w(u'') + _weeks = [(first_day_of_week-ONEWEEK, first_day_of_week-ONEDAY), + (first_day_of_week, first_day_of_week+6*ONEDAY), + (first_day_of_week+ONEWEEK, first_day_of_week+13*ONEDAY)] + self.build_calendar(schedule, _weeks) + self.w(u'
') + self.w(self.nav_header(first_day_of_week)) + + def build_calendar(self, schedule, weeks): + rql = self.rset.printable_rql() + _ = self.req._ + for monday, sunday in weeks: + umonth = self.format_date(monday, '%B %Y') + url = self.build_url(rql=rql, vid='calendarmonth', + year=monday.year, month=monday.month) + monthlink = '%s' % (html_escape(url), umonth) + self.w(u'%s %s (%s)' \ + % (_('week'), monday.isocalendar()[1], monthlink)) + for day in date_range(monday, sunday): + self.w(u'') + self.w(u'%s' % _(WEEKDAYS[day.weekday()])) + self.w(u'%s' % (day.strftime('%Y-%m-%d'))) + events = schedule.get(day) + if events: + events = ['\n'.join(event) for event in events.values()] + self.w(WEEK_CELL % '\n'.join(events)) + else: + self.w(WEEK_EMPTY_CELL) + self.w(u'') + + def nav_header(self, date, smallshift=1, bigshift=3): + """prints shortcut links to go to previous/next steps (month|week)""" + prev1 = date - ONEWEEK * smallshift + prev2 = date - ONEWEEK * bigshift + next1 = date + ONEWEEK * smallshift + next2 = date + ONEWEEK * bigshift + rql = self.rset.printable_rql() + return self.NAV_HEADER % ( + html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])), + html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])), + html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])), + html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1]))) + + + +class AMPMYearCalendarView(YearCalendarView): + id = 'ampmcalendaryear' + title = _('am/pm calendar (year)') + + def build_calendar(self, schedule, first_day): + """method responsible for building *one* HTML calendar""" + umonth = self.format_date(first_day, '%B %Y') # localized month name + rows = [] # each row is: (am,pm), (am,pm) ... week_title + current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday() + rql = self.rset.printable_rql() + for daynum in xrange(0, days_in_month(first_day)): + # build cells day + day = first_day + timedelta(daynum) + events = schedule.get(day) + if events: + current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events)) + else: + current_row.append((AMPM_DAY % (daynum+1), + AMPM_EMPTY % ("amCell", "am"), + AMPM_EMPTY % ("pmCell", "pm"))) + # store & reset current row on Sundays + if day.weekday() == 6: + url = self.build_url(rql=rql, vid='ampmcalendarweek', + year=day.year, week=day.isocalendar()[1]) + weeklink = '%s' % (html_escape(url), + day.isocalendar()[1]) + current_row.append(WEEKNUM_CELL % weeklink) + rows.append(current_row) + current_row = [] + current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday())) + url = self.build_url(rql=rql, vid='ampmcalendarweek', + year=day.year, week=day.isocalendar()[1]) + weeklink = '%s' % (html_escape(url), day.isocalendar()[1]) + current_row.append(WEEKNUM_CELL % weeklink) + rows.append(current_row) + # build two rows for each week: am & pm + formatted_rows = [] + for row in rows: + week_title = row.pop() + day_row = [day for day, am, pm in row] + am_row = [am for day, am, pm in row] + pm_row = [pm for day, am, pm in row] + formatted_rows.append('%s%s'% (week_title, '\n'.join(day_row))) + formatted_rows.append(' %s'% '\n'.join(am_row)) + formatted_rows.append(' %s'% '\n'.join(pm_row)) + # tigh everything together + url = self.build_url(rql=rql, vid='ampmcalendarmonth', + year=first_day.year, month=first_day.month) + monthlink = '%s' % (html_escape(url), umonth) + return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows)) + + + +class AMPMSemesterCalendarView(SemesterCalendarView): + """this view renders a 3x1 calendars' table""" + id = 'ampmcalendarsemester' + title = _('am/pm calendar (semester)') + + def build_calendars(self, schedule, begin, end): + self.w(u'') + 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) + url = self.build_url(rql=rql, vid=self.id, + year=cur_month.year, month=cur_month.month) + self.w(u'%s' % (html_escape(url), + umonth)) + self.w(u'') + _ = self.req._ + for day_num in xrange(31): + self.w(u'') + for cur_month in date_range(begin, end, incmonth=1): + if day_num >= days_in_month(cur_month): + self.w(u'%s%s%s' % (NO_CELL, NO_CELL, NO_CELL)) + else: + day = date(cur_month.year, cur_month.month, day_num+1) + events = schedule.get(day) + self.w(u'%s %s\n' % (_(WEEKDAYS[day.weekday()])[0].upper(), + day_num+1)) + self.format_day_events(day, events) + self.w(u'') + + def format_day_events(self, day, events): + if events: + self.w(u'\n'.join(self._build_ampm_cells(day, events))) + else: + self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"), + AMPM_EMPTY % ("pmCell", "pm"))) + + +class AMPMMonthCalendarView(MonthCalendarView): + """this view renders a 3x1 calendars' table""" + id = 'ampmcalendarmonth' + title = _('am/pm calendar (month)') + + def build_calendar(self, schedule, first_day): + """method responsible for building *one* HTML calendar""" + umonth = self.format_date(first_day, '%B %Y') # localized month name + rows = [] # each row is: (am,pm), (am,pm) ... week_title + current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday() + rql = self.rset.printable_rql() + for daynum in xrange(0, days_in_month(first_day)): + # build cells day + day = first_day + timedelta(daynum) + events = schedule.get(day) + if events: + current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events)) + else: + current_row.append((AMPM_DAY % (daynum+1), + AMPM_EMPTY % ("amCell", "am"), + AMPM_EMPTY % ("pmCell", "pm"))) + # store & reset current row on Sundays + if day.weekday() == 6: + url = self.build_url(rql=rql, vid='ampmcalendarweek', + year=day.year, week=day.isocalendar()[1]) + weeklink = '%s' % (html_escape(url), + day.isocalendar()[1]) + current_row.append(WEEKNUM_CELL % weeklink) + rows.append(current_row) + current_row = [] + current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday())) + url = self.build_url(rql=rql, vid='ampmcalendarweek', + year=day.year, week=day.isocalendar()[1]) + weeklink = '%s' % (html_escape(url), + day.isocalendar()[1]) + current_row.append(WEEKNUM_CELL % weeklink) + rows.append(current_row) + # build two rows for each week: am & pm + formatted_rows = [] + for row in rows: + week_title = row.pop() + day_row = [day for day, am, pm in row] + am_row = [am for day, am, pm in row] + pm_row = [pm for day, am, pm in row] + formatted_rows.append('%s%s'% (week_title, '\n'.join(day_row))) + formatted_rows.append(' %s'% '\n'.join(am_row)) + formatted_rows.append(' %s'% '\n'.join(pm_row)) + # tigh everything together + url = self.build_url(rql=rql, vid='ampmcalendarmonth', + year=first_day.year, month=first_day.month) + monthlink = '%s' % (html_escape(url), + umonth) + return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows)) + + + +class AMPMWeekCalendarView(WeekCalendarView): + """this view renders a 3x1 calendars' table""" + id = 'ampmcalendarweek' + title = _('am/pm calendar (week)') + + def build_calendar(self, schedule, weeks): + rql = self.rset.printable_rql() + w = self.w + _ = self.req._ + for monday, sunday in weeks: + umonth = self.format_date(monday, '%B %Y') + url = self.build_url(rql=rql, vid='ampmcalendarmonth', + year=monday.year, month=monday.month) + monthlink = '%s' % (html_escape(url), umonth) + w(u'%s' % ( + WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink))) + w(u'%s '% _(u'Date')) + for day in date_range(monday, sunday): + events = schedule.get(day) + style = day.weekday() % 2 and "even" or "odd" + w(u'' % style) + if events: + hours = events.keys() + hours.sort() + w(AMPM_DAYWEEK % ( + len(hours), _(WEEKDAYS[day.weekday()]), + self.format_date(day))) + w(AMPM_WEEK_CELL % ( + hours[0].hour, hours[0].minute, + '\n'.join(events[hours[0]]))) + w(u'') + for hour in hours[1:]: + w(u'%s'% ( + style, AMPM_WEEK_CELL % (hour.hour, hour.minute, + '\n'.join(events[hour])))) + else: + w(AMPM_DAYWEEK_EMPTY % ( + _(WEEKDAYS[day.weekday()]), + self.format_date(day))) + w(WEEK_EMPTY_CELL) + w(u'') + + +SMALL_CALENDARS_PAGE = u""" + + + +
%s%s%s
%s%s%s
%s%s%s
+""" + +BIG_CALENDARS_PAGE = u""" + + + +
%s
%s
%s
+""" + +WEEKNUM_CELL = u'%s' + +def CALENDAR(req): + _ = req._ + WEEKNUM_HEADER = u'%s' % _('week') + CAL_HEADER = WEEKNUM_HEADER + u' \n'.join([u'%s' % _(day)[0].upper() + for day in WEEKDAYS]) + return u""" + + + %s + +%%s +
%%s
+""" % (CAL_HEADER,) + + +DAY_TEMPLATE = """%(daylabel)s%(dmydate)s%(dayschedule)s +""" + +NO_CELL = u'' +EMPTY_CELL = u'%s' +CELL = u'%s
%s
' + +AMPM_DAY = u'%d' +AMPM_EMPTY = u'%s' +AMPM_CONTENT = u'%s
%s
' + +WEEK_TITLE = u'%s %s (%s)' +WEEK_EMPTY_CELL = u' ' +WEEK_CELL = u'
%s
' + +AMPM_DAYWEEK_EMPTY = u'%s %s' +AMPM_DAYWEEK = u'%s %s' +AMPM_WEEK_CELL = u'
%02d:%02d - %s
' diff -r a721966779be -r cba9f175da2d web/views/primary.py --- a/web/views/primary.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/primary.py Thu May 07 16:42:34 2009 +0200 @@ -4,19 +4,18 @@ :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ +__docformat__ = "restructuredtext en" from warnings import warn +from logilab.mtconverter import html_escape + from cubicweb import Unauthorized from cubicweb.view import EntityView +from cubicweb.web.uicfg import rdisplay _ = unicode -PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity', - 'owned_by', 'created_by', - 'in_state', 'wf_info_for', 'require_permission', - 'from_entity', 'to_entity', - 'see_also']) class PrimaryView(EntityView): """the full view of an non final entity""" @@ -25,10 +24,47 @@ show_attr_label = True show_rel_label = True skip_none = True - skip_attrs = ('eid', 'creation_date', 'modification_date') - skip_rels = () + rdisplay = rdisplay main_related_section = True + @classmethod + def vreg_initialization_completed(cls): + """set default category tags for relations where it's not yet defined in + the category relation tags + """ + for eschema in cls.schema.entities(): + for rschema, tschemas, role in eschema.relation_definitions(True): + for tschema in tschemas: + if role == 'subject': + X, Y = eschema, tschema + card = rschema.rproperty(X, Y, 'cardinality')[0] + composed = rschema.rproperty(X, Y, 'composite') == 'object' + else: + X, Y = tschema, eschema + card = rschema.rproperty(X, Y, 'cardinality')[1] + composed = rschema.rproperty(X, Y, 'composite') == 'subject' + displayinfo = cls.rdisplay.get(rschema, role, X, Y) + if displayinfo is None: + if rschema.is_final(): + if rschema.meta or tschema.type in ('Password', 'Bytes'): + where = None + else: + where = 'attributes' + elif card in '1+': + where = 'attributes' + elif composed: + where = 'relations' + else: + where = 'sideboxes' + displayinfo = {'where': where, + 'order': cls.rdisplay.get_timestamp()} + cls.rdisplay.tag_relation(displayinfo, (X, rschema, Y), + role) + if role == 'subject': + displayinfo.setdefault('label', rschema.type) + else: + displayinfo.setdefault('label', '%s_%s' % (rschema, role)) + def html_headers(self): """return a list of html headers (eg something to be inserted between and of the returned page @@ -41,30 +77,43 @@ self.row = row # XXX move render_entity implementation here self.render_entity(self.complete_entity(row, col)) + self.maxrelated = self.req.property_value('navigation.related-limit') def render_entity(self, entity): """return html to display the given entity""" - siderelations = [] self.render_entity_title(entity) self.render_entity_metadata(entity) # entity's attributes and relations, excluding meta data # if the entity isn't meta itself - boxes = self._preinit_side_related(entity, siderelations) - if boxes: - self.w(u'\n' % self.req._('application entities')) self.entity_types_table(eschema for eschema in schema.entities() - if not eschema.meta and not eschema.is_subobject(strict=True)) + if uicfg.etypecat.get(eschema) == 'application') if manager: self.w(u'\n' % self.req._('system entities')) self.entity_types_table(eschema for eschema in schema.entities() - if eschema.meta and not eschema.schema_entity()) + if uicfg.etypecat.get(eschema) == 'system') if 'CWAttribute' in schema: # check schema support self.w(u'\n' % self.req._('schema entities')) - self.entity_types_table(schema.eschema(etype) - for etype in schema.schema_entity_types()) + self.entity_types_table(eschema for eschema in schema.entities() + if uicfg.etypecat.get(eschema) == 'schema') self.w(u'
') + boxes = self._prepare_side_boxes(entity) + if boxes or hasattr(self, 'render_side_related'): + self.w(u'
') self.w(u'
') self.w(u'
') - self.render_entity_attributes(entity, siderelations) + try: + self.render_entity_attributes(entity) + except TypeError: # XXX bw compat + warn('siderelations argument of render_entity_attributes is ' + 'deprecated (%s)' % self.__class__) + self.render_entity_attributes(entity, []) self.w(u'
') self.content_navigation_components('navcontenttop') if self.main_related_section: - self.render_entity_relations(entity, siderelations) + try: + self.render_entity_relations(entity) + except TypeError: # XXX bw compat + warn('siderelations argument of render_entity_relations is ' + 'deprecated') + self.render_entity_relations(entity, []) self.w(u'
') - if boxes: + if boxes or hasattr(self, 'render_side_related'): self.w(u'
') # side boxes self.w(u'
') - self.render_side_related(entity, siderelations) + if hasattr(self, 'render_side_related'): + warn('render_side_related is deprecated') + self.render_side_related(entity, []) + self.render_side_boxes(boxes) self.w(u'
') self.w(u'
') self.content_navigation_components('navcontentbottom') @@ -83,29 +132,16 @@ comp.dispatch(w=self.w, view=self) self.w(u'') - def iter_attributes(self, entity): - for rschema, targetschema in entity.e_schema.attribute_definitions(): - if rschema.type in self.skip_attrs: - continue - yield rschema, targetschema - - def iter_relations(self, entity): - skip = set(self.skip_rels) - skip.update(PRIMARY_SKIP_RELS) - for rschema, targetschemas, x in entity.e_schema.relation_definitions(): - if rschema.type in skip: - continue - yield rschema, targetschemas, x - def render_entity_title(self, entity): title = self.content_title(entity) # deprecate content_title? if title: self.w(u'

%s %s

' % (entity.dc_type().capitalize(), title)) + def content_title(self, entity): - """default implementation return an empty string""" - return u'' + """default implementation return dc_title""" + return html_escape(entity.dc_title()) def render_entity_metadata(self, entity): entity.view('metadata', w=self.w) @@ -117,73 +153,39 @@ """default implementation return an empty string""" return u'' - def render_entity_attributes(self, entity, siderelations): - for rschema, targetschema in self.iter_attributes(entity): - attr = rschema.type - if targetschema.type in ('Password', 'Bytes'): - continue - try: - wdg = entity.get_widget(attr) - except Exception, ex: - value = entity.printable_value(attr, entity[attr], targetschema.type) + def render_entity_attributes(self, entity, siderelations=None): + for rschema, tschemas, role, displayinfo in self._iter_display(entity, 'attributes'): + vid = displayinfo.get('vid', 'reledit') + if rschema.is_final() or vid == 'reledit': + value = entity.view(vid, rtype=rschema.type, role=role) else: - value = wdg.render(entity) + rset = self._relation_rset(entity, rschema, role, displayinfo) + if rset: + value = self.view(vid, rset) + else: + value = None if self.skip_none and (value is None or value == ''): continue - if rschema.meta: - continue - self._render_related_entities(entity, rschema, value) + self._render_attribute(rschema, value) - def _preinit_side_related(self, entity, siderelations): - self._sideboxes = None - self._related_entities = [] - if hasattr(self, 'get_side_boxes_defs'): - self._sideboxes = [(label, rset) for label, rset in self.get_side_boxes_defs(entity) - if rset] - else: - eschema = entity.e_schema - maxrelated = self.req.property_value('navigation.related-limit') - for rschema, targetschemas, x in self.iter_relations(entity): - try: - related = entity.related(rschema.type, x, limit=maxrelated+1) - except Unauthorized: - continue - if not related: - continue - if self.is_side_related(rschema, eschema): - siderelations.append((rschema, related, x)) - continue - self._related_entities.append((rschema, related, x)) - self._boxes_in_context = list(self.vreg.possible_vobjects('boxes', self.req, self.rset, - row=self.row, view=self, - context='incontext')) - return self._sideboxes or self._boxes_in_context or self._related_entities or siderelations + def render_entity_relations(self, entity, siderelations=None): + for rschema, tschemas, role, displayinfo in self._iter_display(entity, 'relations'): + rset = self._relation_rset(entity, rschema, role, displayinfo) + if rset: + self._render_relation(rset, displayinfo, 'autolimited', + self.show_rel_label) - def render_entity_relations(self, entity, siderelations): - if self._related_entities: - for rschema, related, x in self._related_entities: - self._render_related_entities(entity, rschema, related, x) - - - def render_side_related(self, entity, siderelations): + def render_side_boxes(self, boxes): """display side related relations: non-meta in a first step, meta in a second step """ - if self._sideboxes: - for label, rset in self._sideboxes: + for box in boxes: + if isinstance(box, tuple): + label, rset, vid, _ = box self.w(u'
') - self.wview('sidebox', rset, title=label) + self.wview(vid, rset, title=label) self.w(u'
') - elif siderelations: - self.w(u'
') - for relatedinfos in siderelations: - # if not relatedinfos[0].meta: - # continue - self._render_related_entities(entity, *relatedinfos) - self.w(u'
') - - if self._boxes_in_context: - for box in self._boxes_in_context: + else: try: box.dispatch(w=self.w, row=self.row) except NotImplementedError: @@ -191,36 +193,87 @@ # .call() and not cell_call() box.dispatch(w=self.w) - def is_side_related(self, rschema, eschema): - return rschema.meta and \ - not rschema.schema_relation() == eschema.schema_entity() + def _prepare_side_boxes(self, entity): + sideboxes = [] + for rschema, tschemas, role, displayinfo in self._iter_display(entity, 'sideboxes'): + rset = self._relation_rset(entity, rschema, role, displayinfo) + if not rset: + continue + label = display_name(self.req, rschema.type, role) + vid = displayinfo.get('vid', 'autolimited') + sideboxes.append((label, rset, vid, displayinfo.get('order'))) + sideboxes = sorted(sideboxes, key=lambda x: x[-1]) + sideboxes += list(self.vreg.possible_vobjects('boxes', self.req, self.rset, + row=self.row, view=self, + context='incontext')) + return sideboxes - def _render_related_entities(self, entity, rschema, related, - role='subject'): + def _iter_display(self, entity, where): + eschema = entity.e_schema + for rschema, tschemas, role in eschema.relation_definitions(True): + matchtschemas = [] + for tschema in tschemas: + displayinfo = self.rdisplay.etype_get(eschema, rschema, role, + tschema) + assert displayinfo is not None, (str(rschema), role, + str(eschema), str(tschema)) + if displayinfo.get('where') == where: + matchtschemas.append(tschema) + if matchtschemas: + # XXX pick the latest displayinfo + yield rschema, matchtschemas, role, displayinfo + + def _relation_rset(self, entity, rschema, role, displayinfo): + try: + if displayinfo.get('limit'): + rset = entity.related(rschema.type, role, + limit=self.maxrelated+1) + else: + rset = entity.related(rschema.type, role) + except Unauthorized: + return + if 'filter' in displayinfo: + rset = displayinfo['filter'](rset) + return rset + + def _render_relation(self, rset, displayinfo, defaultvid, showlabel): + self.w(u'
') + if showlabel: + self.w(u'

%s

' % self.req._(displayinfo['label'])) + self.wview(displayinfo.get('vid', defaultvid), rset) + self.w(u'
') + + def _render_attribute(self, rschema, value, role='subject'): if rschema.is_final(): - value = related show_label = self.show_attr_label else: - if not related: - return show_label = self.show_rel_label - # if not too many entities, show them all in a list - maxrelated = self.req.property_value('navigation.related-limit') - if related.rowcount <= maxrelated: - if related.rowcount == 1: - value = self.view('incontext', related, row=0) - elif 1 < related.rowcount <= 5: - value = self.view('csv', related) - else: - value = '
' + self.view('simplelist', related) + '
' - # else show links to display related entities - else: - rql = related.printable_rql() - related.limit(maxrelated) - value = '
' + self.view('simplelist', related) - value += '[%s]' % (self.build_url(rql=rql), - self.req._('see them all')) - value += '
' label = display_name(self.req, rschema.type, role) self.field(label, value, show_label=show_label, tr=False) + +class RelatedView(EntityView): + id = 'autolimited' + def call(self, title=None, **kwargs): + # if not too many entities, show them all in a list + maxrelated = self.req.property_value('navigation.related-limit') + if title: + self.w(u'
%s
' % title) + if self.rset.rowcount <= maxrelated: + if self.rset.rowcount == 1: + self.wview('incontext', self.rset, row=0) + elif 1 < self.rset.rowcount <= 5: + self.wview('csv', self.rset) + else: + self.w(u'
') + self.wview('simplelist', self.rset) + self.w(u'
') + # else show links to display related entities + else: + rql = self.rset.printable_rql() + self.rset.limit(maxrelated) + self.w(u'
') + self.wview('simplelist', self.rset) + self.w(u'[%s]' % (self.build_url(rql=rql), + self.req._('see them all'))) + self.w(u'
') diff -r a721966779be -r cba9f175da2d web/views/schema.py --- a/web/views/schema.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/schema.py Thu May 07 16:42:34 2009 +0200 @@ -14,24 +14,29 @@ from cubicweb.selectors import implements, yes from cubicweb.schemaviewer import SchemaViewer from cubicweb.view import EntityView, StartupView -from cubicweb.common.uilib import ureport_as_html -from cubicweb.web import uicfg, action -from cubicweb.web.views import TmpFileViewMixin, baseviews +from cubicweb.common import tags, uilib +from cubicweb.web import uicfg, formwidgets, action +from cubicweb.web.views import TmpFileViewMixin, primary, baseviews -uicfg.rcategories.set_rtag('primary', 'require_group', 'subject', 'CWPermission') -uicfg.rcategories.set_rtag('generated', 'final', 'subject', 'EEtype') -uicfg.rcategories.set_rtag('generated', 'final', 'subject', 'ERtype') -uicfg.rinlined.set_rtag(True, 'relation_type', 'subject', 'CWRelation') -uicfg.rinlined.set_rtag(True, 'from_entity', 'subject', 'CWRelation') -uicfg.rinlined.set_rtag(True, 'to_entity', 'subject', 'CWRelation') -uicfg.rwidgets.set_rtag('StringWidget', 'expression', 'subject', 'RQLExpression') +uicfg.rcategories.tag_relation('primary', ('CWPermission', 'require_group', '*'), 'subject') +uicfg.rcategories.tag_attribute('generated', 'EEtype', 'final') +uicfg.rcategories.tag_attribute('generated', 'ERtype', 'final') +uicfg.rinlined.tag_relation(True, ('CWRelation', 'relation_type', '*'), 'subject') +uicfg.rinlined.tag_relation(True, ('CWRelation', 'from_entity', '*'), 'subject') +uicfg.rinlined.tag_relation(True, ('CWRelation', 'to_entity', '*'), 'subject') +uicfg.rwidgets.tag_attribute(formwidgets.TextInput, 'RQLExpression', 'expression') -uicfg.rmode.set_rtag('create', 'state_of', 'object', 'CWEType') -uicfg.rmode.set_rtag('create', 'transition_of', 'object', 'CWEType') -uicfg.rmode.set_rtag('create', 'relation_type', 'object', 'CWRType') -uicfg.rmode.set_rtag('link', 'from_entity', 'object', 'CWEType') -uicfg.rmode.set_rtag('link', 'to_entity', 'object', 'CWEType') +uicfg.rmode.tag_relation('create', ('*', 'state_of', 'CWEType'), 'object') +uicfg.rmode.tag_relation('create', ('*', 'transition_of', 'CWEType'), 'object') +uicfg.rmode.tag_relation('create', ('*', 'relation_type', 'CWRType'), 'object') +uicfg.rmode.tag_relation('link', ('*', 'from_entity', 'CWEType'), 'object') +uicfg.rmode.tag_relation('link', ('*', 'to_entity', 'CWEType'), 'object') + +for attr in ('name', 'meta', 'final'): + uicfg.rdisplay.tag_attribute({}, 'CWRType', attr) +for attr in ('name', 'meta', 'final', 'symetric', 'inlined'): + uicfg.rdisplay.tag_attribute({}, 'CWRType', attr) class ViewSchemaAction(action.Action): @@ -48,25 +53,13 @@ # schema entity types views ################################################### -class _SchemaEntityPrimaryView(baseviews.PrimaryView): - show_attr_label = False +class CWRDEFPrimaryView(primary.PrimaryView): + __select__ = implements('CWAttribute', 'CWRelation') cache_max_age = 60*60*2 # stay in http cache for 2 hours by default def content_title(self, entity): return html_escape(entity.dc_long_title()) -class CWETypePrimaryView(_SchemaEntityPrimaryView): - __select__ = implements('CWEType') - skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final') - -class CWRTypePrimaryView(_SchemaEntityPrimaryView): - __select__ = implements('CWRType') - skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final', - 'symetric', 'inlined') - -class ErdefPrimaryView(_SchemaEntityPrimaryView): - __select__ = implements('CWAttribute', 'CWRelation') - show_attr_label = True class CWETypeOneLineView(baseviews.OneLineView): __select__ = implements('CWEType') @@ -82,41 +75,44 @@ # in memory schema views (yams class instances) ############################### +SKIPPED_RELS = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by', + 'has_text',) -class CWETypeSchemaView(CWETypePrimaryView): +class CWETypeSchemaView(primary.PrimaryView): id = 'eschema' + __select__ = implements('CWEType') title = _('in memory entity schema') main_related_section = False - skip_rels = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by', - 'has_text',) + skip_rels = SKIPPED_RELS - def render_entity_attributes(self, entity, siderelations): - super(CWETypeSchemaView, self).render_entity_attributes(entity, siderelations) + def render_entity_attributes(self, entity): + super(CWETypeSchemaView, self).render_entity_attributes(entity) eschema = self.vreg.schema.eschema(entity.name) viewer = SchemaViewer(self.req) layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels) - self.w(ureport_as_html(layout)) + self.w(uilib.ureport_as_html(layout)) if not eschema.is_final(): - self.w(u'%s' % ( - html_escape(entity.absolute_url(vid='eschemagraph')), - html_escape(self.req._('graphical schema for %s') % entity.name))) + msg = self.req._('graphical schema for %s') % entity.name + self.w(tags.img(src=entity.absolute_url(vid='eschemagraph'), + alt=msg)) -class CWRTypeSchemaView(CWRTypePrimaryView): +class CWRTypeSchemaView(primary.PrimaryView): id = 'eschema' + __select__ = implements('CWRType') title = _('in memory relation schema') main_related_section = False - def render_entity_attributes(self, entity, siderelations): - super(CWRTypeSchemaView, self).render_entity_attributes(entity, siderelations) + def render_entity_attributes(self, entity): + super(CWRTypeSchemaView, self).render_entity_attributes(entity) rschema = self.vreg.schema.rschema(entity.name) viewer = SchemaViewer(self.req) layout = viewer.visit_relationschema(rschema) - self.w(ureport_as_html(layout)) + self.w(uilib.ureport_as_html(layout)) if not rschema.is_final(): - self.w(u'%s' % ( - html_escape(entity.absolute_url(vid='eschemagraph')), - html_escape(self.req._('graphical schema for %s') % entity.name))) + msg = self.req._('graphical schema for %s') % entity.name + self.w(tags.img(src=entity.absolute_url(vid='eschemagraph'), + alt=msg)) # schema images ############################################################### @@ -198,8 +194,9 @@ class SchemaImageView(TmpFileViewMixin, StartupView): id = 'schemagraph' + content_type = 'image/png' - skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of') + skip_rels = SKIPPED_RELS def _generate(self, tmpfile): """display global schema information""" skipmeta = not int(self.req.form.get('withmeta', 0)) @@ -209,9 +206,10 @@ class CWETypeSchemaImageView(TmpFileViewMixin, EntityView): id = 'eschemagraph' + __select__ = implements('CWEType') + content_type = 'image/png' - __select__ = implements('CWEType') - skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of') + skip_rels = SKIPPED_RELS def _generate(self, tmpfile): """display schema information for an entity""" diff -r a721966779be -r cba9f175da2d web/views/startup.py --- a/web/views/startup.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/startup.py Thu May 07 16:42:34 2009 +0200 @@ -6,21 +6,34 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" +_ = unicode from logilab.common.textutils import unormalize from logilab.mtconverter import html_escape from cubicweb.view import StartupView -from cubicweb.common.uilib import ureport_as_html, ajax_replace_url -from cubicweb.web.httpcache import EtagHTTPCacheManager - -_ = unicode +from cubicweb.selectors import match_user_groups +from cubicweb.common.uilib import ureport_as_html +from cubicweb.web import ajax_replace_url, uicfg, httpcache +from cubicweb.web.views.management import SecurityViewMixIn class ManageView(StartupView): id = 'manage' title = _('manage') - http_cache_manager = EtagHTTPCacheManager + http_cache_manager = httpcache.EtagHTTPCacheManager + + @classmethod + def vreg_initialization_completed(cls): + for eschema in cls.schema.entities(): + if eschema.schema_entity(): + uicfg.etypecat.setdefault(eschema, 'schema') + elif eschema.is_subobject(strict=True): + uicfg.etypecat.setdefault(eschema, 'subobject') + elif eschema.meta: + uicfg.etypecat.setdefault(eschema, 'system') + else: + uicfg.etypecat.setdefault(eschema, 'application') def display_folders(self): return False @@ -87,15 +100,15 @@ if manager: self.w(u'
%s
%s
%s
') def entity_types_table(self, eschemas): @@ -159,23 +172,132 @@ def call(self): """display schema information""" self.req.add_js('cubicweb.ajax.js') - self.req.add_css('cubicweb.schema.css') + self.req.add_css(('cubicweb.schema.css','cubicweb.acl.css')) withmeta = int(self.req.form.get('withmeta', 0)) + section = self.req.form.get('sec', '') self.w(u'%s\n' % ( html_escape(self.req.build_url('view', vid='schemagraph', withmeta=withmeta)), self.req._("graphical representation of the application'schema"))) if withmeta: self.w(u'
%s
' % ( - self.build_url('schema', withmeta=0), + html_escape(self.build_url('schema', withmeta=0, sec=section)), self.req._('hide meta-data'))) else: self.w(u'
%s
' % ( - self.build_url('schema', withmeta=1), + html_escape(self.build_url('schema', withmeta=1, sec=section)), self.req._('show meta-data'))) - self.w(u'
%s
' % + self.w(u'%s
' % (html_escape(ajax_replace_url('detailed_schema', '', 'schematext', skipmeta=int(not withmeta))), self.req._('detailed schema view'))) + if self.req.user.matching_groups('managers'): + self.w(u'%s' % + (html_escape(ajax_replace_url('detailed_schema', '', 'schema_security', + skipmeta=int(not withmeta))), + self.req._('security'))) + self.w(u'
') + if section: + self.wview(section, None) + self.w(u'
') + + +class ManagerSchemaPermissionsView(StartupView, SecurityViewMixIn): + id = 'schema_security' + __select__ = StartupView.__select__ & match_user_groups('managers') + + def call(self, display_relations=True, + skiprels=('is', 'is_instance_of', 'identity', 'owned_by', 'created_by')): + _ = self.req._ + formparams = {} + formparams['sec'] = self.id + formparams['withmeta'] = int(self.req.form.get('withmeta', True)) + schema = self.schema + # compute entities + entities = [eschema for eschema in schema.entities() + if not eschema.is_final()] + if not formparams['withmeta']: + entities = [eschema for eschema in entities + if not eschema.meta] + # compute relations + relations = [] + if display_relations: + relations = [rschema for rschema in schema.relations() + if not (rschema.is_final() or rschema.type in skiprels)] + if not formparams['withmeta']: + relations = [rschema for rschema in relations + if not rschema.meta] + # index + self.w(u'
') + self.w(u'

%s

' % _('index').capitalize()) + self.w(u'

%s

' % _('Entities').capitalize()) + ents = [] + for eschema in sorted(entities): + url = html_escape(self.build_url('schema', **formparams) + '#' + eschema.type) + ents.append(u'
%s (%s)' % (url, eschema.type, _(eschema.type))) + self.w('%s' % ', '.join(ents)) + self.w(u'

%s

' % (_('relations').capitalize())) + rels = [] + for eschema in sorted(relations): + url = html_escape(self.build_url('schema', **formparams) + '#' + eschema.type) + rels.append(u'%s (%s), ' % (url , eschema.type, _(eschema.type))) + self.w('%s' % ', '.join(ents)) + # entities + self.display_entities(entities, formparams) + # relations + if relations: + self.display_relations(relations, formparams) + self.w(u'
') + + def display_entities(self, entities, formparams): + _ = self.req._ + self.w(u'') + self.w(u'

%s

' % _('permissions for entities').capitalize()) + for eschema in sorted(entities): + self.w(u'
' % (eschema.type, eschema.type)) + self.w(u'

%s (%s) ' % (eschema.type, _(eschema.type))) + url = html_escape(self.build_url('schema', **formparams) + '#index') + self.w(u'%s' % (url, self.req.external_resource('UP_ICON'), _('up'))) + self.w(u'

') + self.w(u'
') + self.schema_definition(eschema, link=False) + + # display entity attributes only if they have some permissions modified + modified_attrs = [] + for attr, etype in eschema.attribute_definitions(): + if self.has_schema_modified_permissions(attr, attr.ACTIONS): + modified_attrs.append(attr) + if modified_attrs: + self.w(u'

%s

' % _('attributes with modified permissions:').capitalize()) + self.w(u'
') + self.w(u'
') + for attr in modified_attrs: + self.w(u'

%s (%s)

' % (attr.type, _(attr.type))) + self.schema_definition(attr, link=False) + self.w(u'
') + else: + self.w(u'') + + + def display_relations(self, relations, formparams): + _ = self.req._ + self.w(u'') + self.w(u'

%s

' % _('permissions for relations').capitalize()) + for rschema in sorted(relations): + self.w(u'
' % (rschema.type, rschema.type)) + self.w(u'

%s (%s) ' % (rschema.type, _(rschema.type))) + url = html_escape(self.build_url('schema', **formparams) + '#index') + self.w(u'%s' % (url, self.req.external_resource('UP_ICON'), _('up'))) + self.w(u'

') + self.w(u'
') + subjects = [str(subj) for subj in rschema.subjects()] + self.w(u'
%s %s (%s)
' % (_('subject_plural:'), + ', '.join( [str(subj) for subj in rschema.subjects()]), + ', '.join( [_(str(subj)) for subj in rschema.subjects()]))) + self.w(u'
%s %s (%s)
' % (_('object_plural:'), + ', '.join( [str(obj) for obj in rschema.objects()]), + ', '.join( [_(str(obj)) for obj in rschema.objects()]))) + self.schema_definition(rschema, link=False) + self.w(u'
') class SchemaUreportsView(StartupView): diff -r a721966779be -r cba9f175da2d web/views/tableview.py --- a/web/views/tableview.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/tableview.py Thu May 07 16:42:34 2009 +0200 @@ -14,7 +14,8 @@ from cubicweb.selectors import nonempty_rset, match_form_params from cubicweb.utils import make_uid from cubicweb.view import EntityView, AnyRsetView -from cubicweb.common.uilib import toggle_action, limitsize, jsonize, htmlescape +from cubicweb.common.uilib import toggle_action, limitsize, htmlescape +from cubicweb.web import jsonize from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget, PopupBoxMenu, BoxLink) from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens @@ -44,7 +45,7 @@ 'displayfilter': displayfilter}) return self.show_hide_actions(divid, not hidden) return () - + def _generate_form(self, divid, baserql, fwidgets, hidden=True, vidargs={}): """display a form to filter table's content. This should only occurs when a context eid is given @@ -88,7 +89,7 @@ else: displaycols = range(len(self.rset.syntax_tree().children[0].selection)) return displaycols - + def call(self, title=None, subvid=None, displayfilter=None, headers=None, displaycols=None, displayactions=None, actions=(), divid=None, cellvids=None, cellattrs=None): @@ -169,7 +170,7 @@ if currentlydisplayed: return [(showhide, showlabel, 'hidden', '%sShow' % divid), (showhide, hidelabel, None, '%sHide' % divid)] - return [(showhide, showlabel, None, '%sShow' % divid), + return [(showhide, showlabel, None, '%sShow' % divid), (showhide, hidelabel, 'hidden', '%sHide' % divid)] def render_actions(self, divid, actions): @@ -184,7 +185,7 @@ menu.append(BoxLink(url, label, klass, ident=ident, escape=True)) box.render(w=self.w) self.w(u'
') - + def get_columns(self, rqlstdescr, displaycols, headers, subvid, cellvids, cellattrs, mainindex): columns = [] @@ -218,11 +219,11 @@ # add column columns.append(column) return columns - + def render(self, cellvid, row, col, w): self.view('cell', self.rset, row=row, col=col, cellvid=cellvid, w=w) - + def get_rows(self): return self.rset @@ -251,12 +252,12 @@ finalview = 'editable-final' title = _('editable-table') - + class CellView(EntityView): __select__ = nonempty_rset() - + id = 'cell' - + def cell_call(self, row, col, cellvid=None): """ :param row, col: indexes locating the cell value in view's result set @@ -277,13 +278,13 @@ class InitialTableView(TableView): """same display as table view but consider two rql queries : - + * the default query (ie `rql` form parameter), which is only used to select this view and to build the filter form. This query should have the same structure as the actual without actual restriction (but link to restriction variables) and usually with a limit for efficiency (limit set to 2 is advised) - + * the actual query (`actualrql` form parameter) whose results will be displayed with default restrictions set """ @@ -292,7 +293,7 @@ # should not be displayed in possible view since it expects some specific # parameters title = None - + def call(self, title=None, subvid=None, headers=None, divid=None, displaycols=None, displayactions=None): """Dumps a table displaying a composite query""" diff -r a721966779be -r cba9f175da2d web/views/timetable.py --- a/web/views/timetable.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/timetable.py Thu May 07 16:42:34 2009 +0200 @@ -45,7 +45,7 @@ user = u"*" the_dates = [] if task.start and task.stop: - if task.start.absdate == task.stop.absdate: + if task.start.toordinal() == task.stop.toordinal(): the_dates.append(task.start) else: the_dates += date_range( task.start, task.stop ) @@ -164,7 +164,7 @@ previous_is_empty = False klass = "even" - if date.day_of_week in (5, 6) and not empty_line: + if date.weekday() in (5, 6) and not empty_line: klass = "odd" self.w(u'' % klass) odd = not odd diff -r a721966779be -r cba9f175da2d web/views/workflow.py --- a/web/views/workflow.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/workflow.py Thu May 07 16:42:34 2009 +0200 @@ -23,26 +23,27 @@ from cubicweb.web.views import TmpFileViewMixin from cubicweb.web.views.boxes import EditBox +_ = unicode -EditBox.rmode.set_rtag('create', 'destination_state', 'subject', 'Transition') -EditBox.rmode.set_rtag('create', 'allowed_transition', 'object', 'Transition') -EditBox.rmode.set_rtag('create', 'destination_state', 'object', 'State') -EditBox.rmode.set_rtag('create', 'allowed_transition', 'subject', 'State') +EditBox.rmode.tag_relation('create', ('Transition', 'destination_state', '*'), 'subject') +EditBox.rmode.tag_relation('create', ('*', 'allowed_transition', 'Transition'), 'object') +EditBox.rmode.tag_relation('create', ('*', 'destination_state', 'State'), 'object') +EditBox.rmode.tag_relation('create', ('State', 'allowed_transition', '*'), 'subject') # IWorkflowable views ######################################################### class ChangeStateForm(form.EntityFieldsForm): id = 'changestate' - + __method = StringField(name='__method', initial='set_state', widget=HiddenInput) - state = StringField(widget=HiddenInput, eidparam=True) - trcomment = RichTextField(eidparam=True) + state = StringField(eidparam=True, widget=HiddenInput) + trcomment = RichTextField(label=_('comment:'), eidparam=True) form_buttons = [SubmitButton(stdmsgs.YES), Button(stdmsgs.NO, cwaction='cancel')] - + class ChangeStateFormView(FormViewMixIn, view.EntityView): id = 'statuschange' title = _('status change') @@ -91,7 +92,7 @@ rql += ', WF owned_by U?' displaycols = range(5) headers = (_('from_state'), _('to_state'), _('comment'), _('date'), - _('CWUser')) + _('CWUser')) else: sel += ',C' displaycols = range(4) @@ -111,7 +112,7 @@ class CellView(view.EntityView): id = 'cell' __select__ = implements('TrInfo') - + def cell_call(self, row, col, cellvid=None): self.w(self.entity(row, col).printable_value('comment')) @@ -120,30 +121,30 @@ """convenience trick, State's incontext view should not be clickable""" id = 'incontext' __select__ = implements('State') - + def cell_call(self, row, col): self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col))) # workflow images ############################################################# - + class ViewWorkflowAction(action.Action): id = 'workflow' __select__ = implements('CWEType') & has_related_entities('state_of', 'object') - + category = 'mainactions' title = _('view workflow') def url(self): entity = self.rset.get_entity(self.row or 0, self.col or 0) return entity.absolute_url(vid='workflow') - + class CWETypeWorkflowView(view.EntityView): id = 'workflow' __select__ = implements('CWEType') - cache_max_age = 60*60*2 # stay in http cache for 2 hours by default - + cache_max_age = 60*60*2 # stay in http cache for 2 hours by default + def cell_call(self, row, col, **kwargs): entity = self.entity(row, col) self.w(u'

%s

' % (self.req._('workflow for %s') @@ -156,10 +157,10 @@ class WorkflowDotPropsHandler(object): def __init__(self, req): self._ = req._ - + def node_properties(self, stateortransition): """return default DOT drawing options for a state or transition""" - props = {'label': stateortransition.name, + props = {'label': stateortransition.name, 'fontname': 'Courier'} if hasattr(stateortransition, 'state_of'): props['shape'] = 'box' @@ -179,7 +180,7 @@ if descr: props['label'] += escape('\n'.join(descr)) return props - + def edge_properties(self, transition, fromstate, tostate): return {'label': '', 'dir': 'forward', 'color': 'black', 'style': 'filled'} @@ -193,11 +194,11 @@ for state in self.entity.reverse_state_of: state.complete() yield state.eid, state - + for transition in self.entity.reverse_transition_of: transition.complete() yield transition.eid, transition - + def edges(self): for transition in self.entity.reverse_transition_of: for incomingstate in transition.reverse_allowed_transition: @@ -209,7 +210,7 @@ id = 'ewfgraph' content_type = 'image/png' __select__ = implements('CWEType') - + def _generate(self, tmpfile): """display schema information for an entity""" entity = self.entity(self.row, self.col) diff -r a721966779be -r cba9f175da2d web/views/xbel.py --- a/web/views/xbel.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/xbel.py Thu May 07 16:42:34 2009 +0200 @@ -11,18 +11,18 @@ from cubicweb.selectors import implements from cubicweb.view import EntityView -from cubicweb.web.views.xmlrss import XmlView +from cubicweb.web.views.xmlrss import XMLView -class XbelView(XmlView): +class XbelView(XMLView): id = 'xbel' title = _('xbel') templatable = False - content_type = 'text/xml' #application/xbel+xml - + content_type = 'text/xml' #application/xbel+xml + def cell_call(self, row, col): self.wview('xbelitem', self.rset, row=row, col=col) - + def call(self): """display a list of entities by calling their view""" title = self.page_title() @@ -34,7 +34,7 @@ for i in xrange(self.rset.rowcount): self.cell_call(i, 0) self.w(u"") - + class XbelItemView(EntityView): id = 'xbelitem' @@ -48,10 +48,10 @@ def url(self, entity): return entity.absolute_url() - + class XbelItemBookmarkView(XbelItemView): __select__ = implements('Bookmark') def url(self, entity): return entity.actual_url() - + diff -r a721966779be -r cba9f175da2d web/views/xmlrss.py --- a/web/views/xmlrss.py Thu May 07 16:33:22 2009 +0200 +++ b/web/views/xmlrss.py Thu May 07 16:42:34 2009 +0200 @@ -22,7 +22,7 @@ # base xml views ############################################################## -class XmlView(EntityView): +class XMLView(EntityView): """xml view for entities""" id = 'xml' title = _('xml') @@ -43,7 +43,7 @@ self.w(u'\n' % self.xml_root) -class XmlItemView(EntityView): +class XMLItemView(EntityView): id = 'xmlitem' def cell_call(self, row, col): @@ -67,7 +67,7 @@ self.w(u'\n' % (entity.e_schema)) -class XmlRsetView(AnyRsetView): +class XMLRsetView(AnyRsetView): """dumps raw rset as xml""" id = 'rsetxml' title = _('xml export') @@ -142,7 +142,7 @@ self.w(u'rss\n' % (xml_escape(url), rss)) -class RssView(XmlView): +class RSSView(XMLView): id = 'rss' title = _('rss') templatable = False @@ -155,11 +155,11 @@ self.w(u'\n' % req.encoding) self.w(u'\n') self.w(u' \n') - self.w(u' %s RSS Feed\n' % html_escape(self.page_title())) - self.w(u' %s\n' % html_escape(req.form.get('vtitle', ''))) + self.w(u' %s RSS Feed\n' % xml_escape(self.page_title())) + self.w(u' %s\n' % xml_escape(req.form.get('vtitle', ''))) params = req.form.copy() params.pop('vid', None) - self.w(u' %s\n' % html_escape(self.build_url(**params))) + self.w(u' %s\n' % xml_escape(self.build_url(**params))) def _close(self): self.w(u' \n') @@ -175,7 +175,8 @@ def cell_call(self, row, col): self.wview('rssitem', self.rset, row=row, col=col) -class RssItemView(EntityView): + +class RSSItemView(EntityView): id = 'rssitem' date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600) add_div_section = False @@ -183,9 +184,9 @@ def cell_call(self, row, col): entity = self.complete_entity(row, col) self.w(u'\n') - self.w(u'%s\n' % html_escape(entity.absolute_url())) + self.w(u'%s\n' % xml_escape(entity.absolute_url())) self.render_title_link(entity) - self._marker('description', html_escape(entity.dc_description())) + self._marker('description', xml_escape(entity.dc_description())) self._marker('dc:date', entity.dc_date(self.date_format)) self.render_entity_creator(entity) self.w(u'\n') @@ -201,4 +202,4 @@ def _marker(self, marker, value): if value: - self.w(u' <%s>%s\n' % (marker, html_escape(value), marker)) + self.w(u' <%s>%s\n' % (marker, xml_escape(value), marker)) diff -r a721966779be -r cba9f175da2d web/wdoc/custom_view_rss_fr.rst --- a/web/wdoc/custom_view_rss_fr.rst Thu May 07 16:33:22 2009 +0200 +++ b/web/wdoc/custom_view_rss_fr.rst Thu May 07 16:42:34 2009 +0200 @@ -14,5 +14,5 @@ :raw-html:`

latest changes

` +alt="rss" src="data/rss.png"> latest changes

` diff -r a721966779be -r cba9f175da2d web/webconfig.py --- a/web/webconfig.py Thu May 07 16:33:22 2009 +0200 +++ b/web/webconfig.py Thu May 07 16:42:34 2009 +0200 @@ -79,6 +79,12 @@ 'if anonymous-user is set', 'group': 'main', 'inputlevel': 1, }), + ('allow-email-login', + {'type' : 'yn', + 'default': False, + 'help': 'allow users to login with their primary email if set', + 'group': 'main', 'inputlevel': 2, + }), ('query-log-file', {'type' : 'string', 'default': None, diff -r a721966779be -r cba9f175da2d web/widgets.py --- a/web/widgets.py Thu May 07 16:33:22 2009 +0200 +++ b/web/widgets.py Thu May 07 16:42:34 2009 +0200 @@ -63,7 +63,7 @@ autoid = True html_attributes = set(('id', 'class', 'tabindex', 'accesskey', 'onchange', 'onkeypress')) cubicwebns_attributes = set() - + def __init__(self, vreg, subjschema, rschema, objschema, role='subject', description=None, **kwattrs): @@ -83,12 +83,12 @@ because widget instances are cached) """ # brute force copy (subclasses don't have the - # same __init__ prototype) + # same __init__ prototype) widget = self.__new__(self.__class__) widget.__dict__ = dict(self.__dict__) widget.attrs = dict(widget.attrs) return widget - + @staticmethod def size_constraint_attrs(attrs, maxsize): """set html attributes in the attrs dict to consider maxsize""" @@ -105,7 +105,7 @@ elif name in self.html_attributes: attrs.append(u'%s="%s"' % (name, value)) return u' '.join(sorted(attrs)) - + def required(self, entity): """indicates if the widget needs a value to be filled in""" card = self.rschema.cardinality(self.subjtype, self.objtype, self.role) @@ -116,7 +116,7 @@ return self.rname except AttributeError: return eid_param(self.name, entity.eid) - + def render_label(self, entity, label=None): """render widget's label""" label = label or self.rschema.display_name(entity.req, self.role) @@ -130,7 +130,7 @@ else: label = u'%s' % (forattr, label) return label - + def render_error(self, entity): """return validation error for widget's field of the given entity, if any @@ -144,25 +144,26 @@ def render_help(self, entity): """render a help message about the (edited) field""" req = entity.req - help = [u'
'] + help = [u'
'] descr = self.description or self.rschema.rproperty(self.subjtype, self.objtype, 'description') if descr: - help.append(u'%s' % req._(descr)) + help.append(u'%s' % req._(descr)) example = self.render_example(req) if example: - help.append(u'(%s: %s)' + help.append(u'(%s: %s)' % (req._('sample format'), example)) + help.append(u'
') return u' '.join(help) - + def render_example(self, req): return u'' - + def render(self, entity): """render the widget for a simple view""" if not entity.has_eid(): return u'' return entity.printable_value(self.name) - + def edit_render(self, entity, tabindex=None, includehelp=False, useid=None, **kwargs): """render the widget for edition""" @@ -180,7 +181,7 @@ if includehelp: output += self.render_help(entity) return output - + def _edit_render(self, entity): """do the actual job to render the widget for edition""" raise NotImplementedError @@ -196,7 +197,7 @@ elif entity.has_eid(): return [row[0] for row in entity.related(self.name, self.role)] return () - + def current_value(self, entity): return _value_from_values(self.current_values(entity)) @@ -213,13 +214,13 @@ if not isinstance(cdvalues, (list, tuple)): cdvalues = (cdvalues,) return cdvalues - + def current_display_value(self, entity): """same as .current_value but consider values stored in session in case of validation error """ return _value_from_values(self.current_display_values(entity)) - + def hidden_input(self, entity, qvalue): """return an hidden field which 1. indicates that a field is edited @@ -258,7 +259,7 @@ def __init__(self, vreg, subjschema, rschema, objschema, role='subject', **kwattrs): InputWidget.__init__(self, vreg, subjschema, rschema, objschema, - role='subject', + role='subject', **kwattrs) # disable access key del self.attrs['accesskey'] @@ -270,18 +271,18 @@ def current_display_value(self, entity): value = InputWidget.current_display_value(self, entity) return value or INTERNAL_FIELD_VALUE - + def render_label(self, entity, label=None): """render widget's label""" return u'' - + def render_help(self, entity): return u'' - + def hidden_input(self, entity, value): """no hidden input for hidden input""" return '' - + class EidWidget(HiddenWidget): @@ -297,15 +298,15 @@ """set html attributes in the attrs dict to consider maxsize""" attrs['size'] = min(maxsize, 40) attrs['maxlength'] = maxsize - - + + class AutoCompletionWidget(StringWidget): cubicwebns_attributes = (StringWidget.cubicwebns_attributes | set(('accesskey', 'size', 'maxlength'))) attrs = () - + wdgtype = 'SuggestField' - + def current_value(self, entity): value = StringWidget.current_value(self, entity) return value or INTERNAL_FIELD_VALUE @@ -344,22 +345,22 @@ class StaticFileAutoCompletionWidget(AutoCompletionWidget): wdgtype = 'StaticFileSuggestField' - + def _get_url(self, entity): return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema] class RestrictedAutoCompletionWidget(AutoCompletionWidget): - wdgtype = 'RestrictedSuggestField' + wdgtype = 'RestrictedSuggestField' - + class PasswordWidget(InputWidget): input_type = 'password' - + def required(self, entity): if InputWidget.required(self, entity) and not entity.has_eid(): return True return False - + def current_values(self, entity): # on existant entity, show password field has non empty (we don't have # the actual value @@ -374,10 +375,10 @@ html, self.input_type, name, name, entity.req.next_tabindex(), entity.req._('confirm password')) - + class TextWidget(Widget): html_attributes = Widget.html_attributes | set(('rows', 'cols')) - + @staticmethod def size_constraint_attrs(attrs, maxsize): """set html attributes in the attrs dict to consider maxsize""" @@ -385,12 +386,12 @@ attrs['cols'], attrs['rows'] = 60, 5 else: attrs['cols'], attrs['rows'] = 80, 10 - + def render(self, entity): if not entity.has_eid(): return u'' return entity.printable_value(self.name) - + def _edit_render(self, entity, with_format=True): req = entity.req editor = self._edit_render_textarea(entity, with_format) @@ -398,7 +399,7 @@ if isinstance(value, basestring): value = html_escape(value) return u'%s%s' % (self.hidden_input(entity, value), editor) - + def _edit_render_textarea(self, entity, with_format): self.attrs.setdefault('cols', 80) self.attrs.setdefault('rows', 20) @@ -426,8 +427,8 @@ fmtwdgstr = '' return u'%s
' % ( fmtwdgstr, self.rname, self.format_attrs(), dvalue) - - + + class CheckBoxWidget(Widget): html_attributes = Widget.html_attributes | set(('checked', )) def _edit_render(self, entity): @@ -460,7 +461,7 @@ u'%s
' % (self.rname, attrs2, entity.req._('no'))] return '\n'.join(wdgs) - + class FileWidget(Widget): need_multipart = True def _file_wdg(self, entity): @@ -492,7 +493,7 @@ wdgs.append(u'
') wdgs.append(req._('currently attached file: %s' % entity.dc_title())) return '\n'.join(wdgs) - + def _edit_render(self, entity): return self.hidden_input(entity, None) + self._file_wdg(entity) @@ -510,7 +511,7 @@ 'You can either submit a new file using the browse button above' ', or edit file content online with the widget below.') return msg - + def _edit_render(self, entity): wdgs = [self._file_wdg(entity)] if entity.attr_metadata(self.name, 'format') in ('text/plain', 'text/html', 'text/rest'): @@ -534,7 +535,7 @@ class ComboBoxWidget(Widget): html_attributes = Widget.html_attributes | set(('multiple', 'size')) - + def __init__(self, vreg, subjschema, rschema, objschema, multiple=False, **kwattrs): super(ComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema, @@ -545,10 +546,10 @@ self.attrs['size'] = '5' # disable access key (dunno why but this is not allowed by xhtml 1.0) del self.attrs['accesskey'] - + def vocabulary(self, entity): raise NotImplementedError() - + def form_value(self, entity, value, values): if value in values: flag = 'selected="selected"' @@ -574,9 +575,9 @@ res.append(u'') return '\n'.join(res) - + class StaticComboBoxWidget(ComboBoxWidget): - + def __init__(self, vreg, subjschema, rschema, objschema, vocabfunc, multiple=False, sort=False, **kwattrs): super(StaticComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema, @@ -591,11 +592,11 @@ if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'): return zip((entity.req._(v) for v in choices), choices) return zip(choices, choices) - + class EntityLinkComboBoxWidget(ComboBoxWidget): """to be used be specific forms""" - + def current_values(self, entity): if entity.has_eid(): return [r[0] for r in entity.related(self.name, self.role)] @@ -603,13 +604,13 @@ if hasattr(entity, defaultmeth): return getattr(entity, defaultmeth)() return () - + def vocabulary(self, entity): return [('', INTERNAL_FIELD_VALUE)] + entity.vocabulary(self.rschema, self.role) class RawDynamicComboBoxWidget(EntityLinkComboBoxWidget): - + def vocabulary(self, entity, limit=None): req = entity.req # first see if its specified by __linkto form parameters @@ -632,7 +633,7 @@ class DynamicComboBoxWidget(RawDynamicComboBoxWidget): - + def vocabulary(self, entity, limit=None): return sorted(super(DynamicComboBoxWidget, self).vocabulary(entity, limit)) @@ -669,11 +670,11 @@ kwattrs['size'] = 5 kwattrs['maxlength'] = 15 StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + def render_example(self, req): return '23' - - + + class FloatWidget(StringWidget): def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs): kwattrs['size'] = 5 @@ -683,7 +684,7 @@ def render_example(self, req): formatstr = req.property_value('ui.float-format') return formatstr % 1.23 - + def current_values(self, entity): values = entity.attribute_values(self.name) if values: @@ -702,7 +703,7 @@ kwattrs['size'] = 5 kwattrs['maxlength'] = 15 StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + def render_example(self, req): return '345.0300' @@ -724,7 +725,7 @@ daynames = [_(dname) for dname in cls.daynames] req.html_headers.define_var('MONTHNAMES', monthnames) req.html_headers.define_var('DAYNAMES', daynames) - + def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs): kwattrs.setdefault('size', 10) kwattrs.setdefault('maxlength', 10) @@ -734,12 +735,12 @@ values = entity.attribute_values(self.name) if values and hasattr(values[0], 'strftime'): formatstr = entity.req.property_value(self.format_key) - return [values[0].strftime(formatstr)] + return [values[0].strftime(str(formatstr))] return values def render_example(self, req): formatstr = req.property_value(self.format_key) - return datetime.now().strftime(formatstr) + return datetime.now().strftime(str(formatstr)) def _edit_render(self, entity): @@ -750,14 +751,15 @@ def render_help(self, entity): """calendar popup widget""" req = entity.req - help = [ u'
' ] + help = [ u'
' ] descr = self.rschema.rproperty(self.subjtype, self.objtype, 'description') if descr: - help.append('%s' % req._(descr)) + help.append('%s' % req._(descr)) example = self.render_example(req) if example: - help.append('(%s: %s)' + help.append('(%s: %s)' % (req._('sample format'), example)) + help.append(u'
') return u' '.join(help) def render_calendar_popup(self, entity): @@ -784,13 +786,13 @@ kwattrs['size'] = 16 kwattrs['maxlength'] = 16 DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + def render_example(self, req): formatstr1 = req.property_value('ui.datetime-format') formatstr2 = req.property_value('ui.date-format') return req._('%(fmt1)s, or without time: %(fmt2)s') % { - 'fmt1': datetime.now().strftime(formatstr1), - 'fmt2': datetime.now().strftime(formatstr2), + 'fmt1': datetime.now().strftime(str(formatstr1)), + 'fmt2': datetime.now().strftime(str(formatstr2)), } @@ -801,26 +803,26 @@ kwattrs['maxlength'] = 5 StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + class EmailWidget(StringWidget): - + def render(self, entity): email = getattr(entity, self.name) if not email: return u'' return u'%s' % (email, email) - + class URLWidget(StringWidget): - + def render(self, entity): url = getattr(entity, self.name) if not url: return u'' url = html_escape(url) return u'%s' % (url, url) - + class EmbededURLWidget(StringWidget): - + def render(self, entity): url = getattr(entity, self.name) if not url: @@ -828,7 +830,7 @@ aurl = html_escape(entity.build_url('embed', url=url)) return u'%s' % (aurl, url) - + def widget_factory(vreg, subjschema, rschema, objschema, role='subject', **kwargs): @@ -857,14 +859,14 @@ # factories to find the most adapated widget according to a type and other constraints - + def _string_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs): w = None for c in rschema.rproperty(subjschema, objschema, 'constraints'): if isinstance(c, StaticVocabularyConstraint): # may have been set by a previous SizeConstraint but doesn't make sense # here (even doesn't have the same meaning on a combobox actually) - kwargs.pop('size', None) + kwargs.pop('size', None) return (wcls or StaticComboBoxWidget)(vreg, subjschema, rschema, objschema, vocabfunc=c.vocabulary, **kwargs) if isinstance(c, SizeConstraint) and c.max is not None: @@ -914,7 +916,7 @@ 'String' : StringWidget, 'Time': TimeWidget, } - + # widgets registry WIDGETS = {} def register(widget_list):