# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1242298091 -7200 # Node ID aa09e20dd8c02f3a22c72561cf800bf4474eaa95 # Parent 49075f57cf2c4825d886fafb818ac7e0efcc428c# Parent 6d541c610165a070167c37542d83a2b541ae338b backport tls-sprint diff -r 49075f57cf2c -r aa09e20dd8c0 __init__.py --- a/__init__.py Tue May 05 17:18:49 2009 +0200 +++ b/__init__.py Thu May 14 12:48:11 2009 +0200 @@ -22,13 +22,7 @@ from urllib import quote as urlquote, unquote as urlunquote from logilab.common.decorators import cached - -def set_log_methods(cls, logger): - """bind standart logger's methods as static methods on the class - """ - cls._logger = logger - for attr in ('debug', 'info', 'warning', 'error', 'critical', 'exception'): - setattr(cls, attr, getattr(logger, attr)) +from logilab.common.logging_ext import set_log_methods if os.environ.get('APYCOT_ROOT'): logging.basicConfig(level=logging.CRITICAL) @@ -74,7 +68,7 @@ encoding = 'utf-8' self.encoding = encoding # cache result of execution for (rql expr / eids), - # should be emptied on commit/rollback of the server session / web + # should be emptied on commit/rollback of the server session / web # connection self.local_perm_cache = {} @@ -82,14 +76,14 @@ if self.user: return self.user.property_value(key) return self.vreg.property_value(key) - + def etype_rset(self, etype, size=1): """return a fake result set for a particular entity type""" from cubicweb.rset import ResultSet rset = ResultSet([('A',)]*size, '%s X' % etype, description=[(etype,)]*size) - def get_entity(row, col=0, etype=etype, vreg=self.vreg, rset=rset): - return self.vreg.etype_class(etype)(self, rset, row, col) + def get_entity(row, col=0, etype=etype, req=self, rset=rset): + return req.vreg.etype_class(etype)(req, rset, row, col) rset.get_entity = get_entity return self.decorate_rset(rset) @@ -114,7 +108,7 @@ return None # url generation methods ################################################## - + def build_url(self, method, base_url=None, **kwargs): """return an absolute URL using params dictionary key/values as URL parameters. Values are automatically URL quoted, and the @@ -130,7 +124,7 @@ if not kwargs: return u'%s%s' % (base_url, path) return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs)) - + def build_url_params(self, **kwargs): """return encoded params to incorporate them in an URL""" @@ -154,7 +148,7 @@ def url_unquote(self, quoted): """returns a unicode unquoted string - + decoding is based on `self.encoding` which is the encoding used in `url_quote` """ @@ -164,10 +158,10 @@ return unicode(urlunquote(quoted), self.encoding) except UnicodeDecodeError: # might occurs on manually typed URLs return unicode(urlunquote(quoted), 'iso-8859-1') - + # session's user related methods ##################################### - + @cached def user_data(self): """returns a dictionnary with this user's information""" @@ -197,31 +191,44 @@ return False # abstract methods to override according to the web front-end ############# - + def base_url(self): """return the root url of the application""" raise NotImplementedError - + def decorate_rset(self, rset): """add vreg/req (at least) attributes to the given result set """ raise NotImplementedError - + def describe(self, eid): """return a tuple (type, sourceuri, extid) for the entity with id """ raise NotImplementedError - + -# XXX 2.45 is allowing nicer entity type names, use this map for bw compat -ETYPE_NAME_MAP = {'Eetype': 'EEType', - 'Ertype': 'ERType', - 'Efrdef': 'EFRDef', - 'Enfrdef': 'ENFRDef', - 'Econstraint': 'EConstraint', - 'Econstrainttype': 'EConstraintType', - 'Epermission': 'EPermission', - 'Egroup': 'EGroup', - 'Euser': 'EUser', - 'Eproperty': 'EProperty', +# XXX 2.45 is allowing nicer entity type names, use this map for bw compat +ETYPE_NAME_MAP = {# 3.2 migration + 'ECache': 'CWCache', + 'EUser': 'CWUser', + 'EGroup': 'CWGroup', + 'EProperty': 'CWProperty', + 'EFRDef': 'CWAttribute', + 'ENFRDef': 'CWRelation', + 'ERType': 'CWRType', + 'EEType': 'CWEType', + 'EConstraintType': 'CWConstraintType', + 'EConstraint': 'CWConstraint', + 'EPermission': 'CWPermission', + # 2.45 migration + 'Eetype': 'CWEType', + 'Ertype': 'CWRType', + 'Efrdef': 'CWAttribute', + 'Enfrdef': 'CWRelation', + 'Econstraint': 'CWConstraint', + 'Econstrainttype': 'CWConstraintType', + 'Epermission': 'CWPermission', + 'Egroup': 'CWGroup', + 'Euser': 'CWUser', + 'Eproperty': 'CWProperty', 'Emailaddress': 'EmailAddress', 'Rqlexpression': 'RQLExpression', 'Trinfo': 'TrInfo', @@ -255,7 +262,7 @@ 'ezone': 'zone', 'i18ncontent': 'i18ncontent', 'svnfile': 'vcsfile', - + 'eclassschemes': 'keyword', 'eclassfolders': 'folder', 'eclasstags': 'tag', @@ -269,11 +276,6 @@ 'agueol': 'agueol', 'docaster': 'docaster', 'asteretud': 'asteretud', - - # XXX temp - 'keywords': 'keyword', - 'folders': 'folder', - 'tags': 'tag', } def neg_role(role): @@ -292,4 +294,4 @@ return obj.target except AttributeError: return neg_role(obj.role) - + diff -r 49075f57cf2c -r aa09e20dd8c0 __pkginfo__.py --- a/__pkginfo__.py Tue May 05 17:18:49 2009 +0200 +++ b/__pkginfo__.py Thu May 14 12:48:11 2009 +0200 @@ -6,7 +6,7 @@ distname = "cubicweb" modname = "cubicweb" -numversion = (3, 1, 5) +numversion = (3, 2, 0) version = '.'.join(str(num) for num in numversion) license = 'LGPL v2' diff -r 49075f57cf2c -r aa09e20dd8c0 _exceptions.py --- a/_exceptions.py Tue May 05 17:18:49 2009 +0200 +++ b/_exceptions.py Thu May 14 12:48:11 2009 +0200 @@ -26,9 +26,9 @@ """a misconfiguration error""" class InternalError(CubicWebException): - """base class for exceptions which should not occurs""" + """base class for exceptions which should not occurs""" -class SecurityError(CubicWebException): +class SecurityError(CubicWebException): """base class for cubicweb server security exception""" class RepositoryError(CubicWebException): @@ -39,7 +39,7 @@ class CubicWebRuntimeError(CubicWebException): """base class for runtime exceptions""" - + # repository exceptions ####################################################### class ConnectionError(RepositoryError): @@ -53,7 +53,7 @@ class BadConnectionId(ConnectionError): """raised when a bad connection id is given or when an attempt to establish a connection failed""" - + BadSessionId = BadConnectionId # XXX bw compat for pyro connections class UnknownEid(RepositoryError): @@ -68,7 +68,7 @@ """no source support a relation type""" msg = 'No source supports %r relation\'s type' - + # security exceptions ######################################################### class Unauthorized(SecurityError): @@ -80,7 +80,7 @@ var = None #def __init__(self, *args): # self.args = args - + def __str__(self): try: if self.args and len(self.args) == 2: @@ -90,7 +90,7 @@ return self.msg except Exception, ex: return str(ex) - + # source exceptions ########################################################### class EidNotInSource(SourceException): @@ -98,8 +98,8 @@ source has failed """ msg = 'No entity with eid %s in %s' - - + + # registry exceptions ######################################################### class RegistryException(CubicWebException): @@ -110,16 +110,16 @@ this is usually a programming/typo error... """ - + class ObjectNotFound(RegistryException): """raised when an unregistered object is requested this may be a programming/typo or a misconfiguration error """ - + # class ViewNotFound(ObjectNotFound): # """raised when an unregistered view is called""" - + class NoSelectableObject(RegistryException): """some views with the given vid have been found but no one is applyable to the result set @@ -144,5 +144,5 @@ """server execution control error (already started, not running...)""" # pylint: disable-msg=W0611 -from logilab.common.clcommands import BadCommandUsage +from logilab.common.clcommands import BadCommandUsage diff -r 49075f57cf2c -r aa09e20dd8c0 appobject.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/appobject.py Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,329 @@ +"""Base class for dynamically loaded objects manipulated in the web interface + +: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 datetime import datetime, timedelta + +from logilab.common.decorators import classproperty +from logilab.common.deprecation import obsolete + +from rql.nodes import VariableRef, SubQuery +from rql.stmts import Union, Select + +from cubicweb import Unauthorized, NoSelectableObject +from cubicweb.vregistry import VObject, AndSelector +from cubicweb.selectors import yes +from cubicweb.utils import UStringIO, ustrftime + +ONESECOND = timedelta(0, 1, 0) + +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 + :schema: + the application's schema + :config: + the application's configuration + + At instantiation time, the following attributes are set on the instance: + :req: + current request + :rset: + result set on which the object is applied + """ + __select__ = yes() + + @classmethod + def registered(cls, vreg): + super(AppRsetObject, cls).registered(vreg) + cls.vreg = vreg + cls.schema = vreg.schema + 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 + selection according to a request, a result set, and optional + row and col + """ + assert len(args) <= 2 + return cls(*args, **kwargs) + + # Eproperties definition: + # key: id of the property (the actual CWProperty key is build using + # .. + # value: tuple (property type, vocabfunc, default value, property description) + # possible types are those used by `logilab.common.configuration` + # + # 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(): + pdef = pdef.copy() # may be shared + 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) + + @classproperty + @obsolete('use __select__ and & or | operators') + def __selectors__(cls): + selector = cls.__select__ + if isinstance(selector, AndSelector): + return tuple(selector.selectors) + 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 + self.rset = rset + 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 + - etc. + """ + if cachename in CACHE_REGISTRY: + cache = CACHE_REGISTRY[cachename] + else: + cache = Cache() + 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', + {'name':cachename}).get_entity(0,0) + cache.latest_cache_lookup = _now + if not ecache.valid(cache.cache_creation_date): + cache.clear() + cache.cache_creation_date = _now + return cache + + 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 + currently displayed page when necessary + """ + # try to get page boundaries from the navigation component + # XXX we should probably not have a ref to this component here (eg in + # cubicweb.common) + nav = self.vreg.select_component('navigation', self.req, self.rset) + if nav: + start, stop = nav.page_boundaries() + rql = self._limit_offset_rql(stop - start, start) + # result set may have be limited manually in which case navigation won't + # apply + elif self.rset.limited: + rql = self._limit_offset_rql(*self.rset.limited) + # navigation component doesn't apply and rset has not been limited, no + # need to limit query + 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: + select = rqlst.children[0] + olimit, ooffset = select.limit, select.offset + select.limit, select.offset = limit, offset + rql = rqlst.as_string(kwargs=self.rset.args) + # restore original limit/offset + select.limit, select.offset = olimit, ooffset + else: + newselect = Select() + newselect.limit = limit + newselect.offset = offset + aliases = [VariableRef(newselect.get_variable(vref.name, i)) + for i, vref in enumerate(rqlst.selection)] + newselect.set_with([SubQuery(aliases, rqlst)], check=False) + newunion = Union() + newunion.append(newselect) + 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: + view = self.vreg.select_view(__vid, self.req, rset, **kwargs) + except NoSelectableObject: + if __fallback_vid is None: + raise + view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs) + return view.render(**kwargs) + + def initialize_varmaker(self): + varmaker = self.req.get_page_data('rql_varmaker') + if varmaker is None: + varmaker = self.req.varmaker + self.req.set_page_data('rql_varmaker', varmaker) + self.varmaker = varmaker + + # 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 + publishing method to use may be specified or will be guessed. + """ + # XXX I (adim) think that if method is passed explicitly, we should + # not try to process it and directly call req.build_url() + if method is None: + method = self.controller + if method == 'view' and self.req.from_controller() == 'view' and \ + not '_restpath' in kwargs: + method = self.req.relative_path(includeparams=False) or 'view' + 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) + """ + entity = self.entity(row, col) + entity.complete(skip_bytes=skip_bytes) + return entity + + def user_rql_callback(self, args, msg=None): + """register a user callback to execute some rql query and return an url + to call it ready to be inserted in html + """ + 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 '') + return "javascript:userCallbackThenReloadPage('%s', %s)" % ( + cbname, msg) + + # formating methods ####################################################### + + def tal_render(self, template, variables): + """render a precompiled page template with variables in the given + dictionary as context + """ + from cubicweb.ext.tal import CubicWebContext + context = CubicWebContext() + context.update({'self': self, 'rset': self.rset, '_' : self.req._, + 'req': self.req, 'user': self.req.user}) + context.update(variables) + output = UStringIO() + template.expand(context, output) + return output.getvalue() + + def format_date(self, date, date_format=None, time=False): + """return a string for a date time according to application's + configuration + """ + if date: + if date_format is None: + if time: + date_format = self.req.property_value('ui.datetime-format') + else: + date_format = self.req.property_value('ui.date-format') + return ustrftime(date, date_format) + return u'' + + def format_time(self, time): + """return a string for a time according to application's + configuration + """ + if time: + return ustrftime(time, self.req.property_value('ui.time-format')) + return u'' + + def format_float(self, num): + """return a string for floating point number according to application's + configuration + """ + 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 + selection + """ + return cls(*args, **kwargs) + + def __init__(self, req=None, rset=None, **kwargs): + self.req = req + self.rset = rset + self.__dict__.update(kwargs) diff -r 49075f57cf2c -r aa09e20dd8c0 common/__init__.py --- a/common/__init__.py Tue May 05 17:18:49 2009 +0200 +++ b/common/__init__.py Thu May 14 12:48:11 2009 +0200 @@ -2,7 +2,7 @@ hg stserver side and on the client side :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 """ @@ -15,38 +15,38 @@ class COMMA_JOIN(FunctionDescr): supported_backends = ('postgres', 'sqlite',) rtype = 'String' - + @classmethod def st_description(cls, funcnode): return ', '.join(term.get_description() for term in iter_funcnode_variables(funcnode)) - + register_function(COMMA_JOIN) # XXX do not expose? class CONCAT_STRINGS(COMMA_JOIN): aggregat = True - + register_function(CONCAT_STRINGS) # XXX bw compat class GROUP_CONCAT(CONCAT_STRINGS): supported_backends = ('mysql', 'postgres', 'sqlite',) - + register_function(GROUP_CONCAT) class LIMIT_SIZE(FunctionDescr): supported_backends = ('postgres', 'sqlite',) rtype = 'String' - + @classmethod def st_description(cls, funcnode): return funcnode.children[0].get_description() - + register_function(LIMIT_SIZE) class TEXT_LIMIT_SIZE(LIMIT_SIZE): supported_backends = ('mysql', 'postgres', 'sqlite',) - + register_function(TEXT_LIMIT_SIZE) diff -r 49075f57cf2c -r aa09e20dd8c0 common/appobject.py --- a/common/appobject.py Tue May 05 17:18:49 2009 +0200 +++ b/common/appobject.py Thu May 14 12:48:11 2009 +0200 @@ -1,463 +1,5 @@ -"""Base class for dynamically loaded objects manipulated in the web interface - -: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" - +"""pre 3.2 bw compat""" +# pylint: disable-msg=W0614,W0401 from warnings import warn - -from mx.DateTime import now, oneSecond -from simplejson import dumps - -from logilab.common.deprecation import obsolete - -from rql.nodes import VariableRef, SubQuery -from rql.stmts import Union, Select - -from cubicweb import Unauthorized -from cubicweb.vregistry import VObject -from cubicweb.common.utils import UStringIO -from cubicweb.common.uilib import html_escape, ustrftime -from cubicweb.common.registerers import yes_registerer, priority_registerer -from cubicweb.common.selectors import yes - -_MARKER = object() - - -class Cache(dict): - def __init__(self): - super(Cache, self).__init__() - self.cache_creation_date = None - self.latest_cache_lookup = 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 - :schema: - the application's schema - :config: - the application's configuration - - At instantiation time, the following attributes are set on the instance: - :req: - current request - :rset: - result set on which the object is applied - """ - - @classmethod - def registered(cls, vreg): - cls.vreg = vreg - cls.schema = vreg.schema - cls.config = vreg.config - cls.register_properties() - return cls - - @classmethod - def selected(cls, req, rset, row=None, col=None, **kwargs): - """by default web app objects are usually instantiated on - selection according to a request, a result set, and optional - row and col - """ - instance = cls(req, rset) - instance.row = row - instance.col = col - return instance - - # Eproperties definition: - # key: id of the property (the actual EProperty key is build using - # .. - # value: tuple (property type, vocabfunc, default value, property description) - # possible types are those used by `logilab.common.configuration` - # - # 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(): - pdef = pdef.copy() # may be shared - 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) - - - def __init__(self, req, rset): - super(AppRsetObject, self).__init__() - self.req = req - self.rset = rset - - @property - def cursor(self): # XXX deprecate in favor of req.cursor? - msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)' - warn(msg, DeprecationWarning, stacklevel=2) - return self.req.cursor - - def get_cache(self, cachename): - """ - NOTE: cachename should be dotted names as in : - - cubicweb.mycache - - cubes.blog.mycache - - etc. - """ - if cachename in CACHE_REGISTRY: - cache = CACHE_REGISTRY[cachename] - else: - cache = Cache() - CACHE_REGISTRY[cachename] = cache - _now = now() - if _now > cache.latest_cache_lookup + oneSecond: - ecache = self.req.execute('Any C,T WHERE C is ECache, 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): - cache.empty() - cache.cache_creation_date = _now - return cache - - 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 - currently displayed page when necessary - """ - # try to get page boundaries from the navigation component - # XXX we should probably not have a ref to this component here (eg in - # cubicweb.common) - nav = self.vreg.select_component('navigation', self.req, self.rset) - if nav: - start, stop = nav.page_boundaries() - rql = self._limit_offset_rql(stop - start, start) - # result set may have be limited manually in which case navigation won't - # apply - elif self.rset.limited: - rql = self._limit_offset_rql(*self.rset.limited) - # navigation component doesn't apply and rset has not been limited, no - # need to limit query - 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: - select = rqlst.children[0] - olimit, ooffset = select.limit, select.offset - select.limit, select.offset = limit, offset - rql = rqlst.as_string(kwargs=self.rset.args) - # restore original limit/offset - select.limit, select.offset = olimit, ooffset - else: - newselect = Select() - newselect.limit = limit - newselect.offset = offset - aliases = [VariableRef(newselect.get_variable(vref.name, i)) - for i, vref in enumerate(rqlst.selection)] - newselect.set_with([SubQuery(aliases, rqlst)], check=False) - newunion = Union() - newunion.append(newselect) - rql = rqlst.as_string(kwargs=self.rset.args) - rqlst.parent = None - return rql - - # 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 - publishing method to use may be specified or will be guessed. - """ - # XXX I (adim) think that if method is passed explicitly, we should - # not try to process it and directly call req.build_url() - if method is None: - method = self.controller - if method == 'view' and self.req.from_controller() == 'view' and \ - not '_restpath' in kwargs: - method = self.req.relative_path(includeparams=False) or 'view' - return self.req.build_url(method, **kwargs) - - # various resources accessors ############################################# - - def etype_rset(self, etype, size=1): - """return a fake result set for a particular entity type""" - msg = '.etype_rset is deprecated, use req.etype_rset' - warn(msg, DeprecationWarning, stacklevel=2) - return self.req.etype_rset(etype, size=1) - - def eid_rset(self, eid, etype=None): - """return a result set for the given eid""" - msg = '.eid_rset is deprecated, use req.eid_rset' - warn(msg, DeprecationWarning, stacklevel=2) - return self.req.eid_rset(eid, etype) - - 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) - """ - entity = self.entity(row, col) - entity.complete(skip_bytes=skip_bytes) - return entity - - def user_rql_callback(self, args, msg=None): - """register a user callback to execute some rql query and return an url - to call it ready to be inserted in html - """ - 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 - """ - self.req.add_js('cubicweb.ajax.js') - if nonify: - # XXX < 2.48.3 bw compat - warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2) - _cb = cb - def cb(*args): - _cb(*args) - cbname = self.req.register_onetime_callback(cb, *args) - msg = dumps(msg or '') - return "javascript:userCallbackThenReloadPage('%s', %s)" % ( - cbname, msg) - - # formating methods ####################################################### - - def tal_render(self, template, variables): - """render a precompiled page template with variables in the given - dictionary as context - """ - from cubicweb.common.tal import CubicWebContext - context = CubicWebContext() - context.update({'self': self, 'rset': self.rset, '_' : self.req._, - 'req': self.req, 'user': self.req.user}) - context.update(variables) - output = UStringIO() - template.expand(context, output) - return output.getvalue() - - def format_date(self, date, date_format=None, time=False): - """return a string for a mx date time according to application's - configuration - """ - if date: - if date_format is None: - if time: - date_format = self.req.property_value('ui.datetime-format') - else: - date_format = self.req.property_value('ui.date-format') - return ustrftime(date, date_format) - return u'' - - def format_time(self, time): - """return a string for a mx date time according to application's - configuration - """ - if time: - return ustrftime(time, self.req.property_value('ui.time-format')) - return u'' - - def format_float(self, num): - """return a string for floating point number according to application's - configuration - """ - 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')) - - # .accepts handling utilities ############################################# - - accepts = ('Any',) - - @classmethod - def accept_rset(cls, req, rset, row, col): - """apply the following rules: - * if row is None, return the sum of values returned by the method - for each entity's type in the result set. If any score is 0, - return 0. - * if row is specified, return the value returned by the method with - the entity's type of this row - """ - if row is None: - score = 0 - for etype in rset.column_types(0): - accepted = cls.accept(req.user, etype) - if not accepted: - return 0 - score += accepted - return score - return cls.accept(req.user, rset.description[row][col or 0]) - - @classmethod - def accept(cls, user, etype): - """score etype, returning better score on exact match""" - if 'Any' in cls.accepts: - return 1 - eschema = cls.schema.eschema(etype) - matching_types = [e.type for e in eschema.ancestors()] - matching_types.append(etype) - for index, basetype in enumerate(matching_types): - if basetype in cls.accepts: - return 2 + index - return 0 - - # .rtype handling utilities ############################################## - - @classmethod - def relation_possible(cls, etype): - """tell if a relation with etype entity is possible according to - mixed class'.etype, .rtype and .target attributes - - XXX should probably be moved out to a function - """ - schema = cls.schema - rtype = cls.rtype - eschema = schema.eschema(etype) - if hasattr(cls, 'role'): - role = cls.role - elif cls.target == 'subject': - role = 'object' - else: - role = 'subject' - # check if this relation is possible according to the schema - try: - if role == 'object': - rschema = eschema.object_relation(rtype) - else: - rschema = eschema.subject_relation(rtype) - except KeyError: - return False - if hasattr(cls, 'etype'): - letype = cls.etype - try: - if role == 'object': - return etype in rschema.objects(letype) - else: - return etype in rschema.subjects(letype) - except KeyError, ex: - return False - return True - - - # XXX deprecated (since 2.43) ########################## - - @obsolete('use req.datadir_url') - def datadir_url(self): - """return url of the application's data directory""" - return self.req.datadir_url - - @obsolete('use req.external_resource()') - def external_resource(self, rid, default=_MARKER): - return self.req.external_resource(rid, default) - - -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 - selection - """ - return cls(*args, **kwargs) - - def __init__(self, req=None, rset=None, **kwargs): - self.req = req - self.rset = rset - self.__dict__.update(kwargs) - - -class ReloadableMixIn(object): - """simple mixin for reloadable parts of UI""" - - 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 - """ - self.req.add_js('cubicweb.ajax.js') - if nonify: - _cb = cb - def cb(*args): - _cb(*args) - cbname = self.req.register_onetime_callback(cb, *args) - return self.build_js(cbname, html_escape(msg or '')) - - def build_update_js_call(self, cbname, msg): - rql = html_escape(self.rset.printable_rql()) - return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % ( - cbname, self.id, rql, msg, self.__registry__, self.div_id()) - - def build_reload_js_call(self, cbname, msg): - return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg) - - build_js = build_update_js_call # expect updatable component by default - - def div_id(self): - return '' - - -class ComponentMixIn(ReloadableMixIn): - """simple mixin for component object""" - __registry__ = 'components' - __registerer__ = yes_registerer - __selectors__ = (yes,) - __select__ = classmethod(*__selectors__) - - def div_class(self): - return '%s %s' % (self.propval('htmlclass'), self.id) - - def div_id(self): - return '%sComponent' % self.id - - -class Component(ComponentMixIn, AppObject): - """base class for non displayable components - """ - -class SingletonComponent(Component): - """base class for non displayable unique components - """ - __registerer__ = priority_registerer +warn('moved to cubicweb.appobject', DeprecationWarning, stacklevel=2) +from cubicweb.appobject import * diff -r 49075f57cf2c -r aa09e20dd8c0 common/entity.py --- a/common/entity.py Tue May 05 17:18:49 2009 +0200 +++ b/common/entity.py Thu May 14 12:48:11 2009 +0200 @@ -1,1106 +1,6 @@ -"""Base class for entity objects manipulated in clients - -: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.common import interface -from logilab.common.compat import all -from logilab.common.decorators import cached -from logilab.mtconverter import TransformData, TransformError -from rql.utils import rqlvar_maker - -from cubicweb import Unauthorized -from cubicweb.vregistry import autoselectors -from cubicweb.rset import ResultSet -from cubicweb.common.appobject import AppRsetObject -from cubicweb.common.registerers import id_registerer -from cubicweb.common.selectors import yes -from cubicweb.common.uilib import printable_value, html_escape, soup2xhtml -from cubicweb.common.mixins import MI_REL_TRIGGERS -from cubicweb.common.mttransforms import ENGINE -from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype - -_marker = object() - -def greater_card(rschema, subjtypes, objtypes, index): - for subjtype in subjtypes: - for objtype in objtypes: - card = rschema.rproperty(subjtype, objtype, 'cardinality')[index] - if card in '+*': - return card - return '1' - - -class RelationTags(object): - - MODE_TAGS = frozenset(('link', 'create')) - CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated', - 'inlineview')) - - def __init__(self, eclass, tagdefs): - # XXX if a rtag is redefined in a subclass, - # the rtag of the base class overwrite the rtag of the subclass - self.eclass = eclass - self._tagdefs = {} - for relation, tags in tagdefs.iteritems(): - # tags must become a set - if isinstance(tags, basestring): - tags = set((tags,)) - elif not isinstance(tags, set): - tags = set(tags) - # relation must become a 3-uple (rtype, targettype, role) - if isinstance(relation, basestring): - self._tagdefs[(relation, '*', 'subject')] = tags - self._tagdefs[(relation, '*', 'object')] = tags - elif len(relation) == 1: # useful ? - self._tagdefs[(relation[0], '*', 'subject')] = tags - self._tagdefs[(relation[0], '*', 'object')] = tags - elif len(relation) == 2: - rtype, ttype = relation - ttype = bw_normalize_etype(ttype) # XXX bw compat - self._tagdefs[rtype, ttype, 'subject'] = tags - self._tagdefs[rtype, ttype, 'object'] = tags - elif len(relation) == 3: - relation = list(relation) # XXX bw compat - relation[1] = bw_normalize_etype(relation[1]) - self._tagdefs[tuple(relation)] = tags - else: - raise ValueError('bad rtag definition (%r)' % (relation,)) - - - def __initialize__(self): - # eclass.[*]schema are only set when registering - self.schema = self.eclass.schema - eschema = self.eschema = self.eclass.e_schema - rtags = self._tagdefs - # expand wildcards in rtags and add automatic tags - for rschema, tschemas, role in sorted(eschema.relation_definitions(True)): - rtype = rschema.type - star_tags = rtags.pop((rtype, '*', role), set()) - for tschema in tschemas: - tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags)) - 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' - # set default category tags if needed - if not tags & self.CATEGORY_TAGS: - if card in '1+': - if not rschema.is_final() and composed: - category = 'generated' - elif rschema.is_final() and ( - rschema.type.endswith('_format') - or rschema.type.endswith('_encoding')): - category = 'generated' - else: - category = 'primary' - elif rschema.is_final(): - if (rschema.type.endswith('_format') - or rschema.type.endswith('_encoding')): - category = 'generated' - else: - category = 'secondary' - else: - category = 'generic' - tags.add(category) - if not tags & self.MODE_TAGS: - if card in '?1': - # by default, suppose link mode if cardinality doesn't allow - # more than one relation - mode = 'link' - elif rschema.rproperty(X, Y, 'composite') == role: - # if self is composed of the target type, create mode - mode = 'create' - else: - # link mode by default - mode = 'link' - tags.add(mode) - - def _default_target(self, rschema, role='subject'): - eschema = self.eschema - if role == 'subject': - return eschema.subject_relation(rschema).objects(eschema)[0] - else: - return eschema.object_relation(rschema).subjects(eschema)[0] - - # dict compat - def __getitem__(self, key): - if isinstance(key, basestring): - key = (key,) - return self.get_tags(*key) - - __contains__ = __getitem__ - - def get_tags(self, rtype, targettype=None, role='subject'): - rschema = self.schema.rschema(rtype) - if targettype is None: - tschema = self._default_target(rschema, role) - else: - tschema = self.schema.eschema(targettype) - return self._tagdefs[(rtype, tschema.type, role)] - - __call__ = get_tags - - def get_mode(self, rtype, targettype=None, role='subject'): - # XXX: should we make an assertion on rtype not being final ? - # assert not rschema.is_final() - tags = self.get_tags(rtype, targettype, role) - # do not change the intersection order ! - modes = tags & self.MODE_TAGS - assert len(modes) == 1 - return modes.pop() - - def get_category(self, rtype, targettype=None, role='subject'): - tags = self.get_tags(rtype, targettype, role) - categories = tags & self.CATEGORY_TAGS - assert len(categories) == 1 - return categories.pop() - - def is_inlined(self, rtype, targettype=None, role='subject'): - # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype) - return 'inlineview' in self.get_tags(rtype, targettype, role) - - -class metaentity(autoselectors): - """this metaclass sets the relation tags on the entity class - and deals with the `widgets` attribute - """ - def __new__(mcs, name, bases, classdict): - # collect baseclass' rtags - tagdefs = {} - widgets = {} - for base in bases: - tagdefs.update(getattr(base, '__rtags__', {})) - widgets.update(getattr(base, 'widgets', {})) - # update with the class' own rtgas - tagdefs.update(classdict.get('__rtags__', {})) - widgets.update(classdict.get('widgets', {})) - # XXX decide whether or not it's a good idea to replace __rtags__ - # good point: transparent support for inheritance levels >= 2 - # bad point: we loose the information of which tags are specific - # to this entity class - classdict['__rtags__'] = tagdefs - classdict['widgets'] = widgets - eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict) - # adds the "rtags" attribute - eclass.rtags = RelationTags(eclass, tagdefs) - return eclass - - -class Entity(AppRsetObject, dict): - """an entity instance has e_schema automagically set on - the class and instances has access to their issuing cursor. - - A property is set for each attribute and relation on each entity's type - class. Becare that among attributes, 'eid' is *NEITHER* stored in the - dict containment (which acts as a cache for other attributes dynamically - fetched) - - :type e_schema: `cubicweb.schema.EntitySchema` - :ivar e_schema: the entity's schema - - :type rest_var: str - :cvar rest_var: indicates which attribute should be used to build REST urls - If None is specified, the first non-meta attribute will - be used - - :type skip_copy_for: list - :cvar skip_copy_for: a list of relations that should be skipped when copying - this kind of entity. Note that some relations such - as composite relations or relations that have '?1' as object - cardinality - """ - __metaclass__ = metaentity - __registry__ = 'etypes' - __registerer__ = id_registerer - __selectors__ = (yes,) - widgets = {} - id = None - e_schema = None - eid = None - rest_attr = None - skip_copy_for = () - - @classmethod - def registered(cls, registry): - """build class using descriptor at registration time""" - assert cls.id is not None - super(Entity, cls).registered(registry) - if cls.id != 'Any': - cls.__initialize__() - return cls - - MODE_TAGS = set(('link', 'create')) - CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata')) - @classmethod - def __initialize__(cls): - """initialize a specific entity class by adding descriptors to access - entity type's attributes and relations - """ - etype = cls.id - assert etype != 'Any', etype - cls.e_schema = eschema = cls.schema.eschema(etype) - for rschema, _ in eschema.attribute_definitions(): - if rschema.type == 'eid': - continue - setattr(cls, rschema.type, Attribute(rschema.type)) - mixins = [] - for rschema, _, x in eschema.relation_definitions(): - if (rschema, x) in MI_REL_TRIGGERS: - mixin = MI_REL_TRIGGERS[(rschema, x)] - if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ? - mixins.append(mixin) - for iface in getattr(mixin, '__implements__', ()): - if not interface.implements(cls, iface): - interface.extend(cls, iface) - if x == 'subject': - setattr(cls, rschema.type, SubjectRelation(rschema)) - else: - attr = 'reverse_%s' % rschema.type - setattr(cls, attr, ObjectRelation(rschema)) - if mixins: - cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object]) - cls.debug('plugged %s mixins on %s', mixins, etype) - cls.rtags.__initialize__() - - @classmethod - def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', - settype=True, ordermethod='fetch_order'): - """return a rql to fetch all entities of the class type""" - restrictions = restriction or [] - if settype: - restrictions.append('%s is %s' % (mainvar, cls.id)) - if fetchattrs is None: - fetchattrs = cls.fetch_attrs - selection = [mainvar] - orderby = [] - # start from 26 to avoid possible conflicts with X - varmaker = rqlvar_maker(index=26) - cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection, - orderby, restrictions, user, ordermethod) - rql = 'Any %s' % ','.join(selection) - if orderby: - rql += ' ORDERBY %s' % ','.join(orderby) - rql += ' WHERE %s' % ', '.join(restrictions) - return rql - - @classmethod - def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs, - selection, orderby, restrictions, user, - ordermethod='fetch_order', visited=None): - eschema = cls.e_schema - if visited is None: - visited = set((eschema.type,)) - elif eschema.type in visited: - # avoid infinite recursion - return - else: - visited.add(eschema.type) - _fetchattrs = [] - for attr in fetchattrs: - try: - rschema = eschema.subject_relation(attr) - except KeyError: - cls.warning('skipping fetch_attr %s defined in %s (not found in schema)', - attr, cls.id) - continue - if not user.matching_groups(rschema.get_groups('read')): - continue - var = varmaker.next() - selection.append(var) - restriction = '%s %s %s' % (mainvar, attr, var) - restrictions.append(restriction) - if not rschema.is_final(): - # XXX this does not handle several destination types - desttype = rschema.objects(eschema.type)[0] - card = rschema.rproperty(eschema, desttype, 'cardinality')[0] - if card not in '?1': - selection.pop() - restrictions.pop() - continue - if card == '?': - restrictions[-1] += '?' # left outer join if not mandatory - destcls = cls.vreg.etype_class(desttype) - destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs, - selection, orderby, restrictions, - user, ordermethod, visited=visited) - orderterm = getattr(cls, ordermethod)(attr, var) - if orderterm: - orderby.append(orderterm) - return selection, orderby, restrictions - - def __init__(self, req, rset, row=None, col=0): - AppRsetObject.__init__(self, req, rset) - dict.__init__(self) - self.row, self.col = row, col - self._related_cache = {} - if rset is not None: - self.eid = rset[row][col] - else: - self.eid = None - self._is_saved = True - - def __repr__(self): - return '' % ( - self.e_schema, self.eid, self.keys(), id(self)) - - def __nonzero__(self): - return True - - def __hash__(self): - return id(self) - - def pre_add_hook(self): - """hook called by the repository before doing anything to add the entity - (before_add entity hooks have not been called yet). This give the - occasion to do weird stuff such as autocast (File -> Image for instance). - - This method must return the actual entity to be added. - """ - return self - - def set_eid(self, eid): - self.eid = self['eid'] = eid - - def has_eid(self): - """return True if the entity has an attributed eid (False - meaning that the entity has to be created - """ - try: - int(self.eid) - return True - except (ValueError, TypeError): - return False - - def is_saved(self): - """during entity creation, there is some time during which the entity - has an eid attributed though it's not saved (eg during before_add_entity - hooks). You can use this method to ensure the entity has an eid *and* is - saved in its source. - """ - return self.has_eid() and self._is_saved - - @cached - def metainformation(self): - res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid))) - res['source'] = self.req.source_defs()[res['source']] - return res - - def clear_local_perm_cache(self, action): - for rqlexpr in self.e_schema.get_rqlexprs(action): - self.req.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None) - - def check_perm(self, action): - self.e_schema.check_perm(self.req, action, self.eid) - - def has_perm(self, action): - return self.e_schema.has_perm(self.req, action, self.eid) - - def view(self, vid, __registry='views', **kwargs): - """shortcut to apply a view on this entity""" - return self.vreg.render(__registry, vid, self.req, rset=self.rset, - row=self.row, col=self.col, **kwargs) - - def absolute_url(self, method=None, **kwargs): - """return an absolute url to view this entity""" - # in linksearch mode, we don't want external urls else selecting - # the object for use in the relation is tricky - # XXX search_state is web specific - if getattr(self.req, 'search_state', ('normal',))[0] == 'normal': - kwargs['base_url'] = self.metainformation()['source'].get('base-url') - if method is None or method == 'view': - kwargs['_restpath'] = self.rest_path() - else: - kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid - return self.build_url(method, **kwargs) - - def rest_path(self): - """returns a REST-like (relative) path for this entity""" - mainattr, needcheck = self._rest_attr_info() - etype = str(self.e_schema) - if mainattr == 'eid': - value = self.eid - else: - value = getattr(self, mainattr) - if value is None: - return '%s/eid/%s' % (etype.lower(), self.eid) - if needcheck: - # make sure url is not ambiguous - rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr) - if value is not None: - nbresults = self.req.execute(rql, {'value' : value})[0][0] - # may an assertion that nbresults is not 0 would be a good idea - if nbresults != 1: # no ambiguity - return '%s/eid/%s' % (etype.lower(), self.eid) - return '%s/%s' % (etype.lower(), self.req.url_quote(value)) - - @classmethod - def _rest_attr_info(cls): - mainattr, needcheck = 'eid', True - if cls.rest_attr: - mainattr = cls.rest_attr - needcheck = not cls.e_schema.has_unique_values(mainattr) - else: - for rschema in cls.e_schema.subject_relations(): - if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema): - mainattr = str(rschema) - needcheck = False - break - if mainattr == 'eid': - needcheck = False - return mainattr, needcheck - - @cached - def formatted_attrs(self): - """returns the list of attributes which have some format information - (i.e. rich text strings) - """ - attrs = [] - for rschema, attrschema in self.e_schema.attribute_definitions(): - if attrschema.type == 'String' and self.has_format(rschema): - attrs.append(rschema.type) - return attrs - - def format(self, attr): - """return the mime type format for an attribute (if specified)""" - return getattr(self, '%s_format' % attr, None) - - def text_encoding(self, attr): - """return the text encoding for an attribute, default to site encoding - """ - encoding = getattr(self, '%s_encoding' % attr, None) - return encoding or self.vreg.property_value('ui.encoding') - - def has_format(self, attr): - """return true if this entity's schema has a format field for the given - attribute - """ - return self.e_schema.has_subject_relation('%s_format' % attr) - - def has_text_encoding(self, attr): - """return true if this entity's schema has ab encoding field for the - given attribute - """ - return self.e_schema.has_subject_relation('%s_encoding' % attr) - - def printable_value(self, attr, value=_marker, attrtype=None, - format='text/html', displaytime=True): - """return a displayable value (i.e. unicode string) which may contains - html tags - """ - attr = str(attr) - if value is _marker: - value = getattr(self, attr) - if isinstance(value, basestring): - value = value.strip() - if value is None or value == '': # don't use "not", 0 is an acceptable value - return u'' - if attrtype is None: - attrtype = self.e_schema.destination(attr) - props = self.e_schema.rproperties(attr) - if attrtype == 'String': - # internalinalized *and* formatted string such as schema - # description... - if props.get('internationalizable'): - value = self.req._(value) - attrformat = self.format(attr) - if attrformat: - return self.mtc_transform(value, attrformat, format, - self.req.encoding) - elif attrtype == 'Bytes': - attrformat = self.format(attr) - if attrformat: - try: - encoding = getattr(self, '%s_encoding' % attr) - except AttributeError: - encoding = self.req.encoding - return self.mtc_transform(value.getvalue(), attrformat, format, - encoding) - return u'' - value = printable_value(self.req, attrtype, value, props, displaytime) - if format == 'text/html': - value = html_escape(value) - return value - - def mtc_transform(self, data, format, target_format, encoding, - _engine=ENGINE): - trdata = TransformData(data, format, encoding, appobject=self) - data = _engine.convert(trdata, target_format).decode() - if format == 'text/html': - data = soup2xhtml(data, self.req.encoding) - return data - - # entity cloning ########################################################## - - def copy_relations(self, ceid): - """copy relations of the object with the given eid on this object - - By default meta and composite relations are skipped. - Overrides this if you want another behaviour - """ - assert self.has_eid() - execute = self.req.execute - for rschema in self.e_schema.subject_relations(): - if rschema.meta or rschema.is_final(): - continue - # skip already defined relations - if getattr(self, rschema.type): - continue - if rschema.type in self.skip_copy_for: - continue - if rschema.type == 'in_state': - # if the workflow is defining an initial state (XXX AND we are - # not in the managers group? not done to be more consistent) - # don't try to copy in_state - if execute('Any S WHERE S state_of ET, ET initial_state S,' - 'ET name %(etype)s', {'etype': str(self.e_schema)}): - continue - # skip composite relation - if self.e_schema.subjrproperty(rschema, 'composite'): - continue - # skip relation with card in ?1 else we either change the copied - # object (inlined relation) or inserting some inconsistency - if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1': - continue - rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % ( - rschema.type, rschema.type) - execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y')) - self.clear_related_cache(rschema.type, 'subject') - for rschema in self.e_schema.object_relations(): - if rschema.meta: - continue - # skip already defined relations - if getattr(self, 'reverse_%s' % rschema.type): - continue - # skip composite relation - if self.e_schema.objrproperty(rschema, 'composite'): - continue - # skip relation with card in ?1 else we either change the copied - # object (inlined relation) or inserting some inconsistency - if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1': - continue - rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % ( - rschema.type, rschema.type) - execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y')) - self.clear_related_cache(rschema.type, 'object') - - # data fetching methods ################################################### - - @cached - def as_rset(self): - """returns a resultset containing `self` information""" - rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s', - {'x': self.eid}, [(self.id,)]) - return self.req.decorate_rset(rset) - - def to_complete_relations(self): - """by default complete final relations to when calling .complete()""" - for rschema in self.e_schema.subject_relations(): - if rschema.is_final(): - continue - if len(rschema.objects(self.e_schema)) > 1: - # ambigous relations, the querier doesn't handle - # outer join correctly in this case - continue - if rschema.inlined: - matching_groups = self.req.user.matching_groups - if matching_groups(rschema.get_groups('read')) and \ - all(matching_groups(es.get_groups('read')) - for es in rschema.objects(self.e_schema)): - yield rschema, 'subject' - - def to_complete_attributes(self, skip_bytes=True): - for rschema, attrschema in self.e_schema.attribute_definitions(): - # skip binary data by default - if skip_bytes and attrschema.type == 'Bytes': - continue - attr = rschema.type - if attr == 'eid': - continue - # password retreival is blocked at the repository server level - if not self.req.user.matching_groups(rschema.get_groups('read')) \ - or attrschema.type == 'Password': - self[attr] = None - continue - yield attr - - def complete(self, attributes=None, skip_bytes=True): - """complete this entity by adding missing attributes (i.e. query the - repository to fill the entity) - - :type skip_bytes: bool - :param skip_bytes: - if true, attribute of type Bytes won't be considered - """ - assert self.has_eid() - varmaker = rqlvar_maker() - V = varmaker.next() - rql = ['WHERE %s eid %%(x)s' % V] - selected = [] - for attr in (attributes or self.to_complete_attributes(skip_bytes)): - # if attribute already in entity, nothing to do - if self.has_key(attr): - continue - # case where attribute must be completed, but is not yet in entity - var = varmaker.next() - rql.append('%s %s %s' % (V, attr, var)) - selected.append((attr, var)) - # +1 since this doen't include the main variable - lastattr = len(selected) + 1 - if attributes is None: - # fetch additional relations (restricted to 0..1 relations) - for rschema, role in self.to_complete_relations(): - rtype = rschema.type - if self.relation_cached(rtype, role): - continue - var = varmaker.next() - if role == 'subject': - targettype = rschema.objects(self.e_schema)[0] - card = rschema.rproperty(self.e_schema, targettype, - 'cardinality')[0] - if card == '1': - rql.append('%s %s %s' % (V, rtype, var)) - else: # '?" - rql.append('%s %s %s?' % (V, rtype, var)) - else: - targettype = rschema.subjects(self.e_schema)[1] - card = rschema.rproperty(self.e_schema, targettype, - 'cardinality')[1] - if card == '1': - rql.append('%s %s %s' % (var, rtype, V)) - else: # '?" - rql.append('%s? %s %s' % (var, rtype, V)) - assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype, - role, card) - selected.append(((rtype, role), var)) - if selected: - # select V, we need it as the left most selected variable - # if some outer join are included to fetch inlined relations - rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected), - ','.join(rql)) - execute = getattr(self.req, 'unsafe_execute', self.req.execute) - rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0] - # handle attributes - for i in xrange(1, lastattr): - self[str(selected[i-1][0])] = rset[i] - # handle relations - for i in xrange(lastattr, len(rset)): - rtype, x = selected[i-1][0] - value = rset[i] - if value is None: - rrset = ResultSet([], rql, {'x': self.eid}) - self.req.decorate_rset(rrset) - else: - rrset = self.req.eid_rset(value) - self.set_related_cache(rtype, x, rrset) - - def get_value(self, name): - """get value for the attribute relation , query the repository - to get the value if necessary. - - :type name: str - :param name: name of the attribute to get - """ - try: - value = self[name] - except KeyError: - if not self.is_saved(): - return None - rql = "Any A WHERE X eid %%(x)s, X %s A" % name - # XXX should we really use unsafe_execute here?? - execute = getattr(self.req, 'unsafe_execute', self.req.execute) - try: - rset = execute(rql, {'x': self.eid}, 'x') - except Unauthorized: - self[name] = value = None - else: - assert rset.rowcount <= 1, (self, rql, rset.rowcount) - try: - self[name] = value = rset.rows[0][0] - except IndexError: - # probably a multisource error - self.critical("can't get value for attribute %s of entity with eid %s", - name, self.eid) - if self.e_schema.destination(name) == 'String': - self[name] = value = self.req._('unaccessible') - else: - self[name] = value = None - return value - - def related(self, rtype, role='subject', limit=None, entities=False): - """returns a resultset of related entities - - :param role: is the role played by 'self' in the relation ('subject' or 'object') - :param limit: resultset's maximum size - :param entities: if True, the entites are returned; if False, a result set is returned - """ - try: - return self.related_cache(rtype, role, entities, limit) - except KeyError: - pass - assert self.has_eid() - rql = self.related_rql(rtype, role) - rset = self.req.execute(rql, {'x': self.eid}, 'x') - self.set_related_cache(rtype, role, rset) - return self.related(rtype, role, limit, entities) - - def related_rql(self, rtype, role='subject'): - rschema = self.schema[rtype] - if role == 'subject': - targettypes = rschema.objects(self.e_schema) - restriction = 'E eid %%(x)s, E %s X' % rtype - card = greater_card(rschema, (self.e_schema,), targettypes, 0) - else: - targettypes = rschema.subjects(self.e_schema) - restriction = 'E eid %%(x)s, X %s E' % rtype - card = greater_card(rschema, targettypes, (self.e_schema,), 1) - if len(targettypes) > 1: - fetchattrs_list = [] - for ttype in targettypes: - etypecls = self.vreg.etype_class(ttype) - fetchattrs_list.append(set(etypecls.fetch_attrs)) - fetchattrs = reduce(set.intersection, fetchattrs_list) - rql = etypecls.fetch_rql(self.req.user, [restriction], fetchattrs, - settype=False) - else: - etypecls = self.vreg.etype_class(targettypes[0]) - rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False) - # optimisation: remove ORDERBY if cardinality is 1 or ? (though - # greater_card return 1 for those both cases) - if card == '1': - if ' ORDERBY ' in rql: - rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0], - rql.split(' WHERE ', 1)[1]) - elif not ' ORDERBY ' in rql: - args = tuple(rql.split(' WHERE ', 1)) - rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args - return rql - - # generic vocabulary methods ############################################## - - def vocabulary(self, rtype, role='subject', limit=None): - """vocabulary functions must return a list of couples - (label, eid) that will typically be used to fill the - edition view's combobox. - - If `eid` is None in one of these couples, it should be - interpreted as a separator in case vocabulary results are grouped - """ - try: - vocabfunc = getattr(self, '%s_%s_vocabulary' % (role, rtype)) - except AttributeError: - vocabfunc = getattr(self, '%s_relation_vocabulary' % role) - # NOTE: it is the responsibility of `vocabfunc` to sort the result - # (direclty through RQL or via a python sort). This is also - # important because `vocabfunc` might return a list with - # couples (label, None) which act as separators. In these - # cases, it doesn't make sense to sort results afterwards. - return vocabfunc(rtype, limit) - - def subject_relation_vocabulary(self, rtype, limit=None): - """defaut vocabulary method for the given relation, looking for - relation's object entities (i.e. self is the subject) - """ - if isinstance(rtype, basestring): - rtype = self.schema.rschema(rtype) - done = None - assert not rtype.is_final(), rtype - if self.has_eid(): - done = set(e.eid for e in getattr(self, str(rtype))) - result = [] - rsetsize = None - for objtype in rtype.objects(self.e_schema): - if limit is not None: - rsetsize = limit - len(result) - result += self.relation_vocabulary(rtype, objtype, 'subject', - rsetsize, done) - if limit is not None and len(result) >= limit: - break - return result - - def object_relation_vocabulary(self, rtype, limit=None): - """defaut vocabulary method for the given relation, looking for - relation's subject entities (i.e. self is the object) - """ - if isinstance(rtype, basestring): - rtype = self.schema.rschema(rtype) - done = None - if self.has_eid(): - done = set(e.eid for e in getattr(self, 'reverse_%s' % rtype)) - result = [] - rsetsize = None - for subjtype in rtype.subjects(self.e_schema): - if limit is not None: - rsetsize = limit - len(result) - result += self.relation_vocabulary(rtype, subjtype, 'object', - rsetsize, done) - if limit is not None and len(result) >= limit: - break - return result - - def relation_vocabulary(self, rtype, targettype, role, - limit=None, done=None): - if done is None: - done = set() - req = self.req - rset = self.unrelated(rtype, targettype, role, limit) - res = [] - for entity in rset.entities(): - if entity.eid in done: - continue - done.add(entity.eid) - res.append((entity.view('combobox'), entity.eid)) - return res - - def unrelated_rql(self, rtype, targettype, role, ordermethod=None, - vocabconstraints=True): - """build a rql to fetch `targettype` entities unrelated to this entity - using (rtype, role) relation - """ - ordermethod = ordermethod or 'fetch_unrelated_order' - if isinstance(rtype, basestring): - rtype = self.schema.rschema(rtype) - if role == 'subject': - evar, searchedvar = 'S', 'O' - subjtype, objtype = self.e_schema, targettype - else: - searchedvar, evar = 'S', 'O' - objtype, subjtype = self.e_schema, targettype - if self.has_eid(): - restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar] - else: - restriction = [] - constraints = rtype.rproperty(subjtype, objtype, 'constraints') - if vocabconstraints: - # RQLConstraint is a subclass for RQLVocabularyConstraint, so they - # will be included as well - restriction += [cstr.restriction for cstr in constraints - if isinstance(cstr, RQLVocabularyConstraint)] - else: - restriction += [cstr.restriction for cstr in constraints - if isinstance(cstr, RQLConstraint)] - etypecls = self.vreg.etype_class(targettype) - rql = etypecls.fetch_rql(self.req.user, restriction, - mainvar=searchedvar, ordermethod=ordermethod) - # ensure we have an order defined - if not ' ORDERBY ' in rql: - before, after = rql.split(' WHERE ', 1) - rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after) - return rql - - def unrelated(self, rtype, targettype, role='subject', limit=None, - ordermethod=None): - """return a result set of target type objects that may be related - by a given relation, with self as subject or object - """ - rql = self.unrelated_rql(rtype, targettype, role, ordermethod) - if limit is not None: - before, after = rql.split(' WHERE ', 1) - rql = '%s LIMIT %s WHERE %s' % (before, limit, after) - if self.has_eid(): - return self.req.execute(rql, {'x': self.eid}) - return self.req.execute(rql) - - # relations cache handling ################################################ - - def relation_cached(self, rtype, role): - """return true if the given relation is already cached on the instance - """ - return '%s_%s' % (rtype, role) in self._related_cache - - def related_cache(self, rtype, role, entities=True, limit=None): - """return values for the given relation if it's cached on the instance, - else raise `KeyError` - """ - res = self._related_cache['%s_%s' % (rtype, role)][entities] - if limit: - if entities: - res = res[:limit] - else: - res = res.limit(limit) - return res - - def set_related_cache(self, rtype, role, rset, col=0): - """set cached values for the given relation""" - if rset: - related = list(rset.entities(col)) - rschema = self.schema.rschema(rtype) - if role == 'subject': - rcard = rschema.rproperty(self.e_schema, related[0].e_schema, - 'cardinality')[1] - target = 'object' - else: - rcard = rschema.rproperty(related[0].e_schema, self.e_schema, - 'cardinality')[0] - target = 'subject' - if rcard in '?1': - for rentity in related: - rentity._related_cache['%s_%s' % (rtype, target)] = (self.as_rset(), [self]) - else: - related = [] - self._related_cache['%s_%s' % (rtype, role)] = (rset, related) - - def clear_related_cache(self, rtype=None, role=None): - """clear cached values for the given relation or the entire cache if - no relation is given - """ - if rtype is None: - self._related_cache = {} - else: - assert role - self._related_cache.pop('%s_%s' % (rtype, role), None) - - # raw edition utilities ################################################### - - def set_attributes(self, **kwargs): - assert kwargs - relations = [] - for key in kwargs: - relations.append('X %s %%(%s)s' % (key, key)) - # update current local object - self.update(kwargs) - # and now update the database - kwargs['x'] = self.eid - self.req.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations), - kwargs, 'x') - - def delete(self): - assert self.has_eid(), self.eid - self.req.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema, - {'x': self.eid}) - - # server side utilities ################################################### - - def set_defaults(self): - """set default values according to the schema""" - self._default_set = set() - for attr, value in self.e_schema.defaults(): - if not self.has_key(attr): - self[str(attr)] = value - self._default_set.add(attr) - - def check(self, creation=False): - """check this entity against its schema. Only final relation - are checked here, constraint on actual relations are checked in hooks - """ - # necessary since eid is handled specifically and yams require it to be - # in the dictionary - if self.req is None: - _ = unicode - else: - _ = self.req._ - self.e_schema.check(self, creation=creation, _=_) - - def fti_containers(self, _done=None): - if _done is None: - _done = set() - _done.add(self.eid) - containers = tuple(self.e_schema.fulltext_containers()) - if containers: - yielded = False - for rschema, target in containers: - if target == 'object': - targets = getattr(self, rschema.type) - else: - targets = getattr(self, 'reverse_%s' % rschema) - for entity in targets: - if entity.eid in _done: - continue - for container in entity.fti_containers(_done): - yield container - yielded = True - if not yielded: - yield self - else: - yield self - - def get_words(self): - """used by the full text indexer to get words to index - - this method should only be used on the repository side since it depends - on the indexer package - - :rtype: list - :return: the list of indexable word of this entity - """ - from indexer.query_objects import tokenize - words = [] - for rschema in self.e_schema.indexable_attributes(): - try: - value = self.printable_value(rschema, format='text/plain') - except TransformError, ex: - continue - except: - self.exception("can't add value of %s to text index for entity %s", - rschema, self.eid) - continue - if value: - words += tokenize(value) - - for rschema, role in self.e_schema.fulltext_relations(): - if role == 'subject': - for entity in getattr(self, rschema.type): - words += entity.get_words() - else: # if role == 'object': - for entity in getattr(self, 'reverse_%s' % rschema.type): - words += entity.get_words() - return words - - -# attribute and relation descriptors ########################################## - -class Attribute(object): - """descriptor that controls schema attribute access""" - - def __init__(self, attrname): - assert attrname != 'eid' - self._attrname = attrname - - def __get__(self, eobj, eclass): - if eobj is None: - return self - return eobj.get_value(self._attrname) - - def __set__(self, eobj, value): - # XXX bw compat - # would be better to generate UPDATE queries than the current behaviour - eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)") - eobj[self._attrname] = value - - -class Relation(object): - """descriptor that controls schema relation access""" - _role = None # for pylint - - def __init__(self, rschema): - self._rschema = rschema - self._rtype = rschema.type - - def __get__(self, eobj, eclass): - if eobj is None: - raise AttributeError('%s cannot be only be accessed from instances' - % self._rtype) - return eobj.related(self._rtype, self._role, entities=True) - - def __set__(self, eobj, value): - raise NotImplementedError - - -class SubjectRelation(Relation): - """descriptor that controls schema relation access""" - _role = 'subject' - -class ObjectRelation(Relation): - """descriptor that controls schema relation access""" - _role = 'object' - -from logging import getLogger -from cubicweb import set_log_methods -set_log_methods(Entity, getLogger('cubicweb.entity')) +"""pre 3.2 bw compat""" +# pylint: disable-msg=W0614,W0401 +from warnings import warn +warn('moved to cubicweb.entity', DeprecationWarning, stacklevel=2) +from cubicweb.entity import * +from cubicweb.entity import _marker diff -r 49075f57cf2c -r aa09e20dd8c0 common/html4zope.py --- a/common/html4zope.py Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ -# Author: David Goodger -# Contact: goodger@users.sourceforge.net -# Revision: $Revision: 1.2 $ -# Date: $Date: 2005-07-04 16:36:50 $ -# Copyright: This module has been placed in the public domain. - -""" -Simple HyperText Markup Language document tree Writer. - -The output conforms to the HTML 4.01 Transitional DTD and to the Extensible -HTML version 1.0 Transitional DTD (*almost* strict). The output contains a -minimum of formatting information. A cascading style sheet ("default.css" by -default) is required for proper viewing with a modern graphical browser. - -http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup -""" - -__docformat__ = 'reStructuredText' - -from logilab.mtconverter import html_escape - -from docutils import nodes -from docutils.writers.html4css1 import Writer as CSS1Writer -from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator -import os - -default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3)) - -class Writer(CSS1Writer): - """css writer using our html translator""" - def __init__(self, base_url): - CSS1Writer.__init__(self) - self.translator_class = URLBinder(base_url, HTMLTranslator) - - def apply_template(self): - """overriding this is necessary with docutils >= 0.5""" - return self.visitor.astext() - -class URLBinder: - def __init__(self, url, klass): - self.base_url = url - self.translator_class = HTMLTranslator - - def __call__(self, document): - translator = self.translator_class(document) - translator.base_url = self.base_url - return translator - -class HTMLTranslator(CSS1HTMLTranslator): - """ReST tree to html translator""" - - def astext(self): - """return the extracted html""" - return ''.join(self.body) - - def visit_title(self, node): - """Only 6 section levels are supported by HTML.""" - if isinstance(node.parent, nodes.topic): - self.body.append( - self.starttag(node, 'p', '', CLASS='topic-title')) - if node.parent.hasattr('id'): - self.body.append( - self.starttag({}, 'a', '', name=node.parent['id'])) - self.context.append('

\n') - else: - self.context.append('

\n') - elif self.section_level == 0: - # document title - self.head.append('%s\n' - % self.encode(node.astext())) - self.body.append(self.starttag(node, 'h%d' % default_level, '', - CLASS='title')) - self.context.append('\n' % default_level) - else: - self.body.append( - self.starttag(node, 'h%s' % ( - default_level+self.section_level-1), '')) - atts = {} - if node.hasattr('refid'): - atts['class'] = 'toc-backref' - atts['href'] = '%s#%s' % (self.base_url, node['refid']) - self.body.append(self.starttag({}, 'a', '', **atts)) - self.context.append('\n' % ( - default_level+self.section_level-1)) - - def visit_subtitle(self, node): - """format a subtitle""" - if isinstance(node.parent, nodes.sidebar): - self.body.append(self.starttag(node, 'p', '', - CLASS='sidebar-subtitle')) - self.context.append('

\n') - else: - self.body.append( - self.starttag(node, 'h%s' % (default_level+1), '', - CLASS='subtitle')) - self.context.append('\n' % (default_level+1)) - - def visit_document(self, node): - """syt: i don't want the enclosing
""" - def depart_document(self, node): - """syt: i don't want the enclosing
""" - - def visit_reference(self, node): - """syt: i want absolute urls""" - if node.has_key('refuri'): - href = node['refuri'] - if ( self.settings.cloak_email_addresses - and href.startswith('mailto:')): - href = self.cloak_mailto(href) - self.in_mailto = 1 - else: - assert node.has_key('refid'), \ - 'References must have "refuri" or "refid" attribute.' - href = '%s#%s' % (self.base_url, node['refid']) - atts = {'href': href, 'class': 'reference'} - if not isinstance(node.parent, nodes.TextElement): - assert len(node) == 1 and isinstance(node[0], nodes.image) - atts['class'] += ' image-reference' - self.body.append(self.starttag(node, 'a', '', **atts)) - - ## override error messages to avoid XHTML problems ######################## - def visit_problematic(self, node): - pass - - def depart_problematic(self, node): - pass - - def visit_system_message(self, node): - backref_text = '' - if len(node['backrefs']): - backrefs = node['backrefs'] - if len(backrefs) == 1: - backref_text = '; backlink' - else: - i = 1 - backlinks = [] - for backref in backrefs: - backlinks.append(str(i)) - i += 1 - backref_text = ('; backlinks: %s' - % ', '.join(backlinks)) - if node.hasattr('line'): - line = ', line %s' % node['line'] - else: - line = '' - a_start = a_end = '' - error = u'System Message: %s%s/%s%s (%s %s)%s

\n' % ( - a_start, node['type'], node['level'], a_end, - self.encode(node['source']), line, backref_text) - self.body.append(u'
ReST / HTML errors:%s
' % html_escape(error)) - - def depart_system_message(self, node): - pass diff -r 49075f57cf2c -r aa09e20dd8c0 common/i18n.py --- a/common/i18n.py Tue May 05 17:18:49 2009 +0200 +++ b/common/i18n.py Thu May 14 12:48:11 2009 +0200 @@ -1,14 +1,14 @@ """Some i18n/gettext utilities. :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" import re import os -from os.path import join, abspath, basename, splitext, exists +from os.path import join, basename, splitext, exists from glob import glob from cubicweb.toolsutils import create_dir @@ -90,4 +90,3 @@ except Exception: continue return errors - diff -r 49075f57cf2c -r aa09e20dd8c0 common/mail.py --- a/common/mail.py Tue May 05 17:18:49 2009 +0200 +++ b/common/mail.py Thu May 14 12:48:11 2009 +0200 @@ -1,7 +1,7 @@ """Common utilies to format / semd emails. :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" diff -r 49075f57cf2c -r aa09e20dd8c0 common/migration.py --- a/common/migration.py Tue May 05 17:18:49 2009 +0200 +++ b/common/migration.py Thu May 14 12:48:11 2009 +0200 @@ -2,7 +2,7 @@ version :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" @@ -125,10 +125,10 @@ def repo_connect(self): return self.config.repository() - + def migrate(self, vcconf, toupgrade, options): """upgrade the given set of cubes - + `cubes` is an ordered list of 3-uple: (cube, fromversion, toversion) """ @@ -149,23 +149,23 @@ 'versions_map': vmap}) self.scripts_session(scripts) else: - print 'no migration script to execute' + print 'no migration script to execute' def shutdown(self): pass - + def __getattribute__(self, name): try: return object.__getattribute__(self, name) except AttributeError: cmd = 'cmd_%s' % name if hasattr(self, cmd): - meth = getattr(self, cmd) + meth = getattr(self, cmd) return lambda *args, **kwargs: self.interact(args, kwargs, meth=meth) raise raise AttributeError(name) - + def interact(self, args, kwargs, meth): """execute the given method according to user's confirmation""" msg = 'execute command: %s(%s) ?' % ( @@ -224,7 +224,7 @@ except ImportError: # readline not available pass - else: + else: readline.set_completer(Completer(local_ctx).complete) readline.parse_and_bind('tab: complete') histfile = os.path.join(os.environ["HOME"], ".eshellhist") @@ -256,7 +256,7 @@ else: context[attr[4:]] = getattr(self, attr) return context - + def process_script(self, migrscript, funcname=None, *args, **kwargs): """execute a migration script in interactive mode, display the migration script path, ask for @@ -280,7 +280,7 @@ self.critical('no %s in script %s', funcname, migrscript) return None return func(*args, **kwargs) - + def scripts_session(self, migrscripts): """execute some scripts in a transaction""" try: @@ -311,7 +311,7 @@ def cmd_option_type_changed(self, optname, oldtype, newvalue): """a configuration option's type has changed""" self._option_changes.append(('typechanged', optname, oldtype, newvalue)) - + def cmd_add_cubes(self, cubes): """modify the list of used cubes in the in-memory config returns newly inserted cubes, including dependencies @@ -319,7 +319,7 @@ if isinstance(cubes, basestring): cubes = (cubes,) origcubes = self.config.cubes() - newcubes = [p for p in self.config.expand_cubes(cubes) + newcubes = [p for p in self.config.expand_cubes(cubes) if not p in origcubes] if newcubes: for cube in cubes: @@ -340,7 +340,7 @@ assert cube in removed, \ "can't remove cube %s, used as a dependancy" % cube return removed - + def rewrite_configuration(self): # import locally, show_diffs unavailable in gae environment from cubicweb.toolsutils import show_diffs diff -r 49075f57cf2c -r aa09e20dd8c0 common/mixins.py --- a/common/mixins.py Tue May 05 17:18:49 2009 +0200 +++ b/common/mixins.py Thu May 14 12:48:11 2009 +0200 @@ -7,9 +7,11 @@ """ __docformat__ = "restructuredtext en" +from logilab.common.deprecation import obsolete from logilab.common.decorators import cached -from cubicweb.common.selectors import implement_interface +from cubicweb import typed_eid +from cubicweb.selectors import implements from cubicweb.interfaces import IWorkflowable, IEmailable, ITree @@ -24,10 +26,10 @@ # XXX misnamed parent_target = 'subject' children_target = 'object' - + def different_type_children(self, entities=True): """return children entities of different type as this entity. - + according to the `entities` parameter, return entity objects or the equivalent result set """ @@ -39,7 +41,7 @@ def same_type_children(self, entities=True): """return children entities of the same type as this entity. - + according to the `entities` parameter, return entity objects or the equivalent result set """ @@ -48,7 +50,7 @@ if entities: return [e for e in res if e.e_schema == self.e_schema] return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.col) - + def iterchildren(self, _done=None): if _done is None: _done = set() @@ -72,7 +74,7 @@ yield entity except AttributeError: pass - + @cached def path(self): """returns the list of eids from the root object to this object""" @@ -94,7 +96,7 @@ path.reverse() return path - + def iterparents(self): def _uptoroot(self): curr = self @@ -108,7 +110,7 @@ def notification_references(self, view): """used to control References field of email send on notification for this entity. `view` is the notification view. - + Should return a list of eids which can be used to generate message ids of previously sent email """ @@ -140,7 +142,7 @@ def children_rql(self): return self.related_rql(self.tree_attribute, self.children_target) - + def __iter__(self): return self.iterchildren() @@ -161,7 +163,7 @@ relation (which implies supporting 'wf_info_for' as well) """ __implements__ = (IWorkflowable,) - + @property def state(self): try: @@ -169,7 +171,7 @@ except IndexError: self.warning('entity %s has no state', self) return None - + @property def displayable_state(self): return self.req._(self.state) @@ -180,14 +182,14 @@ if rset: return rset.get_entity(0, 0) return None - + def wf_transition(self, trname): rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, T transition_of E, E name %(e)s', {'n': trname, 'e': str(self.e_schema)}) if rset: return rset.get_entity(0, 0) return None - + def change_state(self, stateeid, trcomment=None, trcommentformat=None): """change the entity's state according to a state defined in given parameters @@ -199,7 +201,7 @@ self.req.set_shared_data('trcommentformat', trcommentformat) self.req.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s', {'x': self.eid, 's': stateeid}, 'x') - + def can_pass_transition(self, trname): """return the Transition instance if the current user can pass the transition with the given name, else None @@ -213,42 +215,30 @@ for tr in rset.entities(): if tr.may_be_passed(self.eid, stateeid): return tr - + def latest_trinfo(self): """return the latest transition information for this entity""" return self.reverse_wf_info_for[-1] - - # specific vocabulary methods ############################################# - def subject_in_state_vocabulary(self, rschema, limit=None): - """vocabulary method for the in_state relation, looking for - relation's object entities (i.e. self is the subject) according - to initial_state, state_of and next_state relation - """ - if not self.has_eid() or not self.in_state: - # get the initial state - rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S' - rset = self.req.execute(rql, {'etype': str(self.e_schema)}) - if rset: - return [(rset.get_entity(0, 0).view('combobox'), rset[0][0])] - return [] - results = [] - for tr in self.in_state[0].transitions(self): - state = tr.destination_state[0] - results.append((state.view('combobox'), state.eid)) - return sorted(results) - # __method methods ######################################################## - + def set_state(self, params=None): """change the entity's state according to a state defined in given parameters, used to be called using __method controler facility """ params = params or self.req.form - self.change_state(int(params.pop('state')), params.get('trcomment'), + self.change_state(typed_eid(params.pop('state')), + params.get('trcomment'), params.get('trcommentformat')) self.req.set_message(self.req._('__msg state changed')) + # specific vocabulary methods ############################################# + + @obsolete('use EntityFieldsForm.subject_in_state_vocabulary') + def subject_in_state_vocabulary(self, rschema, limit=None): + from cubicweb.web.form import EntityFieldsForm + return EntityFieldsForm(self.req, None, entity=self).subject_in_state_vocabulary(rschema, limit) + class EmailableMixIn(object): @@ -259,7 +249,7 @@ primary_email / use_email scheme """ __implements__ = (IEmailable,) - + def get_email(self): if getattr(self, 'primary_email', None): return self.primary_email[0].address @@ -281,14 +271,14 @@ 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. """ return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() ) - + MI_REL_TRIGGERS = { ('in_state', 'subject'): WorkflowableMixIn, ('primary_email', 'subject'): EmailableMixIn, @@ -316,14 +306,13 @@ """a recursive tree view""" id = 'tree' item_vid = 'treeitem' - __selectors__ = (implement_interface,) - accepts_interfaces = (ITree,) + __select__ = implements(ITree) def call(self, done=None, **kwargs): if done is None: done = set() super(TreeViewMixIn, self).call(done=done, **kwargs) - + def cell_call(self, row, col=0, vid=None, done=None, **kwargs): done, entity = _done_init(done, self, row, col) if done is None: @@ -352,7 +341,7 @@ self.w(u'
') super(TreePathMixIn, self).call(**kwargs) self.w(u'
') - + def cell_call(self, row, col=0, vid=None, done=None, **kwargs): done, entity = _done_init(done, self, row, col) if done is None: @@ -394,7 +383,7 @@ def in_progress(self): raise NotImplementedError() - + def progress(self): try: return 100. * self.done / self.revised_cost diff -r 49075f57cf2c -r aa09e20dd8c0 common/mttransforms.py --- a/common/mttransforms.py Tue May 05 17:18:49 2009 +0200 +++ b/common/mttransforms.py Thu May 14 12:48:11 2009 +0200 @@ -1,7 +1,7 @@ """mime type transformation engine for cubicweb, based on mtconverter :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" @@ -11,10 +11,10 @@ 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, remove_html_tags +from cubicweb.common.uilib import rest_publish, html_publish HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml') @@ -32,15 +32,6 @@ def _convert(self, trdata): return html_publish(trdata.appobject, trdata.data) -class ept_to_html(Transform): - inputs = ('text/cubicweb-page-template',) - output = 'text/html' - output_encoding = 'utf-8' - def _convert(self, trdata): - from cubicweb.common.tal import compile_template - value = trdata.encode(self.output_encoding) - return trdata.appobject.tal_render(compile_template(value), {}) - # Instantiate and configure the transformation engine @@ -49,13 +40,32 @@ ENGINE = TransformEngine() ENGINE.add_transform(rest_to_html()) ENGINE.add_transform(html_to_html()) -ENGINE.add_transform(ept_to_html()) + +try: + from cubicweb.ext.tal import compile_template +except ImportError: + 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' + output_encoding = 'utf-8' + def _convert(self, trdata): + value = trdata.encode(self.output_encoding) + return trdata.appobject.tal_render(compile_template(value), {}) + + ENGINE.add_transform(ept_to_html()) if register_pil_transforms(ENGINE, verb=False): HAS_PIL_TRANSFORMS = True else: HAS_PIL_TRANSFORMS = False - + try: from logilab.mtconverter.transforms import pygmentstransforms for mt in ('text/plain',) + HTML_MIMETYPES: @@ -74,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 49075f57cf2c -r aa09e20dd8c0 common/registerers.py --- a/common/registerers.py Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,207 +0,0 @@ -"""This file contains some basic registerers required by application objects -registry to handle registration at startup time. - -A registerer is responsible to tell if an object should be registered according -to the application's schema or to already registered object - -:organization: Logilab -:copyright: 2006-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" -__docformat__ = "restructuredtext en" - -from cubicweb.vregistry import registerer - - -def _accepts_interfaces(obj): - return sorted(getattr(obj, 'accepts_interfaces', ())) - - -class yes_registerer(registerer): - """register without any other action""" - def do_it_yourself(self, registered): - return self.vobject - -class priority_registerer(registerer): - """systematically kick previous registered class and register the - wrapped class (based on the fact that directory containing vobjects - are loaded from the most generic to the most specific). - - This is usually for templates or startup views where we want to - keep only the latest in the load path - """ - def do_it_yourself(self, registered): - if registered: - if len(registered) > 1: - self.warning('priority_registerer found more than one registered objects ' - '(registerer monkey patch ?)') - for regobj in registered[:]: - self.kick(registered, regobj) - return self.vobject - - def remove_equivalents(self, registered): - for _obj in registered[:]: - if self.equivalent(_obj): - self.kick(registered, _obj) - break - - def remove_all_equivalents(self, registered): - for _obj in registered[:]: - if _obj is self.vobject: - continue - if self.equivalent(_obj): - self.kick(registered, _obj) - - def equivalent(self, other): - raise NotImplementedError(self, self.vobject) - - -class kick_registerer(registerer): - """systematically kick previous registered class and don't register the - wrapped class. This is temporarily used to discard library object registrable - but that we don't want to use - """ - def do_it_yourself(self, registered): - if registered: - self.kick(registered, registered[-1]) - return - - -class accepts_registerer(priority_registerer): - """register according to the .accepts attribute of the wrapped - class, which should be a tuple refering some entity's types - - * if no type is defined the application'schema, skip the wrapped - class - * if the class defines a requires attribute, each entity type defined - in the requires list must be in the schema - * if an object previously registered has equivalent .accepts - attribute, kick it out - * register - """ - def do_it_yourself(self, registered): - # if object is accepting interface, we have register it now and - # remove it latter if no object is implementing accepted interfaces - if _accepts_interfaces(self.vobject): - return self.vobject - if not 'Any' in self.vobject.accepts: - for ertype in self.vobject.accepts: - if ertype in self.schema: - break - else: - self.skip() - return None - for required in getattr(self.vobject, 'requires', ()): - if required not in self.schema: - self.skip() - return - self.remove_equivalents(registered) - return self.vobject - - def equivalent(self, other): - if _accepts_interfaces(self.vobject) != _accepts_interfaces(other): - return False - if getattr(self.vobject, 'require_groups', ()) != getattr(other, 'require_groups', ()): - return False - try: - newaccepts = list(other.accepts) - for etype in self.vobject.accepts: - try: - newaccepts.remove(etype) - except ValueError: - continue - if newaccepts: - other.accepts = tuple(newaccepts) - return False - return True - except AttributeError: - return False - - -class id_registerer(priority_registerer): - """register according to the "id" attribute of the wrapped class, - refering to an entity type. - - * if the type is not Any and is not defined the application'schema, - skip the wrapped class - * if an object previously registered has the same .id attribute, - kick it out - * register - """ - def do_it_yourself(self, registered): - etype = self.vobject.id - if etype != 'Any' and not self.schema.has_entity(etype): - self.skip() - return - self.remove_equivalents(registered) - return self.vobject - - def equivalent(self, other): - return other.id == self.vobject.id - - -class etype_rtype_registerer(registerer): - """registerer handling optional .etype and .rtype attributes.: - - * if .etype is set and is not an entity type defined in the - application schema, skip the wrapped class - * if .rtype or .relname is set and is not a relation type defined in - the application schema, skip the wrapped class - * register - """ - def do_it_yourself(self, registered): - cls = self.vobject - if hasattr(cls, 'etype'): - if not self.schema.has_entity(cls.etype): - return - rtype = getattr(cls, 'rtype', None) - if rtype and not self.schema.has_relation(rtype): - return - return cls - -class etype_rtype_priority_registerer(etype_rtype_registerer): - """add priority behaviour to the etype_rtype_registerer - """ - def do_it_yourself(self, registered): - cls = super(etype_rtype_priority_registerer, self).do_it_yourself(registered) - if cls: - registerer = priority_registerer(self.registry, cls) - cls = registerer.do_it_yourself(registered) - return cls - -class action_registerer(etype_rtype_registerer): - """'all in one' actions registerer, handling optional .accepts, - .etype and .rtype attributes: - - * if .etype is set and is not an entity type defined in the - application schema, skip the wrapped class - * if .rtype or .relname is set and is not a relation type defined in - the application schema, skip the wrapped class - * if .accepts is set, delegate to the accepts_registerer - * register - """ - def do_it_yourself(self, registered): - cls = super(action_registerer, self).do_it_yourself(registered) - if hasattr(cls, 'accepts'): - registerer = accepts_registerer(self.registry, cls) - cls = registerer.do_it_yourself(registered) - return cls - - -class extresources_registerer(priority_registerer): - """'registerer according to a .need_resources attributes which - should list necessary resource identifiers for the wrapped object. - If one of its resources is missing, don't register - """ - def do_it_yourself(self, registered): - if not hasattr(self.config, 'has_resource'): - return - for resourceid in self.vobject.need_resources: - if not self.config.has_resource(resourceid): - return - return super(extresources_registerer, self).do_it_yourself(registered) - - -__all__ = [cls.__name__ for cls in globals().values() - if isinstance(cls, type) and issubclass(cls, registerer) - and not cls is registerer] diff -r 49075f57cf2c -r aa09e20dd8c0 common/rest.py --- a/common/rest.py Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +0,0 @@ -"""rest publishing functions - -contains some functions and setup of docutils for cubicweb. Provides the -following ReST directives: - -* `eid`, create link to entity in the repository by their eid - -* `card`, create link to card entity in the repository by their wikiid - (proposing to create it when the refered card doesn't exist yet) - -* `winclude`, reference to a web documentation file (in wdoc/ directories) - -* `sourcecode` (if pygments is installed), source code colorization - -: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 cStringIO import StringIO -from itertools import chain -from logging import getLogger -from os.path import join - -from docutils import statemachine, nodes, utils, io -from docutils.core import publish_string -from docutils.parsers.rst import Parser, states, directives -from docutils.parsers.rst.roles import register_canonical_role, set_classes - -from logilab.mtconverter import html_escape - -from cubicweb.common.html4zope import Writer - -# We provide our own parser as an attempt to get rid of -# state machine reinstanciation - -import re -# compile states.Body patterns -for k, v in states.Body.patterns.items(): - if isinstance(v, str): - states.Body.patterns[k] = re.compile(v) - -# register ReStructured Text mimetype / extensions -import mimetypes -mimetypes.add_type('text/rest', '.rest') -mimetypes.add_type('text/rest', '.rst') - - -LOGGER = getLogger('cubicweb.rest') - -def eid_reference_role(role, rawtext, text, lineno, inliner, - options={}, content=[]): - try: - try: - eid_num, rest = text.split(u':', 1) - except: - eid_num, rest = text, '#'+text - eid_num = int(eid_num) - if eid_num < 0: - raise ValueError - except ValueError: - msg = inliner.reporter.error( - 'EID number must be a positive number; "%s" is invalid.' - % text, line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - # Base URL mainly used by inliner.pep_reference; so this is correct: - context = inliner.document.settings.context - refedentity = context.req.eid_rset(eid_num).get_entity(0, 0) - ref = refedentity.absolute_url() - set_classes(options) - return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref, - **options)], [] - -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. - - same as standard include directive but using config.locate_doc_resource to - get actual file to include. - - Most part of this implementation is copied from `include` directive defined - in `docutils.parsers.rst.directives.misc` - """ - context = state.document.settings.context - source = state_machine.input_lines.source( - lineno - state_machine.input_offset - 1) - #source_dir = os.path.dirname(os.path.abspath(source)) - fid = arguments[0] - for lang in chain((context.req.lang, context.vreg.property_value('ui.language')), - context.config.available_languages()): - rid = '%s_%s.rst' % (fid, lang) - resourcedir = context.config.locate_doc_file(rid) - if resourcedir: - break - else: - severe = state_machine.reporter.severe( - 'Problems with "%s" directive path:\nno resource matching %s.' - % (name, fid), - nodes.literal_block(block_text, block_text), line=lineno) - return [severe] - path = join(resourcedir, rid) - encoding = options.get('encoding', state.document.settings.input_encoding) - try: - state.document.settings.record_dependencies.add(path) - include_file = io.FileInput( - source_path=path, encoding=encoding, - error_handler=state.document.settings.input_encoding_error_handler, - handle_io_errors=None) - except IOError, error: - severe = state_machine.reporter.severe( - 'Problems with "%s" directive path:\n%s: %s.' - % (name, error.__class__.__name__, error), - nodes.literal_block(block_text, block_text), line=lineno) - return [severe] - try: - include_text = include_file.read() - except UnicodeError, error: - severe = state_machine.reporter.severe( - 'Problem with "%s" directive:\n%s: %s' - % (name, error.__class__.__name__, error), - nodes.literal_block(block_text, block_text), line=lineno) - return [severe] - if options.has_key('literal'): - literal_block = nodes.literal_block(include_text, include_text, - source=path) - literal_block.line = 1 - return literal_block - else: - include_lines = statemachine.string2lines(include_text, - convert_whitespace=1) - state_machine.insert_input(include_lines, path) - return [] - -winclude_directive.arguments = (1, 0, 1) -winclude_directive.options = {'literal': directives.flag, - 'encoding': directives.encoding} -directives.register_directive('winclude', winclude_directive) - -try: - from pygments import highlight - from pygments.lexers import get_lexer_by_name, LEXERS - from pygments.formatters import HtmlFormatter -except ImportError: - pass -else: - _PYGMENTS_FORMATTER = HtmlFormatter() - - def pygments_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - try: - lexer = get_lexer_by_name(arguments[0]) - except ValueError: - import traceback - traceback.print_exc() - print sorted(aliases for module_name, name, aliases, _, _ in LEXERS.itervalues()) - # no lexer found - lexer = get_lexer_by_name('text') - print 'LEXER', lexer - parsed = highlight(u'\n'.join(content), lexer, _PYGMENTS_FORMATTER) - context = state.document.settings.context - context.req.add_css('pygments.css') - return [nodes.raw('', parsed, format='html')] - - pygments_directive.arguments = (1, 0, 1) - pygments_directive.content = 1 - directives.register_directive('sourcecode', pygments_directive) - - -class CubicWebReSTParser(Parser): - """The (customized) reStructuredText parser.""" - - def __init__(self): - self.initial_state = 'Body' - self.state_classes = states.state_classes - self.inliner = states.Inliner() - self.statemachine = states.RSTStateMachine( - state_classes=self.state_classes, - initial_state=self.initial_state, - debug=0) - - def parse(self, inputstring, document): - """Parse `inputstring` and populate `document`, a document tree.""" - self.setup_parse(inputstring, document) - inputlines = statemachine.string2lines(inputstring, - convert_whitespace=1) - self.statemachine.run(inputlines, document, inliner=self.inliner) - self.finish_parse() - - -_REST_PARSER = CubicWebReSTParser() - -def rest_publish(context, data): - """publish a string formatted as ReStructured Text to HTML - - :type context: a cubicweb application object - - :type data: str - :param data: some ReST text - - :rtype: unicode - :return: - the data formatted as HTML or the original data if an error occured - """ - req = context.req - if isinstance(data, unicode): - encoding = 'unicode' - else: - encoding = req.encoding - settings = {'input_encoding': encoding, 'output_encoding': 'unicode', - 'warning_stream': StringIO(), 'context': context, - # dunno what's the max, severe is 4, and we never want a crash - # (though try/except may be a better option...) - 'halt_level': 10, - } - if context: - if hasattr(req, 'url'): - base_url = req.url() - elif hasattr(context, 'absolute_url'): - base_url = context.absolute_url() - else: - base_url = req.base_url() - else: - base_url = None - try: - return publish_string(writer=Writer(base_url=base_url), - parser=_REST_PARSER, source=data, - settings_overrides=settings) - except Exception: - LOGGER.exception('error while publishing ReST text') - if not isinstance(data, unicode): - data = unicode(data, encoding, 'replace') - return html_escape(req._('error while publishing ReST text') - + '\n\n' + data) diff -r 49075f57cf2c -r aa09e20dd8c0 common/schema.py --- a/common/schema.py Tue May 05 17:18:49 2009 +0200 +++ b/common/schema.py Thu May 14 12:48:11 2009 +0200 @@ -1,3 +1,5 @@ +"""pre 3.0 bw compat""" +# pylint: disable-msg=W0614,W0401 from warnings import warn warn('moved to cubicweb.schema', DeprecationWarning, stacklevel=2) from cubicweb.schema import * diff -r 49075f57cf2c -r aa09e20dd8c0 common/selectors.py --- a/common/selectors.py Tue May 05 17:18:49 2009 +0200 +++ b/common/selectors.py Thu May 14 12:48:11 2009 +0200 @@ -1,571 +1,6 @@ -"""This file contains some basic selectors required by application objects. - -A selector is responsible to score how well an object may be used with a -given result set (publishing time selection) - -If you have trouble with selectors, especially if the objet (typically -a view or a component) you want to use is not selected and you want to -know which one(s) of its selectors fail (e.g. returns 0), you can use -`traced_selection` or even direclty `TRACED_OIDS`. - -`TRACED_OIDS` is a tuple of traced object ids. The special value -'all' may be used to log selectors for all objects. - -For instance, say that the following code yields a `NoSelectableObject` -exception:: - - self.view('calendar', myrset) - -You can log the selectors involved for *calendar* by replacing the line -above by:: - - # in Python2.5 - from cubicweb.common.selectors import traced_selection - with traced_selection(): - self.view('calendar', myrset) - - # in Python2.4 - from cubicweb.common import selectors - selectors.TRACED_OIDS = ('calendar',) - self.view('calendar', myrset) - selectors.TRACED_OIDS = () - - - -: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" - -import logging - -from logilab.common.compat import all -from logilab.common.deprecation import deprecated_function - -from cubicweb import Unauthorized, NoSelectableObject, role -from cubicweb.cwvreg import DummyCursorError -from cubicweb.vregistry import chainall, chainfirst, NoSelectableObject -from cubicweb.cwconfig import CubicWebConfiguration -from cubicweb.schema import split_expression - -# helpers for debugging selectors -SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors') -TRACED_OIDS = () - -def lltrace(selector): - # don't wrap selectors if not in development mode - if CubicWebConfiguration.mode == 'installed': - return selector - def traced(cls, *args, **kwargs): - ret = selector(cls, *args, **kwargs) - if TRACED_OIDS == 'all' or cls.id in TRACED_OIDS: - SELECTOR_LOGGER.critical('selector %s returned %s for %s', selector.__name__, ret, cls) - return ret - traced.__name__ = selector.__name__ - return traced - -class traced_selection(object): - """selector debugging helper. - - Typical usage is : - - >>> with traced_selection(): - ... # some code in which you want to debug selectors - ... # for all objects - - or - - >>> with traced_selection( ('oid1', 'oid2') ): - ... # some code in which you want to debug selectors - ... # for objects with id 'oid1' and 'oid2' - - """ - def __init__(self, traced='all'): - self.traced = traced - - def __enter__(self): - global TRACED_OIDS - TRACED_OIDS = self.traced - - def __exit__(self, exctype, exc, traceback): - global TRACED_OIDS - TRACED_OIDS = () - return traceback is None - -# very basic selectors ######################################################## - -def yes(cls, *args, **kwargs): - """accept everything""" - return 1 -yes_selector = deprecated_function(yes) - -@lltrace -def none_rset(cls, req, rset, *args, **kwargs): - """accept no result set""" - if rset is None: - return 1 - return 0 -norset_selector = deprecated_function(none_rset) - -@lltrace -def any_rset(cls, req, rset, *args, **kwargs): - """accept result set, whatever the number of result""" - if rset is not None: - return 1 - return 0 -rset_selector = deprecated_function(any_rset) - -@lltrace -def nonempty_rset(cls, req, rset, *args, **kwargs): - """accept any non empty result set""" - if rset is not None and rset.rowcount: - return 1 - return 0 -anyrset_selector = deprecated_function(nonempty_rset) - -@lltrace -def empty_rset(cls, req, rset, *args, **kwargs): - """accept empty result set""" - if rset is not None and rset.rowcount == 0: - return 1 - return 0 -emptyrset_selector = deprecated_function(empty_rset) - -@lltrace -def one_line_rset(cls, req, rset, row=None, *args, **kwargs): - """accept result set with a single line of result""" - if rset is not None and (row is not None or rset.rowcount == 1): - return 1 - return 0 -onelinerset_selector = deprecated_function(one_line_rset) - -@lltrace -def two_lines_rset(cls, req, rset, *args, **kwargs): - """accept result set with *at least* two lines of result""" - if rset is not None and rset.rowcount > 1: - return 1 - return 0 -twolinerset_selector = deprecated_function(two_lines_rset) - -@lltrace -def two_cols_rset(cls, req, rset, *args, **kwargs): - """accept result set with at least one line and two columns of result""" - if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1: - return 1 - return 0 -twocolrset_selector = deprecated_function(two_cols_rset) - -@lltrace -def paginated_rset(cls, req, rset, *args, **kwargs): - """accept result sets with more rows than the page size - """ - page_size = kwargs.get('page_size') - if page_size is None: - page_size = req.form.get('page_size') - if page_size is None: - page_size = req.property_value('navigation.page-size') - else: - page_size = int(page_size) - if rset is None or len(rset) <= page_size: - return 0 - return 1 -largerset_selector = deprecated_function(paginated_rset) - -@lltrace -def sorted_rset(cls, req, rset, row=None, col=None, **kwargs): - """accept sorted result set""" - rqlst = rset.syntax_tree() - if len(rqlst.children) > 1 or not rqlst.children[0].orderby: - return 0 - return 2 -sortedrset_selector = deprecated_function(sorted_rset) - -@lltrace -def one_etype_rset(cls, req, rset, *args, **kwargs): - """accept result set where entities in the first columns are all of the - same type - """ - if len(rset.column_types(0)) != 1: - return 0 - return 1 -oneetyperset_selector = deprecated_function(one_etype_rset) - -@lltrace -def two_etypes_rset(cls, req, rset, **kwargs): - """accepts resultsets containing several entity types""" - if rset: - etypes = rset.column_types(0) - if len(etypes) > 1: - return 1 - return 0 -multitype_selector = deprecated_function(two_etypes_rset) - -@lltrace -def match_search_state(cls, req, rset, row=None, col=None, **kwargs): - """checks if the current search state is in a .search_states attribute of - the wrapped class - - search state should be either 'normal' or 'linksearch' (eg searching for an - object to create a relation with another) - """ - try: - if not req.search_state[0] in cls.search_states: - return 0 - except AttributeError: - return 1 # class doesn't care about search state, accept it - return 1 -searchstate_selector = deprecated_function(match_search_state) - -@lltrace -def anonymous_user(cls, req, *args, **kwargs): - """accept if user is anonymous""" - if req.cnx.anonymous_connection: - return 1 - return 0 -anonymous_selector = deprecated_function(anonymous_user) - -@lltrace -def authenticated_user(cls, req, *args, **kwargs): - """accept if user is authenticated""" - return not anonymous_user(cls, req, *args, **kwargs) -not_anonymous_selector = deprecated_function(authenticated_user) - -@lltrace -def match_form_params(cls, req, *args, **kwargs): - """check if parameters specified by the form_params attribute on - the wrapped class are specified in request form parameters - """ - score = 0 - for param in cls.form_params: - val = req.form.get(param) - if not val: - return 0 - score += 1 - return score + 1 -req_form_params_selector = deprecated_function(match_form_params) - -@lltrace -def match_kwargs(cls, req, *args, **kwargs): - """check if arguments specified by the expected_kwargs attribute on - the wrapped class are specified in given named parameters - """ - values = [] - for arg in cls.expected_kwargs: - if not arg in kwargs: - return 0 - return 1 -kwargs_selector = deprecated_function(match_kwargs) - - -# not so basic selectors ###################################################### - -@lltrace -def accept_etype(cls, req, *args, **kwargs): - """check etype presence in request form *and* accepts conformance""" - if 'etype' not in req.form and 'etype' not in kwargs: - return 0 - try: - etype = req.form['etype'] - except KeyError: - etype = kwargs['etype'] - # value is a list or a tuple if web request form received several - # values for etype parameter - assert isinstance(etype, basestring), "got multiple etype parameters in req.form" - if 'Any' in cls.accepts: - return 1 - # no Any found, we *need* exact match - if etype not in cls.accepts: - return 0 - # exact match must return a greater value than 'Any'-match - return 2 -etype_form_selector = deprecated_function(accept_etype) - -@lltrace -def _non_final_entity(cls, req, rset, row=None, col=None, **kwargs): - """accept non final entities - if row is not specified, use the first one - if col is not specified, use the first one - """ - etype = rset.description[row or 0][col or 0] - if etype is None: # outer join - return 0 - if cls.schema.eschema(etype).is_final(): - return 0 - return 1 -_nfentity_selector = deprecated_function(_non_final_entity) - -@lltrace -def _rql_condition(cls, req, rset, row=None, col=None, **kwargs): - """accept single entity result set if the entity match an rql condition - """ - if cls.condition: - eid = rset[row or 0][col or 0] - if 'U' in frozenset(split_expression(cls.condition)): - rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition - else: - rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition - try: - return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x')) - except Unauthorized: - return 0 - - return 1 -_rqlcondition_selector = deprecated_function(_rql_condition) - -@lltrace -def _implement_interface(cls, req, rset, row=None, col=None, **kwargs): - """accept uniform result sets, and apply the following rules: - - * wrapped class must have a accepts_interfaces attribute listing the - accepted ORed interfaces - * if row is None, return the sum of values returned by the method - for each entity's class in the result set. If any score is 0, - return 0. - * if row is specified, return the value returned by the method with - the entity's class of this row - """ - # XXX this selector can be refactored : extract the code testing - # for entity schema / interface compliance - score = 0 - # check 'accepts' to give priority to more specific classes - if row is None: - for etype in rset.column_types(col or 0): - eclass = cls.vreg.etype_class(etype) - escore = 0 - for iface in cls.accepts_interfaces: - escore += iface.is_implemented_by(eclass) - if not escore: - return 0 - score += escore - accepts = set(getattr(cls, 'accepts', ())) - # if accepts is defined on the vobject, eclass must match - if accepts: - eschema = eclass.e_schema - etypes = set([eschema] + eschema.ancestors()) - if accepts & etypes: - score += 2 - elif 'Any' not in accepts: - return 0 - return score + 1 - etype = rset.description[row][col or 0] - if etype is None: # outer join - return 0 - eclass = cls.vreg.etype_class(etype) - for iface in cls.accepts_interfaces: - score += iface.is_implemented_by(eclass) - if score: - accepts = set(getattr(cls, 'accepts', ())) - # if accepts is defined on the vobject, eclass must match - if accepts: - eschema = eclass.e_schema - etypes = set([eschema] + eschema.ancestors()) - if accepts & etypes: - score += 1 - elif 'Any' not in accepts: - return 0 - score += 1 - return score -_interface_selector = deprecated_function(_implement_interface) - -@lltrace -def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs): - if row is None: - rows = xrange(rset.rowcount) - else: - rows = (row,) - for row in rows: - try: - score = cls.score_entity(rset.get_entity(row, col or 0)) - except DummyCursorError: - # get a dummy cursor error, that means we are currently - # using a dummy rset to list possible views for an entity - # type, not for an actual result set. In that case, we - # don't care of the value, consider the object as selectable - return 1 - if not score: - return 0 - return 1 - -@lltrace -def accept_rset(cls, req, rset, row=None, col=None, **kwargs): - """simply delegate to cls.accept_rset method""" - return cls.accept_rset(req, rset, row=row, col=col) -accept_rset_selector = deprecated_function(accept_rset) - -@lltrace -def but_etype(cls, req, rset, row=None, col=None, **kwargs): - """restrict the searchstate_accept_one_selector to exclude entity's type - refered by the .etype attribute - """ - if rset.description[row or 0][col or 0] == cls.etype: - return 0 - return 1 -but_etype_selector = deprecated_function(but_etype) - -@lltrace -def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs): - """only check if the user has read access on the entity's type refered - by the .etype attribute and on the relations's type refered by the - .rtype attribute if set. - """ - schema = cls.schema - perm = getattr(cls, 'require_permission', 'read') - if hasattr(cls, 'etype'): - eschema = schema.eschema(cls.etype) - if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)): - return 0 - if hasattr(cls, 'rtype'): - rschema = schema.rschema(cls.rtype) - if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)): - return 0 - return 1 - -@lltrace -def has_relation(cls, req, rset, row=None, col=None, **kwargs): - """check if the user has read access on the relations's type refered by the - .rtype attribute of the class, and if all entities types in the - result set has this relation. - """ - if hasattr(cls, 'rtype'): - rschema = cls.schema.rschema(cls.rtype) - perm = getattr(cls, 'require_permission', 'read') - if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)): - return 0 - if row is None: - for etype in rset.column_types(col or 0): - if not cls.relation_possible(etype): - return 0 - elif not cls.relation_possible(rset.description[row][col or 0]): - return 0 - return 1 -accept_rtype_selector = deprecated_function(has_relation) - -@lltrace -def one_has_relation(cls, req, rset, row=None, col=None, **kwargs): - """check if the user has read access on the relations's type refered by the - .rtype attribute of the class, and if at least one entity type in the - result set has this relation. - """ - rschema = cls.schema.rschema(cls.rtype) - perm = getattr(cls, 'require_permission', 'read') - if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)): - return 0 - if row is None: - for etype in rset.column_types(col or 0): - if cls.relation_possible(etype): - return 1 - elif cls.relation_possible(rset.description[row][col or 0]): - return 1 - return 0 -one_has_relation_selector = deprecated_function(one_has_relation) - -@lltrace -def has_related_entities(cls, req, rset, row=None, col=None, **kwargs): - return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls))) - - -@lltrace -def match_user_group(cls, req, rset=None, row=None, col=None, **kwargs): - """select according to user's groups""" - if not cls.require_groups: - return 1 - user = req.user - if user is None: - return int('guests' in cls.require_groups) - score = 0 - if 'owners' in cls.require_groups and rset: - if row is not None: - eid = rset[row][col or 0] - if user.owns(eid): - score = 1 - else: - score = all(user.owns(r[col or 0]) for r in rset) - score += user.matching_groups(cls.require_groups) - if score: - # add 1 so that an object with one matching group take priority - # on an object without require_groups - return score + 1 - return 0 -in_group_selector = deprecated_function(match_user_group) - -@lltrace -def user_can_add_etype(cls, req, rset, row=None, col=None, **kwargs): - """only check if the user has add access on the entity's type refered - by the .etype attribute. - """ - if not cls.schema.eschema(cls.etype).has_perm(req, 'add'): - return 0 - return 1 -add_etype_selector = deprecated_function(user_can_add_etype) - -@lltrace -def match_context_prop(cls, req, rset, row=None, col=None, context=None, - **kwargs): - propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id)) - if not propval: - propval = cls.context - if context is not None and propval and context != propval: - return 0 - return 1 -contextprop_selector = deprecated_function(match_context_prop) - -@lltrace -def primary_view(cls, req, rset, row=None, col=None, view=None, - **kwargs): - if view is not None and not view.is_primary(): - return 0 - return 1 -primaryview_selector = deprecated_function(primary_view) - -def appobject_selectable(registry, oid): - """return a selector that will have a positive score if an object for the - given registry and object id is selectable for the input context - """ - @lltrace - def selector(cls, req, rset, *args, **kwargs): - try: - cls.vreg.select_object(registry, oid, req, rset, *args, **kwargs) - return 1 - except NoSelectableObject: - return 0 - return selector - - -# compound selectors ########################################################## - -non_final_entity = chainall(nonempty_rset, _non_final_entity) -non_final_entity.__name__ = 'non_final_entity' -nfentity_selector = deprecated_function(non_final_entity) - -implement_interface = chainall(non_final_entity, _implement_interface) -implement_interface.__name__ = 'implement_interface' -interface_selector = deprecated_function(implement_interface) - -accept = chainall(non_final_entity, accept_rset) -accept.__name__ = 'accept' -accept_selector = deprecated_function(accept) - -accept_one = chainall(one_line_rset, accept) -accept_one.__name__ = 'accept_one' -accept_one_selector = deprecated_function(accept_one) - -rql_condition = chainall(non_final_entity, one_line_rset, _rql_condition) -rql_condition.__name__ = 'rql_condition' -rqlcondition_selector = deprecated_function(rql_condition) - - -searchstate_accept = chainall(nonempty_rset, match_search_state, accept) -searchstate_accept.__name__ = 'searchstate_accept' -searchstate_accept_selector = deprecated_function(searchstate_accept) - -searchstate_accept_one = chainall(one_line_rset, match_search_state, - accept, _rql_condition) -searchstate_accept_one.__name__ = 'searchstate_accept_one' -searchstate_accept_one_selector = deprecated_function(searchstate_accept_one) - -searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype) -searchstate_accept_one_but_etype.__name__ = 'searchstate_accept_one_but_etype' -searchstate_accept_one_but_etype_selector = deprecated_function( - searchstate_accept_one_but_etype) +"""pre 3.2 bw compat""" +# pylint: disable-msg=W0614,W0401 +from warnings import warn +warn('moved to cubicweb.selectors', DeprecationWarning, stacklevel=2) +from cubicweb.selectors import * +from cubicweb.selectors import _rql_condition diff -r 49075f57cf2c -r aa09e20dd8c0 common/tags.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/tags.py Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,45 @@ +"""helper classes to generate simple (X)HTML tags + +:organization: Logilab +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from cubicweb.common.uilib import simple_sgml_tag + +class tag(object): + 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', False) +img = tag('img') +label = tag('label') +option = tag('option') +h1 = tag('h1') +h2 = tag('h2') +h3 = tag('h3') +h4 = tag('h4') +h5 = tag('h5') + +def select(name, id=None, multiple=False, options=[], **attrs): + if multiple: + attrs['multiple'] = 'multiple' + if id: + attrs['id'] = id + attrs['name'] = name + html = [u'') + return u'\n'.join(html) + diff -r 49075f57cf2c -r aa09e20dd8c0 common/tal.py --- a/common/tal.py Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,256 +0,0 @@ -"""provides simpleTAL extensions for CubicWeb - -:organization: Logilab -:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" - -__docformat__ = "restructuredtext en" - -import sys -import re -from os.path import exists, isdir, join -from logging import getLogger -from StringIO import StringIO - -from simpletal import simpleTAL, simpleTALES - -from logilab.common.decorators import cached - -LOGGER = getLogger('cubicweb.tal') - - -class LoggerAdapter(object): - def __init__(self, tal_logger): - self.tal_logger = tal_logger - - def debug(self, msg): - LOGGER.debug(msg) - - def warn(self, msg): - LOGGER.warning(msg) - - def __getattr__(self, attrname): - return getattr(self.tal_logger, attrname) - - -class CubicWebContext(simpleTALES.Context): - """add facilities to access entity / resultset""" - - def __init__(self, options=None, allowPythonPath=1): - simpleTALES.Context.__init__(self, options, allowPythonPath) - self.log = LoggerAdapter(self.log) - - def update(self, context): - for varname, value in context.items(): - self.addGlobal(varname, value) - - def addRepeat(self, name, var, initialValue): - simpleTALES.Context.addRepeat(self, name, var, initialValue) - -# XXX FIXME need to find a clean to define OPCODE values for extensions -I18N_CONTENT = 18 -I18N_REPLACE = 19 -RQL_EXECUTE = 20 -# simpleTAL uses the OPCODE values to define priority over commands. -# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but -# we can't use the same OPCODE for two different commands without changing -# the simpleTAL implementation. Another solution would be to totally override -# the REPEAT implementation with the ITER one, but some specific operations -# (involving len() for instance) are not implemented for ITER, so we prefer -# to keep both implementations for now, and to fool simpleTAL by using a float -# number between 3 and 4 -TAL_ITER = 3.1 - - -# FIX simpleTAL HTML 4.01 stupidity -# (simpleTAL never closes tags like INPUT, IMG, HR ...) -simpleTAL.HTML_FORBIDDEN_ENDTAG.clear() - -class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler): - """extends default compiler by adding i18n:content commands""" - - def __init__(self): - simpleTAL.HTMLTemplateCompiler.__init__(self) - self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content - self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace - self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql - self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter - - def setTALPrefix(self, prefix): - simpleTAL.TemplateCompiler.setTALPrefix(self, prefix) - self.tal_attribute_map['i18n:content'] = I18N_CONTENT - self.tal_attribute_map['i18n:replace'] = I18N_REPLACE - self.tal_attribute_map['rql:execute'] = RQL_EXECUTE - self.tal_attribute_map['tal:iter'] = TAL_ITER - - def compile_cmd_i18n_content(self, argument): - # XXX tal:content structure=, text= should we support this ? - structure_flag = 0 - return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol)) - - def compile_cmd_i18n_replace(self, argument): - # XXX tal:content structure=, text= should we support this ? - structure_flag = 0 - return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol)) - - def compile_cmd_rql(self, argument): - return (RQL_EXECUTE, (argument, self.endTagSymbol)) - - def compile_cmd_tal_iter(self, argument): - original_id, (var_name, expression, end_tag_symbol) = \ - simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument) - return (TAL_ITER, (var_name, expression, self.endTagSymbol)) - - def getTemplate(self): - return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable) - - def compileCmdAttributes (self, argument): - """XXX modified to support single attribute - definition ending by a ';' - - backport this to simpleTAL - """ - # Compile tal:attributes into attribute command - # Argument: [(attributeName, expression)] - - # Break up the list of attribute settings first - commandArgs = [] - # We only want to match semi-colons that are not escaped - argumentSplitter = re.compile(r'(? 1 - peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '**') - self.assertEquals(Personne.fetch_rql(user), - 'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB') - # XXX test unauthorized attribute - finally: - Personne.fetch_attrs = pfetch_attrs - Societe.fetch_attrs = sfetch_attrs - - def test_related_rql(self): - from cubicweb.entities import fetch_config - Personne = self.vreg.etype_class('Personne') - Societe = self.vreg.etype_class('Societe') - Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'prenom', 'sexe')) - Societe.fetch_attrs, Societe.fetch_order = fetch_config(('nom', 'web')) - aff = self.add_entity('Affaire', sujet=u'my subject', ref=u'the ref') - self.assertEquals(aff.related_rql('liee_a'), - 'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E liee_a X, ' - 'X nom AA, X modification_date AB') - Societe.fetch_attrs = ('web',) - self.assertEquals(aff.related_rql('liee_a'), - 'Any X ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E liee_a X') - - 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') - rschema = e.e_schema.subject_relation('tags') - related = [r.eid for r in e.tags] - self.failUnlessEqual(related, []) - unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')] - self.failUnless(p.eid in unrelated) - self.execute('SET X tags Y WHERE X is Tag, Y is Personne') - e = self.entity('Any X WHERE X is Tag') - unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')] - self.failIf(p.eid in unrelated) - - def test_entity_unrelated_limit(self): - e = self.add_entity('Tag', name=u'x') - self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien') - self.add_entity('Personne', nom=u'di mascio', prenom=u'gwen') - rschema = e.e_schema.subject_relation('tags') - self.assertEquals(len(e.vocabulary(rschema, 'subject', limit=1)), - 1) - - def test_new_entity_unrelated(self): - e = self.etype_instance('EUser') - rschema = e.e_schema.subject_relation('in_group') - unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')] - # should be default groups but owners, i.e. managers, users, guests - self.assertEquals(len(unrelated), 3) - - - def test_rtags_expansion(self): - from cubicweb.entities import AnyEntity - class Personne(AnyEntity): - id = 'Personne' - __rtags__ = { - ('travaille', 'Societe', 'subject') : set(('primary',)), - ('evaluee', '*', 'subject') : set(('secondary',)), - 'ecrit_par' : set(('inlineview',)), - } - self.vreg.register_vobject_class(Personne) - rtags = Personne.rtags - self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('secondary', 'link'))) - self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False) - self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link'))) - self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False) - self.assertEquals(rtags.get_tags('ecrit_par', 'Note', 'object'), set(('inlineview', 'link'))) - self.assertEquals(rtags.is_inlined('ecrit_par', 'Note', 'object'), True) - class Personne2(Personne): - id = 'Personne' - __rtags__ = { - ('evaluee', 'Note', 'subject') : set(('inlineview',)), - } - self.vreg.register_vobject_class(Personne2) - rtags = Personne2.rtags - self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('inlineview', 'link'))) - self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True) - self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link'))) - self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False) - - def test_relations_by_category(self): - e = self.etype_instance('EUser') - def rbc(iterable): - return [(rschema.type, x) for rschema, tschemas, x in iterable] - self.assertEquals(rbc(e.relations_by_category('primary')), - [('login', 'subject'), ('upassword', 'subject'), - ('in_group', 'subject'), ('in_state', 'subject'), - ('eid', 'subject'),]) - # firstname and surname are put in secondary category in views.entities.EUserEntity - self.assertListEquals(rbc(e.relations_by_category('secondary')), - [('firstname', 'subject'), ('surname', 'subject')]) - self.assertListEquals(rbc(e.relations_by_category('generic')), - [('primary_email', 'subject'), - ('evaluee', 'subject'), - ('for_user', 'object')]) - # owned_by is defined both as subject and object relations on EUser - self.assertListEquals(rbc(e.relations_by_category('generated')), - [('last_login_time', 'subject'), - ('created_by', 'subject'), - ('creation_date', 'subject'), - ('is', 'subject'), - ('is_instance_of', 'subject'), - ('modification_date', 'subject'), - ('owned_by', 'subject'), - ('created_by', 'object'), - ('wf_info_for', 'object'), - ('owned_by', 'object'), - ('bookmarked_by', 'object')]) - e = self.etype_instance('Personne') - self.assertListEquals(rbc(e.relations_by_category('primary')), - [('nom', 'subject'), ('eid', 'subject')]) - self.assertListEquals(rbc(e.relations_by_category('secondary')), - [('prenom', 'subject'), - ('sexe', 'subject'), - ('promo', 'subject'), - ('titre', 'subject'), - ('adel', 'subject'), - ('ass', 'subject'), - ('web', 'subject'), - ('tel', 'subject'), - ('fax', 'subject'), - ('datenaiss', 'subject'), - ('test', 'subject'), - ('description', 'subject'), - ('salary', 'subject')]) - self.assertListEquals(rbc(e.relations_by_category('generic')), - [('concerne', 'subject'), - ('connait', 'subject'), - ('evaluee', 'subject'), - ('travaille', 'subject'), - ('ecrit_par', 'object'), - ('evaluee', 'object'), - ('liee_a', 'object'), - ('tags', 'object')]) - self.assertListEquals(rbc(e.relations_by_category('generated')), - [('created_by', 'subject'), - ('creation_date', 'subject'), - ('is', 'subject'), - ('is_instance_of', 'subject'), - ('modification_date', 'subject'), - ('owned_by', 'subject')]) - - - def test_printable_value_string(self): - 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') - e['content'] = 'du html users' - e['content_format'] = 'text/html' - self.assertEquals(e.printable_value('content'), - 'du html users') - e['content'] = 'du *texte*' - e['content_format'] = 'text/plain' - self.assertEquals(e.printable_value('content'), - '

\ndu *texte*\n

') - e['title'] = 'zou' - e['content'] = '

titre

' - e['content_format'] = 'text/cubicweb-page-template' - self.assertEquals(e.printable_value('content'), - '

zou

') - - #e = self.etype_instance('Task') - e['content'] = '''\ -a title -======= -du :eid:`1:*ReST*`''' - e['content_format'] = 'text/rest' - self.assertEquals(e.printable_value('content', format='text/plain'), - e['content']) - - e['content'] = u'yo (zou éà ;)' - e['content_format'] = 'text/html' - self.assertEquals(e.printable_value('content', format='text/plain').strip(), - u'**yo (zou éà ;)**') - - def test_printable_value_bytes(self): - e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python', - data_encoding=u'ascii', name=u'toto.py') - from cubicweb.common import mttransforms - if mttransforms.HAS_PYGMENTS_TRANSFORMS: - self.assertEquals(e.printable_value('data'), - '''
lambda x: 1
-
-''') - else: - self.assertEquals(e.printable_value('data'), - '''
-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'), - u'

héhéhé

\n') - - def test_printable_value_bad_html(self): - """make sure we don't crash if we try to render invalid XHTML strings""" - e = self.add_entity('Card', title=u'bad html', content=u'
R&D
', - content_format=u'text/html') - tidy = lambda x: x.replace('\n', '') - self.assertEquals(tidy(e.printable_value('content')), - '
R&D
') - e['content'] = u'yo !! R&D
pas fermé' - self.assertEquals(tidy(e.printable_value('content')), - u'yo !! R&D
pas fermé
') - e['content'] = u'R&D' - self.assertEquals(tidy(e.printable_value('content')), u'R&D') - e['content'] = u'R&D;' - self.assertEquals(tidy(e.printable_value('content')), u'R&D;') - e['content'] = u'yo !! R&D
pas fermé' - self.assertEquals(tidy(e.printable_value('content')), - u'yo !! R&D
pas fermé
') - e['content'] = u'été
été' - self.assertEquals(tidy(e.printable_value('content')), - u'été
été
') - e['content'] = u'C'est un exemple sérieux' - self.assertEquals(tidy(e.printable_value('content')), - u"C'est un exemple sérieux") - # make sure valid xhtml is left untouched - e['content'] = u'
R&D
' - self.assertEquals(e.printable_value('content'), e['content']) - e['content'] = u'
été
' - self.assertEquals(e.printable_value('content'), e['content']) - e['content'] = u'été' - self.assertEquals(e.printable_value('content'), e['content']) - - - def test_entity_formatted_attrs(self): - e = self.etype_instance('Note') - self.assertEquals(e.formatted_attrs(), []) - e = self.etype_instance('File') - self.assertEquals(e.formatted_attrs(), ['description']) - e = self.etype_instance('AnotherNote') - self.assertEquals(e.formatted_attrs(), ['descr', 'descr2']) - - - def test_fulltextindex(self): - e = self.etype_instance('File') - e['name'] = 'an html file' - e['description'] = 'du html' - e['description_format'] = 'text/html' - e['data'] = Binary('some data') - e['data_format'] = 'text/html' - e['data_encoding'] = 'ascii' - 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() - try: - eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,' - 'X from_state S1, X to_state S2 WHERE ' - 'U login "admin", S1 name "activated", S2 name "deactivated"')[0][0] - trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x') - trinfo.complete() - self.failUnless(trinfo.relation_cached('from_state', 'subject')) - self.failUnless(trinfo.relation_cached('to_state', 'subject')) - self.failUnless(trinfo.relation_cached('wf_info_for', 'subject')) - # check with a missing relation - eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,' - 'X to_state S2 WHERE ' - 'U login "admin", S2 name "activated"')[0][0] - trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x') - trinfo.complete() - self.failUnless(isinstance(trinfo.creation_date, DateTimeType)) - self.failUnless(trinfo.relation_cached('from_state', 'subject')) - self.failUnless(trinfo.relation_cached('to_state', 'subject')) - self.failUnless(trinfo.relation_cached('wf_info_for', 'subject')) - self.assertEquals(trinfo.from_state, []) - finally: - self.rollback() - self.execute('DELETE RT add_permission G WHERE RT name "wf_info_for", G name "managers"') - self.commit() - - def test_request_cache(self): - req = self.request() - user = self.entity('EUser X WHERE X login "admin"', req=req) - state = user.in_state[0] - samestate = self.entity('State X WHERE X name "activated"', req=req) - self.failUnless(state is samestate) - - def test_rest_path(self): - note = self.add_entity('Note', type=u'z') - self.assertEquals(note.rest_path(), 'note/%s' % note.eid) - # unique attr - tag = self.add_entity('Tag', name=u'x') - self.assertEquals(tag.rest_path(), 'tag/x') - # test explicit rest_attr - person = self.add_entity('Personne', prenom=u'john', nom=u'doe') - self.assertEquals(person.rest_path(), 'personne/doe') - # ambiguity test - person2 = self.add_entity('Personne', prenom=u'remi', nom=u'doe') - self.assertEquals(person.rest_path(), 'personne/eid/%s' % person.eid) - self.assertEquals(person2.rest_path(), 'personne/eid/%s' % person2.eid) - # unique attr with None value (wikiid in this case) - card1 = self.add_entity('Card', title=u'hop') - self.assertEquals(card1.rest_path(), 'card/eid/%s' % card1.eid) - card2 = self.add_entity('Card', title=u'pod', wikiid=u'zob/i') - self.assertEquals(card2.rest_path(), 'card/zob%2Fi') - - def test_set_attributes(self): - person = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien') - self.assertEquals(person.prenom, u'adrien') - self.assertEquals(person.nom, u'di mascio') - person.set_attributes(prenom=u'sylvain', nom=u'thénault') - person = self.entity('Personne P') # XXX retreival needed ? - self.assertEquals(person.prenom, u'sylvain') - self.assertEquals(person.nom, u'thénault') - - def test_metainformation(self): - note = self.add_entity('Note', type=u'z') - metainf = note.metainformation() - self.assertEquals(metainf, {'source': {'adapter': 'native', 'uri': 'system'}, 'type': u'Note', 'extid': None}) - self.assertEquals(note.absolute_url(), 'http://testing.fr/cubicweb/note/%s' % note.eid) - 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 49075f57cf2c -r aa09e20dd8c0 common/test/unittest_mail.py --- a/common/test/unittest_mail.py Tue May 05 17:18:49 2009 +0200 +++ b/common/test/unittest_mail.py Thu May 14 12:48:11 2009 +0200 @@ -17,14 +17,14 @@ Another solution would be to use $LOGNAME, $USER or $USERNAME """ return pwd.getpwuid(os.getuid())[0] - + class EmailTC(EnvBasedTC): def test_format_mail(self): self.set_option('sender-addr', 'bim@boum.fr') self.set_option('sender-name', 'BimBam') - + mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'}, ['test@logilab.fr'], u'un petit cöucou', u'bïjour', config=self.config) @@ -47,7 +47,7 @@ self.assertEquals(msg.get('reply-to'), u'oim , BimBam ') self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou') - + def test_format_mail_euro(self): mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'}, ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €') @@ -92,7 +92,7 @@ self.assertEquals(msg.get('reply-to'), u'tutu ') # set sender name and address as expected self.set_option('sender-name', 'cubicweb-test') - self.set_option('sender-addr', 'cubicweb-test@logilab.fr') + self.set_option('sender-addr', 'cubicweb-test@logilab.fr') # anonymous notification: no name and no email specified msg = format_mail({'name': u'', 'email': u''}, ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €', @@ -119,4 +119,4 @@ if __name__ == '__main__': unittest_main() - + diff -r 49075f57cf2c -r aa09e20dd8c0 common/test/unittest_migration.py --- a/common/test/unittest_migration.py Tue May 05 17:18:49 2009 +0200 +++ b/common/test/unittest_migration.py Thu May 14 12:48:11 2009 +0200 @@ -24,13 +24,15 @@ def cube_migration_scripts_dir(cls, cube): return TMIGRDIR - + class MigrationToolsTC(TestCase): def setUp(self): self.config = MigrTestConfig('data') from yams.schema import Schema self.config.load_schema = lambda expand_cubes=False: Schema('test') - + self.config.__class__.cubicweb_vobject_path = frozenset() + self.config.__class__.cube_vobject_path = frozenset() + def test_migration_files_base(self): self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)), ('TEMPLATE', (0,0,2), (0,0,3))]), @@ -46,7 +48,7 @@ [SMIGRDIR+'bootstrapmigration_repository.py', SMIGRDIR+'2.6.0_Any.sql', TMIGRDIR+'0.0.4_Any.py']) - + ## def test_migration_files_overlap(self): ## self.assertListEquals(migration_files(self.config, (2,4,0), (2,10,2), ## (0,0,2), (0,1,2)), @@ -60,7 +62,7 @@ ## TMIGRDIR+'0.1.0_repository.py', ## TMIGRDIR+'0.1.2_Any.py', ## SMIGRDIR+'2.10.1_2.10.2_Any.sql']) - + def test_migration_files_for_mode(self): from cubicweb.server.migractions import ServerMigrationHelper self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper) diff -r 49075f57cf2c -r aa09e20dd8c0 common/test/unittest_mixins.py --- a/common/test/unittest_mixins.py Tue May 05 17:18:49 2009 +0200 +++ b/common/test/unittest_mixins.py Thu May 14 12:48:11 2009 +0200 @@ -7,19 +7,19 @@ self.execute('SET X state_of ET WHERE ET name "Bookmark", X eid %(x)s', {'x': s.eid}) es = self.user().wf_state('activated') - self.assertEquals(es.state_of[0].name, 'EUser') - + self.assertEquals(es.state_of[0].name, 'CWUser') + def test_wf_transition(self): t = self.add_entity('Transition', name=u'deactivate') self.execute('SET X transition_of ET WHERE ET name "Bookmark", X eid %(x)s', {'x': t.eid}) et = self.user().wf_transition('deactivate') - self.assertEquals(et.transition_of[0].name, 'EUser') + self.assertEquals(et.transition_of[0].name, 'CWUser') def test_change_state(self): user = self.user() user.change_state(user.wf_state('deactivated').eid) self.assertEquals(user.state, 'deactivated') - + if __name__ == '__main__': unittest_main() diff -r 49075f57cf2c -r aa09e20dd8c0 common/test/unittest_rest.py --- a/common/test/unittest_rest.py Tue May 05 17:18:49 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('EUser 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 49075f57cf2c -r aa09e20dd8c0 common/test/unittest_uilib.py --- a/common/test/unittest_uilib.py Tue May 05 17:18:49 2009 +0200 +++ b/common/test/unittest_uilib.py Thu May 14 12:48:11 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 49075f57cf2c -r aa09e20dd8c0 common/test/unittest_utils.py --- a/common/test/unittest_utils.py Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -"""unit tests for module cubicweb.common.utils""" - -from logilab.common.testlib import TestCase, unittest_main - -from cubicweb.common.utils import make_uid, UStringIO, SizeConstrainedList - - -class MakeUidTC(TestCase): - def test_1(self): - self.assertNotEquals(make_uid('xyz'), make_uid('abcd')) - self.assertNotEquals(make_uid('xyz'), make_uid('xyz')) - - def test_2(self): - d = {} - while len(d)<10000: - uid = make_uid('xyz') - if d.has_key(uid): - self.fail(len(d)) - d[uid] = 1 - - -class UStringIOTC(TestCase): - def test_boolean_value(self): - self.assert_(UStringIO()) - - -class SizeConstrainedListTC(TestCase): - - def test_append(self): - l = SizeConstrainedList(10) - for i in xrange(12): - l.append(i) - self.assertEquals(l, range(2, 12)) - - def test_extend(self): - testdata = [(range(5), range(5)), - (range(10), range(10)), - (range(12), range(2, 12)), - ] - for extension, expected in testdata: - l = SizeConstrainedList(10) - l.extend(extension) - yield self.assertEquals, l, expected - - -if __name__ == '__main__': - unittest_main() diff -r 49075f57cf2c -r aa09e20dd8c0 common/uilib.py --- a/common/uilib.py Tue May 05 17:18:49 2009 +0200 +++ b/common/uilib.py Thu May 14 12:48:11 2009 +0200 @@ -10,30 +10,13 @@ __docformat__ = "restructuredtext en" import csv -import decimal -import locale import re from urllib import quote as urlquote -from cStringIO import StringIO -from copy import deepcopy +from StringIO import StringIO -import simplejson - -from mx.DateTime import DateTimeType, DateTimeDeltaType - -from logilab.common.textutils import unormalize from logilab.mtconverter import html_escape, html_unescape -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. - - encoding is guessed by locale.getpreferredencoding() - """ - # date format may depend on the locale - encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8' - return unicode(date.strftime(fmt), encoding) - +from cubicweb.utils import ustrftime def rql_for_eid(eid): """return the rql query necessary to fetch entity with the given eid. This @@ -56,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')) @@ -78,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 @@ -216,20 +199,27 @@ 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 """ value = u'<%s' % tag if attrs: + try: + attrs['class'] = attrs.pop('klass') + 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 @@ -247,30 +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: - 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 @@ -324,7 +290,7 @@ else: for child in path[-1].children: build_matrix(path[:] + [child], matrix) - + matrix = [] build_matrix([tree], matrix) @@ -353,12 +319,12 @@ cell_12 = line[j+1] is not None cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None link_type = link_types.get((cell_11, cell_12, cell_21), 0) - if link_type == 0 and i > 0 and links[i-1][j] in (1,2,3): + 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 @@ -378,7 +344,7 @@ s += '' s += '' % link_cell s += '' % link_cell - + cell = line[-1] if cell: if cell.id == selected_node: @@ -468,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) @@ -476,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 @@ -514,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, DateTimeType): - ret = ret.strftime('%Y-%m-%d %H:%M') - elif isinstance(ret, DateTimeDeltaType): - ret = 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 49075f57cf2c -r aa09e20dd8c0 common/utils.py --- a/common/utils.py Tue May 05 17:18:49 2009 +0200 +++ b/common/utils.py Thu May 14 12:48:11 2009 +0200 @@ -1,263 +1,5 @@ -"""Some utilities for CubicWeb server/clients. - -:organization: Logilab -:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" -__docformat__ = "restructuredtext en" - -from md5 import md5 -from time import time -from random import randint, seed - -# initialize random seed from current time -seed() - -def make_uid(key): - """forge a unique identifier""" - msg = str(key) + "%.10f"%time() + str(randint(0, 1000000)) - return md5(msg).hexdigest() - -def working_hours(mxdate): - """ - Predicate returning True is the date's hour is in working hours (8h->20h) - """ - if mxdate.hour > 7 and mxdate.hour < 21: - return True - return False - -def date_range(begin, end, incr=1, include=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. - :param include: None (means no exclusion) or a function taking a - date as parameter, and returning True if the date - should be included. - """ - date = begin - while date <= end: - if include is None or include(date): - yield date - date += incr - - -def dump_class(cls, clsname): - """create copy of a class by creating an empty class inheriting - from the given cls. - - Those class will be used as place holder for attribute and relation - description - """ - # type doesn't accept unicode name - # return type.__new__(type, str(clsname), (cls,), {}) - # __autogenerated__ attribute is just a marker - return type(str(clsname), (cls,), {'__autogenerated__': True}) - - -def merge_dicts(dict1, dict2): - """update a copy of `dict1` with `dict2`""" - dict1 = dict(dict1) - dict1.update(dict2) - return dict1 - - -class SizeConstrainedList(list): - """simple list that makes sure the list does not get bigger - than a given size. - - when the list is full and a new element is added, the first - element of the list is removed before appending the new one - - >>> l = SizeConstrainedList(2) - >>> l.append(1) - >>> l.append(2) - >>> l - [1, 2] - >>> l.append(3) - [2, 3] - """ - def __init__(self, maxsize): - self.maxsize = maxsize - - def append(self, element): - if len(self) == self.maxsize: - del self[0] - super(SizeConstrainedList, self).append(element) - - def extend(self, sequence): - super(SizeConstrainedList, self).extend(sequence) - keepafter = len(self) - self.maxsize - if keepafter > 0: - del self[:keepafter] - - __iadd__ = extend - - -class UStringIO(list): - """a file wrapper which automatically encode unicode string to an encoding - specifed in the constructor - """ - - 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) - - def __repr__(self): - return '<%s at %#x>' % (self.__class__.__name__, id(self)) - - -class HTMLHead(UStringIO): - """wraps HTML header's stream - - Request objects use a HTMLHead instance to ease adding of - javascripts and stylesheets - """ - js_unload_code = u'jQuery(window).unload(unloadPageData);' - - def __init__(self): - super(HTMLHead, self).__init__() - self.jsvars = [] - self.jsfiles = [] - self.cssfiles = [] - self.ie_cssfiles = [] - self.post_inlined_scripts = [] - self.pagedata_unload = False - - - def add_raw(self, rawheader): - self.write(rawheader) - - def define_var(self, var, value): - self.jsvars.append( (var, value) ) - - def add_post_inline_script(self, content): - self.post_inlined_scripts.append(content) - - def add_onload(self, jscode): - 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 - - This function checks if the file has already been added - :param jsfile: the script's URL - """ - if jsfile not in self.jsfiles: - self.jsfiles.append(jsfile) - - def add_css(self, cssfile, media): - """adds `cssfile` to the list of javascripts used in the webpage - - This function checks if the file has already been added - :param cssfile: the stylesheet's URL - """ - if (cssfile, media) not in self.cssfiles: - self.cssfiles.append( (cssfile, media) ) - - def add_ie_css(self, cssfile, media='all'): - """registers some IE specific CSS""" - if (cssfile, media) not in self.ie_cssfiles: - self.ie_cssfiles.append( (cssfile, media) ) - - def add_unload_pagedata(self): - """registers onunload callback to clean page data on server""" - if not self.pagedata_unload: - self.post_inlined_scripts.append(self.js_unload_code) - self.pagedata_unload = True - - def getvalue(self): - """reimplement getvalue to provide a consistent (and somewhat browser - optimzed cf. http://stevesouders.com/cuzillion) order in external - resources declaration - """ - w = self.write - # 1/ variable declaration if any - if self.jsvars: - from simplejson import dumps - w(u'\n') - # 2/ css files - for cssfile, media in self.cssfiles: - w(u'\n' % - (media, cssfile)) - # 3/ ie css if necessary - if self.ie_cssfiles: - w(u' \n') - # 4/ js files - for jsfile in self.jsfiles: - w(u'\n' % jsfile) - # 5/ post inlined scripts (i.e. scripts depending on other JS files) - if self.post_inlined_scripts: - w(u'\n') - return u'\n%s\n' % super(HTMLHead, self).getvalue() - - -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 - # main stream - self.body = UStringIO() - self.doctype = u'' - # xmldecl and html opening tag - self.xmldecl = u'\n' % req.encoding - self.htmltag = u'' % (req.lang, req.lang) - - - def write(self, data): - """StringIO interface: this method will be assigned to self.w - """ - self.body.write(data) - - def getvalue(self): - """writes HTML headers, closes tag and writes HTML body""" - return u'%s\n%s\n%s\n%s\n%s\n' % (self.xmldecl, self.doctype, - self.htmltag, - self.head.getvalue(), - self.body.getvalue()) - - -class AcceptMixIn(object): - """Mixin class for vobjects defining the 'accepts' attribute describing - a set of supported entity type (Any by default). - """ - # XXX deprecated, no more necessary - - -from logilab.common.deprecation import moved, class_moved -rql_for_eid = moved('cubicweb.common.uilib', 'rql_for_eid') -ajax_replace_url = moved('cubicweb.common.uilib', 'ajax_replace_url') - -import cubicweb -Binary = class_moved(cubicweb.Binary) +"""pre 3.2 bw compat""" +# pylint: disable-msg=W0614,W0401 +from warnings import warn +warn('moved to cubicweb.utils', DeprecationWarning, stacklevel=2) +from cubicweb.utils import * diff -r 49075f57cf2c -r aa09e20dd8c0 common/view.py --- a/common/view.py Tue May 05 17:18:49 2009 +0200 +++ b/common/view.py Thu May 14 12:48:11 2009 +0200 @@ -1,481 +1,5 @@ -"""abstract views and templates classes for CubicWeb web client - - -: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 cStringIO import StringIO - -from logilab.mtconverter import html_escape - -from cubicweb import NotAnEntity, NoSelectableObject -from cubicweb.common.registerers import accepts_registerer, priority_registerer -from cubicweb.common.selectors import (chainfirst, match_user_group, accept, - nonempty_rset, empty_rset, none_rset) -from cubicweb.common.appobject import AppRsetObject, ComponentMixIn -from cubicweb.common.utils import UStringIO, HTMLStream - -_ = unicode - -# robots control -NOINDEX = u'' -NOFOLLOW = u'' - -CW_XHTML_EXTENSIONS = '''[ - - - ] ''' - -TRANSITIONAL_DOCTYPE = u'\n' - -STRICT_DOCTYPE = u'\n' - -class View(AppRsetObject): - """abstract view class, used as base for every renderable object such - as views, templates, some components...web - - A view is instantiated to render a [part of a] result set. View - subclasses may be parametred using the following class attributes: - - * `templatable` indicates if the view may be embeded in a main - template or if it has to be rendered standalone (i.e. XML for - instance) - * if the view is not templatable, it should set the `content_type` class - attribute to the correct MIME type (text/xhtml by default) - * the `category` attribute may be used in the interface to regroup related - objects together - - At instantiation time, the standard `req`, `rset`, and `cursor` - attributes are added and the `w` attribute will be set at rendering - time to a write function to use. - """ - __registry__ = 'views' - - templatable = True - need_navigation = True - # content_type = 'application/xhtml+xml' # text/xhtml' - binary = False - add_to_breadcrumbs = True - category = 'view' - - def __init__(self, req, rset): - super(View, self).__init__(req, rset) - self.w = None - - @property - def content_type(self): - if self.req.xhtml_browser(): - return 'application/xhtml+xml' - return 'text/html' - - def set_stream(self, w=None): - if self.w is not None: - return - if w is None: - if self.binary: - self._stream = stream = StringIO() - else: - self._stream = stream = UStringIO() - w = stream.write - else: - stream = None - self.w = w - return stream - - # main view interface ##################################################### - - def dispatch(self, w=None, **context): - """called to render a view object for a result set. - - This method is a dispatched to an actual method selected - according to optional row and col parameters, which are locating - a particular row or cell in the result set: - - * if row [and col] are specified, `cell_call` is called - * if none of them is supplied, the view is considered to apply on - the whole result set (which may be None in this case), `call` is - called - """ - row, col = context.get('row'), context.get('col') - if row is not None: - context.setdefault('col', 0) - view_func = self.cell_call - else: - view_func = self.call - stream = self.set_stream(w) - # stream = self.set_stream(context) - view_func(**context) - # return stream content if we have created it - if stream is not None: - return self._stream.getvalue() - - # should default .call() method add a
around each - # rset item - add_div_section = True - - def call(self, **kwargs): - """the view is called for an entire result set, by default loop - other rows of the result set and call the same view on the - particular row - - Views applicable on None result sets have to override this method - """ - rset = self.rset - if rset is None: - raise NotImplementedError, self - wrap = self.templatable and len(rset) > 1 and self.add_div_section - for i in xrange(len(rset)): - if wrap: - self.w(u'
') - self.wview(self.id, rset, row=i, **kwargs) - if wrap: - self.w(u"
") - - def cell_call(self, row, col, **kwargs): - """the view is called for a particular result set cell""" - raise NotImplementedError, self - - def linkable(self): - """return True if the view may be linked in a menu - - by default views without title are not meant to be displayed - """ - if not getattr(self, 'title', None): - return False - return True - - def is_primary(self): - return self.id == 'primary' - - def url(self): - """return the url associated with this view. Should not be - necessary for non linkable views, but a default implementation - is provided anyway. - """ - try: - return self.build_url(vid=self.id, rql=self.req.form['rql']) - except KeyError: - return self.build_url(vid=self.id) - - def set_request_content_type(self): - """set the content type returned by this view""" - self.req.set_content_type(self.content_type) - - # view utilities ########################################################## - - def view(self, __vid, rset, __fallback_vid=None, **kwargs): - """shortcut to self.vreg.render method avoiding to pass self.req""" - try: - view = self.vreg.select_view(__vid, self.req, rset, **kwargs) - except NoSelectableObject: - if __fallback_vid is None: - raise - view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs) - return view.dispatch(**kwargs) - - def wview(self, __vid, rset, __fallback_vid=None, **kwargs): - """shortcut to self.view method automatically passing self.w as argument - """ - self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) - - def whead(self, data): - self.req.html_headers.write(data) - - def wdata(self, data): - """simple helper that escapes `data` and writes into `self.w`""" - self.w(html_escape(data)) - - def action(self, actionid, row=0): - """shortcut to get action object with id `actionid`""" - return self.vreg.select_action(actionid, self.req, self.rset, - row=row) - - def action_url(self, actionid, label=None, row=0): - """simple method to be able to display `actionid` as a link anywhere - """ - action = self.vreg.select_action(actionid, self.req, self.rset, - row=row) - if action: - label = label or self.req._(action.title) - return u'%s' % (html_escape(action.url()), label) - return u'' - - def html_headers(self): - """return a list of html headers (eg something to be inserted between - and of the returned page - - by default return a meta tag to disable robot indexation of the page - """ - return [NOINDEX] - - def page_title(self): - """returns a title according to the result set - used for the - title in the HTML header - """ - vtitle = self.req.form.get('vtitle') - if vtitle: - return self.req._(vtitle) - # class defined title will only be used if the resulting title doesn't - # seem clear enough - vtitle = getattr(self, 'title', None) or u'' - if vtitle: - vtitle = self.req._(vtitle) - rset = self.rset - if rset and rset.rowcount: - if rset.rowcount == 1: - try: - entity = self.complete_entity(0) - # use long_title to get context information if any - clabel = entity.dc_long_title() - except NotAnEntity: - clabel = display_name(self.req, rset.description[0][0]) - clabel = u'%s (%s)' % (clabel, vtitle) - else : - etypes = rset.column_types(0) - if len(etypes) == 1: - etype = iter(etypes).next() - clabel = display_name(self.req, etype, 'plural') - else : - clabel = u'#[*] (%s)' % vtitle - else: - clabel = vtitle - return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title')) - - def output_url_builder( self, name, url, args ): - self.w(u'\n') - - def create_url(self, etype, **kwargs): - """ return the url of the entity creation form for a given entity type""" - return self.req.build_url('add/%s'%etype, **kwargs) - - -# concrete views base classes ################################################# - -class EntityView(View): - """base class for views applying on an entity (i.e. uniform result set) - """ - __registerer__ = accepts_registerer - __selectors__ = (accept,) - accepts = ('Any',) - category = 'entityview' - - def field(self, label, value, row=True, show_label=True, w=None, tr=True): - """ read-only field """ - if w is None: - w = self.w - if row: - w(u'
') - if show_label: - if tr: - label = display_name(self.req, label) - w(u'%s' % label) - w(u'
%s
' % value) - if row: - w(u'
') - - -class StartupView(View): - """base class for views which doesn't need a particular result set - to be displayed (so they can always be displayed !) - """ - __registerer__ = priority_registerer - __selectors__ = (match_user_group, none_rset) - require_groups = () - category = 'startupview' - - def url(self): - """return the url associated with this view. We can omit rql here""" - return self.build_url('view', vid=self.id) - - def html_headers(self): - """return a list of html headers (eg something to be inserted between - and of the returned page - - by default startup views are indexed - """ - return [] - - -class EntityStartupView(EntityView): - """base class for entity views which may also be applied to None - result set (usually a default rql is provided by the view class) - """ - __registerer__ = accepts_registerer - __selectors__ = (chainfirst(none_rset, accept),) - - default_rql = None - - def __init__(self, req, rset): - super(EntityStartupView, self).__init__(req, rset) - if rset is None: - # this instance is not in the "entityview" category - self.category = 'startupview' - - def startup_rql(self): - """return some rql to be executedif the result set is None""" - return self.default_rql - - def call(self, **kwargs): - """override call to execute rql returned by the .startup_rql - method if necessary - """ - if self.rset is None: - self.rset = self.req.execute(self.startup_rql()) - rset = self.rset - for i in xrange(len(rset)): - self.wview(self.id, rset, row=i, **kwargs) - - def url(self): - """return the url associated with this view. We can omit rql if we - are on a result set on which we do not apply. - """ - if not self.__select__(self.req, self.rset): - return self.build_url(vid=self.id) - return super(EntityStartupView, self).url() - - -class AnyRsetView(View): - """base class for views applying on any non empty result sets""" - __registerer__ = priority_registerer - __selectors__ = (nonempty_rset,) - - category = 'anyrsetview' - - def columns_labels(self, tr=True): - if tr: - translate = display_name - else: - translate = lambda req, val: val - rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support - labels = [] - for colindex, attr in enumerate(rqlstdescr): - # compute column header - if colindex == 0 or attr == 'Any': # find a better label - label = ','.join(translate(self.req, et) - for et in self.rset.column_types(colindex)) - else: - label = translate(self.req, attr) - labels.append(label) - return labels - - -class EmptyRsetView(View): - """base class for views applying on any empty result sets""" - __registerer__ = priority_registerer - __selectors__ = (empty_rset,) - - -# concrete template base classes ############################################## - -class Template(View): - """a template is almost like a view, except that by default a template - is only used globally (i.e. no result set adaptation) - """ - __registry__ = 'templates' - __registerer__ = priority_registerer - __selectors__ = (match_user_group,) - - require_groups = () - - def template(self, oid, **kwargs): - """shortcut to self.registry.render method on the templates registry""" - w = kwargs.pop('w', self.w) - self.vreg.render('templates', oid, self.req, w=w, **kwargs) - - -class MainTemplate(Template): - """main template are primary access point to render a full HTML page. - There is usually at least a regular main template and a simple fallback - one to display error if the first one failed - """ - - base_doctype = STRICT_DOCTYPE - - @property - def doctype(self): - if self.req.xhtml_browser(): - return self.base_doctype % CW_XHTML_EXTENSIONS - return self.base_doctype % '' - - def set_stream(self, w=None, templatable=True): - if templatable and self.w is not None: - return - - if w is None: - if self.binary: - self._stream = stream = StringIO() - elif not templatable: - # not templatable means we're using a non-html view, we don't - # want the HTMLStream stuff to interfere during data generation - self._stream = stream = UStringIO() - else: - self._stream = stream = HTMLStream(self.req) - w = stream.write - else: - stream = None - self.w = w - return stream - - def write_doctype(self, xmldecl=True): - assert isinstance(self._stream, HTMLStream) - self._stream.doctype = self.doctype - if not xmldecl: - self._stream.xmldecl = u'' - -# viewable components base classes ############################################ - -class VComponent(ComponentMixIn, View): - """base class for displayable components""" - property_defs = { - 'visible': dict(type='Boolean', default=True, - help=_('display the component or not')),} - -class SingletonVComponent(VComponent): - """base class for displayable unique components""" - __registerer__ = priority_registerer +"""pre 3.2 bw compat""" +# pylint: disable-msg=W0614,W0401 +from warnings import warn +warn('moved to cubicweb.view', DeprecationWarning, stacklevel=2) +from cubicweb.view import * diff -r 49075f57cf2c -r aa09e20dd8c0 cwconfig.py --- a/cwconfig.py Tue May 05 17:18:49 2009 +0200 +++ b/cwconfig.py Thu May 14 12:48:11 2009 +0200 @@ -4,6 +4,11 @@ :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr + +.. envvar:: CW_CUBES_PATH + + Augments the default search path for cubes + """ __docformat__ = "restructuredtext en" @@ -18,7 +23,7 @@ ConfigurationMixIn, merge_options) from cubicweb import CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, ConfigurationError -from cubicweb.toolsutils import env_path, read_config, create_dir +from cubicweb.toolsutils import env_path, create_dir CONFIGURATIONS = [] @@ -56,17 +61,6 @@ % (directory, modes)) return modes[0] -# XXX generate this according to the configuration (repository/all-in-one/web) -VREGOPTIONS = [] -for registry in ('etypes', 'hooks', 'controllers', 'actions', 'components', - 'views', 'templates', 'boxes', 'contentnavigation', 'urlrewriting', - 'facets'): - VREGOPTIONS.append(('disable-%s'%registry, - {'type' : 'csv', 'default': (), - 'help': 'list of identifier of application objects from the %s registry to disable'%registry, - 'group': 'appobjects', 'inputlevel': 2, - })) -VREGOPTIONS = tuple(VREGOPTIONS) # persistent options definition PERSISTENT_OPTIONS = ( @@ -75,44 +69,44 @@ 'default': 'UTF-8', 'help': _('user interface encoding'), 'group': 'ui', 'sitewide': True, - }), + }), ('language', {'type' : 'string', 'default': 'en', 'vocabulary': Method('available_languages'), 'help': _('language of the user interface'), - 'group': 'ui', + 'group': 'ui', }), ('date-format', {'type' : 'string', 'default': '%Y/%m/%d', 'help': _('how to format date in the ui ("man strftime" for format description)'), - 'group': 'ui', + 'group': 'ui', }), ('datetime-format', {'type' : 'string', 'default': '%Y/%m/%d %H:%M', 'help': _('how to format date and time in the ui ("man strftime" for format description)'), - 'group': 'ui', + 'group': 'ui', }), ('time-format', {'type' : 'string', 'default': '%H:%M', 'help': _('how to format time in the ui ("man strftime" for format description)'), - 'group': 'ui', + 'group': 'ui', }), ('float-format', {'type' : 'string', 'default': '%.3f', 'help': _('how to format float numbers in the ui'), - 'group': 'ui', + 'group': 'ui', }), ('default-text-format', {'type' : 'choice', 'choices': ('text/plain', 'text/rest', 'text/html'), 'default': 'text/html', # use fckeditor in the web ui 'help': _('default text format for rich text fields.'), - 'group': 'ui', + 'group': 'ui', }), ('short-line-size', {'type' : 'int', @@ -125,7 +119,7 @@ def register_persistent_options(options): global PERSISTENT_OPTIONS PERSISTENT_OPTIONS = merge_options(PERSISTENT_OPTIONS + options) - + CFGTYPE2ETYPE_MAP = { 'string': 'String', 'choice': 'String', @@ -133,7 +127,7 @@ 'int': 'Int', 'float' : 'Float', } - + class CubicWebNoAppConfiguration(ConfigurationMixIn): """base class for cubicweb configuration without a specific instance directory """ @@ -157,7 +151,7 @@ mode = 'installed' CUBES_DIR = '/usr/share/cubicweb/cubes/' - options = VREGOPTIONS + ( + options = ( ('log-threshold', {'type' : 'string', # XXX use a dedicated type? 'default': 'ERROR', @@ -195,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, @@ -202,9 +204,14 @@ this option is set to yes", 'group': 'email', 'inputlevel': 2, }), + ('disable-appobjects', + {'type' : 'csv', 'default': (), + 'help': 'comma separated list of identifiers of application objects (.) to disable', + 'group': 'appobjects', 'inputlevel': 2, + }), ) # static and class methods used to get application independant resources ## - + @staticmethod def cubicweb_version(): """return installed cubicweb version""" @@ -213,7 +220,7 @@ version = __pkginfo__.numversion assert len(version) == 3, version return Version(version) - + @staticmethod def persistent_options_configuration(): return Configuration(options=PERSISTENT_OPTIONS) @@ -226,7 +233,7 @@ if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'): return join(CW_SOFTWARE_ROOT, 'web') return cls.cube_dir('shared') - + @classmethod def i18n_lib_dir(cls): """return application's i18n directory""" @@ -242,7 +249,7 @@ if isdir(join(directory, cube)) and not cube in ('CVS', '.svn', 'shared', '.hg'): cubes.add(cube) return sorted(cubes) - + @classmethod def cubes_search_path(cls): """return the path of directories where cubes should be searched""" @@ -257,7 +264,7 @@ if not cls.CUBES_DIR in path: path.append(cls.CUBES_DIR) return path - + @classmethod def cube_dir(cls, cube): """return the cube directory for the given cube id, @@ -273,7 +280,7 @@ def cube_migration_scripts_dir(cls, cube): """cube migration scripts directory""" return join(cls.cube_dir(cube), 'migration') - + @classmethod def cube_pkginfo(cls, cube): """return the information module for the given cube""" @@ -286,7 +293,7 @@ @classmethod def cube_version(cls, cube): - """return the version of the cube located in the given directory + """return the version of the cube located in the given directory """ from logilab.common.changelog import Version version = cls.cube_pkginfo(cube).numversion @@ -349,7 +356,7 @@ except KeyError: continue return tuple(reversed(cubes)) - + @classmethod def cls_adjust_sys_path(cls): """update python path if necessary""" @@ -387,7 +394,7 @@ except: cls.exception('while loading cube %s', cube) else: - cls.warning('no __init__ file in cube %s', cube) + cls.warning('no __init__ file in cube %s', cube) @classmethod def init_available_cubes(cls): @@ -399,7 +406,7 @@ __import__('cubes.%s' % cube) except Exception, ex: cls.warning("can't init cube %s: %s", cube, ex) - + cubicweb_vobject_path = set(['entities']) cube_vobject_path = set(['entities']) @@ -447,17 +454,17 @@ elif exists(path + '.py'): vregpath.append(path + '.py') return vregpath - + def __init__(self): ConfigurationMixIn.__init__(self) self.adjust_sys_path() self.load_defaults() - self.translations = {} + self.translations = {} def adjust_sys_path(self): self.cls_adjust_sys_path() - - def init_log(self, logthreshold=None, debug=False, + + def init_log(self, logthreshold=None, debug=False, logfile=None, syslog=False): """init the log service""" if logthreshold is None: @@ -474,7 +481,7 @@ for application objects. By default return nothing in NoApp config. """ return [] - + def eproperty_definitions(self): cfg = self.persistent_options_configuration() for section, options in cfg.options_by_section(): @@ -487,7 +494,7 @@ 'help': optdict['help'], 'sitewide': optdict.get('sitewide', False)} yield key, pdef - + def map_option(self, optdict): try: vocab = optdict['choices'] @@ -497,10 +504,10 @@ vocab = getattr(self, vocab.method, ()) return CFGTYPE2ETYPE_MAP[optdict['type']], vocab - + class CubicWebConfiguration(CubicWebNoAppConfiguration): """base class for cubicweb server and web configurations""" - + INSTANCE_DATA_DIR = None if CubicWebNoAppConfiguration.mode == 'test': root = os.environ['APYCOT_ROOT'] @@ -523,7 +530,7 @@ set_language = True # set this to true to avoid false error message while creating an application creating = False - + options = CubicWebNoAppConfiguration.options + ( ('log-file', {'type' : 'string', @@ -546,7 +553,7 @@ }), ('sender-name', {'type' : 'string', - 'default': Method('default_application_id'), + 'default': Method('default_application_id'), 'help': 'name used as HELO name for outgoing emails from the \ repository.', 'group': 'email', 'inputlevel': 2, @@ -564,7 +571,7 @@ def runtime_dir(cls): """run time directory for pid file...""" return env_path('CW_RUNTIME', cls.RUNTIME_DIR, 'run time') - + @classmethod def registry_dir(cls): """return the control directory""" @@ -576,7 +583,7 @@ return env_path('CW_INSTANCE_DATA', cls.INSTANCE_DATA_DIR or cls.REGISTRY_DIR, 'additional data') - + @classmethod def migration_scripts_dir(cls): """cubicweb migration scripts directory""" @@ -589,7 +596,7 @@ config = config or guess_configuration(cls.application_home(appid)) configcls = configuration_cls(config) return configcls(appid) - + @classmethod def possible_configurations(cls, appid): """return the name of possible configurations for the given @@ -597,7 +604,7 @@ """ home = cls.application_home(appid) return possible_configurations(home) - + @classmethod def application_home(cls, appid): """return the home directory of the application with the given @@ -616,9 +623,9 @@ def accept_mode(cls, mode): #assert mode in cls.MODES, mode return mode in cls.MCOMPAT[cls.name] - + # default configuration methods ########################################### - + def default_application_id(self): """return the application identifier, useful for option which need this as default value @@ -640,13 +647,13 @@ i += 1 return path return '/var/log/cubicweb/%s-%s.log' % (self.appid, self.name) - + def default_pid_file(self): """return default path to the pid file of the application'server""" return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name)) - + # instance methods used to get application specific resources ############# - + def __init__(self, appid): self.appid = appid CubicWebNoAppConfiguration.__init__(self) @@ -664,13 +671,13 @@ @property def apphome(self): return join(self.registry_dir(), self.appid) - + @property def appdatahome(self): 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: @@ -681,7 +688,7 @@ self.load_file_configuration(self.main_config_file()) # configuration initialization hook self.load_configuration() - + def cubes(self): """return the list of cubes used by this instance @@ -690,7 +697,7 @@ """ assert self._cubes is not None return self._cubes - + def cubes_path(self): """return the list of path to cubes used by this instance, from outer most to inner most cubes @@ -702,11 +709,11 @@ if not isinstance(cubes, list): cubes = list(cubes) self._cubes = self.reorder_cubes(list(self._cubes) + cubes) - + def main_config_file(self): """return application's control configuration file""" return join(self.apphome, '%s.conf' % self.name) - + def save(self): """write down current configuration""" self.generate_config(open(self.main_config_file(), 'w')) @@ -719,7 +726,7 @@ version = self.cube_version(pkg) infos.append('%s-%s' % (pkg, version)) return md5.new(';'.join(infos)).hexdigest() - + def load_site_cubicweb(self): """load (web?) application's specific site_cubicweb file""" for path in reversed([self.apphome] + self.cubes_path()): @@ -733,7 +740,7 @@ self._load_site_cubicweb(sitefile) self._site_loaded.add(sitefile) self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py') - + def _load_site_cubicweb(self, sitefile): context = {} execfile(sitefile, context, context) @@ -742,14 +749,14 @@ if context.get('options'): self.register_options(context['options']) self.load_defaults() - + def load_configuration(self): """load application's configuration files""" super(CubicWebConfiguration, self).load_configuration() if self.apphome and self.set_language: # init gettext self._set_language() - + def init_log(self, logthreshold=None, debug=False, force=False): """init the log service""" if not force and hasattr(self, '_logging_initialized'): @@ -775,7 +782,7 @@ lang = path.split(os.sep)[-3] if lang != 'en': yield lang - + def _set_language(self): """set language for gettext""" from gettext import translation @@ -787,8 +794,8 @@ self.translations[language] = tr.ugettext except (ImportError, AttributeError, IOError): self.exception('localisation support error for language %s', - language) - + language) + def vregistry_path(self): """return a list of files or directories where the registry will look for application objects @@ -802,7 +809,7 @@ if not 'all' in sources: print 'warning: ignoring specified sources, requires a repository '\ 'configuration' - + def migration_handler(self): """return a migration handler instance""" from cubicweb.common.migration import MigrationHelper @@ -820,7 +827,7 @@ return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs) set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration')) - + # alias to get a configuration instance from an application id -application_configuration = CubicWebConfiguration.config_for +application_configuration = CubicWebConfiguration.config_for diff -r 49075f57cf2c -r aa09e20dd8c0 cwctl.py --- a/cwctl.py Tue May 05 17:18:49 2009 +0200 +++ b/cwctl.py Thu May 14 12:48:11 2009 +0200 @@ -1,17 +1,18 @@ """%%prog %s [options] %s -CubicWeb main applications controller. +CubicWeb main applications controller. %s""" import sys -from os import remove, listdir, system, kill, getpgid +from os import remove, listdir, system, kill, getpgid, pathsep from os.path import exists, join, isfile, isdir +from logilab.common.clcommands import register_commands, pop_arg + from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage -from cubicweb.cwconfig import CubicWebConfiguration, CONFIGURATIONS -from cubicweb.toolsutils import (Command, register_commands, main_run, - rm, create_dir, pop_arg, confirm) - +from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS +from cubicweb.toolsutils import Command, main_run, rm, create_dir, confirm + def wait_process_end(pid, maxtry=10, waittime=1): """wait for a process to actually die""" import signal @@ -41,13 +42,13 @@ modes.append('web ui') break return modes - - + + class ApplicationCommand(Command): """base class for command taking 0 to n application id as arguments (0 meaning all registered applications) """ - arguments = '[...]' + arguments = '[...]' options = ( ("force", {'short': 'f', 'action' : 'store_true', @@ -57,13 +58,13 @@ ), ) actionverb = None - + def ordered_instances(self): """return instances in the order in which they should be started, considering $REGISTRY_DIR/startorder file if it exists (useful when some instances depends on another as external source """ - regdir = CubicWebConfiguration.registry_dir() + regdir = cwcfg.registry_dir() _allinstances = list_instances(regdir) if isfile(join(regdir, 'startorder')): allinstances = [] @@ -74,12 +75,13 @@ _allinstances.remove(line) allinstances.append(line) except ValueError: - print 'ERROR: startorder file contains unexistant instance %s' % line + print ('ERROR: startorder file contains unexistant ' + 'instance %s' % line) allinstances += _allinstances else: allinstances = _allinstances return allinstances - + def run(self, args): """run the _method on each argument (a list of application identifiers) @@ -94,7 +96,7 @@ else: askconfirm = False self.run_args(args, askconfirm) - + def run_args(self, args, askconfirm): for appid in args: if askconfirm: @@ -102,7 +104,7 @@ if not confirm('%s application %r ?' % (self.name, appid)): continue self.run_arg(appid) - + def run_arg(self, appid): cmdmeth = getattr(self, '%s_application' % self.name) try: @@ -141,7 +143,7 @@ sys.exit(status) else: self.run_arg(appid) - + # base commands ############################################################### class ListCommand(Command): @@ -153,16 +155,16 @@ name = 'list' options = ( ('verbose', - {'short': 'v', 'action' : 'store_true', - 'help': "display more information."}), + {'short': 'v', 'action' : 'store_true', + 'help': "display more information."}), ) - + def run(self, args): """run the command with its specific arguments""" if args: raise BadCommandUsage('Too much arguments') - print 'CubicWeb version:', CubicWebConfiguration.cubicweb_version() - print 'Detected mode:', CubicWebConfiguration.mode + print 'CubicWeb version:', cwcfg.cubicweb_version() + print 'Detected mode:', cwcfg.mode print print 'Available configurations:' for config in CONFIGURATIONS: @@ -172,21 +174,21 @@ if not line: continue print ' ', line - print - cubesdirs = ', '.join(CubicWebConfiguration.cubes_search_path()) + print try: - namesize = max(len(x) for x in CubicWebConfiguration.available_cubes()) + cubesdir = pathsep.join(cwcfg.cubes_search_path()) + namesize = max(len(x) for x in cwcfg.available_cubes()) except ConfigurationError, ex: print 'No cubes available:', ex except ValueError: - print 'No cubes available in %s' % cubesdirs + print 'No cubes available in %s' % cubesdir else: - print 'Available cubes (%s):' % cubesdirs - for cube in CubicWebConfiguration.available_cubes(): + print 'Available cubes (%s):' % cubesdir + for cube in cwcfg.available_cubes(): if cube in ('CVS', '.svn', 'shared', '.hg'): continue try: - tinfo = CubicWebConfiguration.cube_pkginfo(cube) + tinfo = cwcfg.cube_pkginfo(cube) tversion = tinfo.version except ConfigurationError: tinfo = None @@ -201,7 +203,7 @@ print ' available modes: %s' % ', '.join(modes) print try: - regdir = CubicWebConfiguration.registry_dir() + regdir = cwcfg.registry_dir() except ConfigurationError, ex: print 'No application available:', ex print @@ -210,14 +212,14 @@ if instances: print 'Available applications (%s):' % regdir for appid in instances: - modes = CubicWebConfiguration.possible_configurations(appid) + modes = cwcfg.possible_configurations(appid) if not modes: print '* %s (BROKEN application, no configuration found)' % appid continue print '* %s (%s)' % (appid, ', '.join(modes)) try: - config = CubicWebConfiguration.config_for(appid, modes[0]) - except Exception, exc: + config = cwcfg.config_for(appid, modes[0]) + except Exception, exc: print ' (BROKEN application, %s)' % exc continue else: @@ -259,7 +261,7 @@ } ), ) - + def run(self, args): """run the command with its specific arguments""" from logilab.common.textutils import get_csv @@ -267,19 +269,19 @@ cubes = get_csv(pop_arg(args, 1)) appid = pop_arg(args) # get the configuration and helper - CubicWebConfiguration.creating = True - config = CubicWebConfiguration.config_for(appid, configname) + cwcfg.creating = True + config = cwcfg.config_for(appid, configname) config.set_language = False config.init_cubes(config.expand_cubes(cubes)) helper = self.config_helper(config) # check the cube exists try: - templdirs = [CubicWebConfiguration.cube_dir(cube) + templdirs = [cwcfg.cube_dir(cube) for cube in cubes] except ConfigurationError, ex: print ex print '\navailable cubes:', - print ', '.join(CubicWebConfiguration.available_cubes()) + print ', '.join(cwcfg.available_cubes()) return # create the registry directory for this application create_dir(config.apphome) @@ -295,7 +297,6 @@ # write down configuration config.save() # handle i18n files structure - # XXX currently available languages are guessed from translations found # in the first cube given from cubicweb.common import i18n langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))] @@ -322,21 +323,21 @@ print helper.postcreate() - + class DeleteApplicationCommand(Command): """Delete an application. Will remove application's files and unregister it. """ name = 'delete' arguments = '' - + options = () def run(self, args): """run the command with its specific arguments""" appid = pop_arg(args, msg="No application specified !") - configs = [CubicWebConfiguration.config_for(appid, configname) - for configname in CubicWebConfiguration.possible_configurations(appid)] + configs = [cwcfg.config_for(appid, configname) + for configname in cwcfg.possible_configurations(appid)] if not configs: raise ExecutionError('unable to guess configuration for %s' % appid) for config in configs: @@ -360,7 +361,7 @@ class StartApplicationCommand(ApplicationCommand): """Start the given applications. If no application is given, start them all. - + ... identifiers of the applications to start. If no application is given, start them all. @@ -389,7 +390,7 @@ # without all options defined debug = self.get('debug') force = self.get('force') - config = CubicWebConfiguration.config_for(appid) + config = cwcfg.config_for(appid) if self.get('profile'): config.global_set_option('profile', self.config.profile) helper = self.config_helper(config, cmdname='start') @@ -413,22 +414,22 @@ class StopApplicationCommand(ApplicationCommand): """Stop the given applications. - + ... identifiers of the applications to stop. If no application is given, stop them all. """ name = 'stop' actionverb = 'stopped' - + def ordered_instances(self): instances = super(StopApplicationCommand, self).ordered_instances() instances.reverse() return instances - + def stop_application(self, appid): """stop the application's server""" - config = CubicWebConfiguration.config_for(appid) + config = cwcfg.config_for(appid) helper = self.config_helper(config, cmdname='stop') helper.poststop() # do this anyway pidf = config['pid-file'] @@ -459,12 +460,12 @@ # already removed by twistd pass print 'application %s stopped' % appid - + class RestartApplicationCommand(StartApplicationCommand, StopApplicationCommand): """Restart the given applications. - + ... identifiers of the applications to restart. If no application is given, restart them all. @@ -473,7 +474,7 @@ actionverb = 'restarted' def run_args(self, args, askconfirm): - regdir = CubicWebConfiguration.registry_dir() + regdir = cwcfg.registry_dir() if not isfile(join(regdir, 'startorder')) or len(args) <= 1: # no specific startorder super(RestartApplicationCommand, self).run_args(args, askconfirm) @@ -496,30 +497,30 @@ status = system('%s %s' % (forkcmd, appid)) if status: sys.exit(status) - + def restart_application(self, appid): self.stop_application(appid) if self.start_application(appid): print 'application %s %s' % (appid, self.actionverb) - + class ReloadConfigurationCommand(RestartApplicationCommand): """Reload the given applications. This command is equivalent to a restart for now. - + ... identifiers of the applications to reload. If no application is given, reload them all. """ name = 'reload' - + def reload_application(self, appid): self.restart_application(appid) - + class StatusCommand(ApplicationCommand): """Display status information about the given applications. - + ... identifiers of the applications to status. If no application is given, get status information about all registered applications. @@ -527,10 +528,11 @@ name = 'status' options = () - def status_application(self, appid): + @staticmethod + def status_application(appid): """print running status information for an application""" - for mode in CubicWebConfiguration.possible_configurations(appid): - config = CubicWebConfiguration.config_for(appid, mode) + for mode in cwcfg.possible_configurations(appid): + config = cwcfg.config_for(appid, mode) print '[%s-%s]' % (appid, mode), try: pidf = config['pid-file'] @@ -574,7 +576,7 @@ {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z', 'default': None, 'help': 'force migration from the indicated cubicweb version.'}), - + ('fs-only', {'short': 's', 'action' : 'store_true', 'default': False, @@ -584,13 +586,13 @@ {'short': 'n', 'action' : 'store_true', 'default': False, 'help': 'don\'t try to stop application before migration and to restart it after.'}), - + ('verbosity', {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', 'default': 1, 'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \ for everything."}), - + ('backup-db', {'short': 'b', 'type' : 'yn', 'metavar': '', 'default': None, @@ -611,10 +613,10 @@ def ordered_instances(self): # need this since mro return StopApplicationCommand implementation return ApplicationCommand.ordered_instances(self) - + def upgrade_application(self, appid): from logilab.common.changelog import Version - config = CubicWebConfiguration.config_for(appid) + config = cwcfg.config_for(appid) config.creating = True # notice we're not starting the server config.verbosity = self.config.verbosity try: @@ -644,7 +646,7 @@ continue if installedversion > applversion: toupgrade.append( (cube, applversion, installedversion) ) - cubicwebversion = config.cubicweb_version() + cubicwebversion = config.cubicweb_version() if self.config.force_cubicweb_version: applcubicwebversion = Version(self.config.force_cubicweb_version) vcconf['cubicweb'] = applcubicwebversion @@ -658,7 +660,7 @@ for cube, fromversion, toversion in toupgrade: print '**** %s migration %s -> %s' % (cube, fromversion, toversion) # only stop once we're sure we have something to do - if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop): + if not (cwcfg.mode == 'dev' or self.config.nostartstop): self.stop_application(appid) # run cubicweb/componants migration scripts mih.migrate(vcconf, reversed(toupgrade), self.config) @@ -667,10 +669,9 @@ # handle i18n upgrade: # * install new languages # * recompile catalogs - # XXX currently available languages are guessed from translations found # in the first componant given from cubicweb.common import i18n - templdir = CubicWebConfiguration.cube_dir(config.cubes()[0]) + templdir = cwcfg.cube_dir(config.cubes()[0]) langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))] errors = config.i18ncompile(langs) if errors: @@ -683,7 +684,7 @@ mih.shutdown() print print 'application migrated' - if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop): + if not (cwcfg.mode == 'dev' or self.config.nostartstop): self.start_application(appid) print @@ -706,7 +707,7 @@ 'help': 'only connect to the system source when the instance is ' 'using multiple sources. You can\'t use this option and the ' '--ext-sources option at the same time.'}), - + ('ext-sources', {'short': 'E', 'type' : 'csv', 'metavar': '', 'default': None, @@ -715,11 +716,11 @@ will connect to all defined sources. If 'migration' is given, appropriate \ sources for migration will be automatically selected.", }), - + ) def run(self, args): appid = pop_arg(args, 99, msg="No application specified !") - config = CubicWebConfiguration.config_for(appid) + config = cwcfg.config_for(appid) if self.config.ext_sources: assert not self.config.system_only sources = self.config.ext_sources @@ -733,21 +734,22 @@ mih.scripts_session(args) else: mih.interactive_shell() - mih.shutdown() + mih.shutdown() class RecompileApplicationCatalogsCommand(ApplicationCommand): """Recompile i18n catalogs for applications. - + ... identifiers of the applications to consider. If no application is given, recompile for all registered applications. """ name = 'i18ncompile' - - def i18ncompile_application(self, appid): + + @staticmethod + def i18ncompile_application(appid): """recompile application's messages catalogs""" - config = CubicWebConfiguration.config_for(appid) + config = cwcfg.config_for(appid) try: config.bootstrap_cubes() except IOError, ex: @@ -771,10 +773,10 @@ """list available instances, useful for bash completion.""" name = 'listinstances' hidden = True - + def run(self, args): """run the command with its specific arguments""" - regdir = CubicWebConfiguration.registry_dir() + regdir = cwcfg.registry_dir() for appid in sorted(listdir(regdir)): print appid @@ -783,10 +785,10 @@ """list available componants, useful for bash completion.""" name = 'listcubes' hidden = True - + def run(self, args): """run the command with its specific arguments""" - for cube in CubicWebConfiguration.available_cubes(): + for cube in cwcfg.available_cubes(): print cube register_commands((ListCommand, @@ -803,10 +805,10 @@ ListInstancesCommand, ListCubesCommand, )) - + def run(args): """command line tool""" - CubicWebConfiguration.load_cwctl_plugins() + cwcfg.load_cwctl_plugins() main_run(args, __doc__) if __name__ == '__main__': diff -r 49075f57cf2c -r aa09e20dd8c0 cwvreg.py --- a/cwvreg.py Tue May 05 17:18:49 2009 +0200 +++ b/cwvreg.py Thu May 14 12:48:11 2009 +0200 @@ -5,8 +5,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" - -from warnings import warn +_ = unicode from logilab.common.decorators import cached, clear_cache @@ -14,19 +13,33 @@ from cubicweb import Binary, UnknownProperty, UnknownEid from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject +from cubicweb.rtags import RTAGS -_ = unicode -class DummyCursorError(Exception): pass -class RaiseCursor: - @classmethod - def execute(cls, rql, args=None, eid_key=None): - raise DummyCursorError() +def use_interfaces(obj): + """return interfaces used by the given object by searchinf for implements + selectors, with a bw compat fallback to accepts_interfaces attribute + """ + from cubicweb.selectors import implements + try: + # XXX deprecated + return sorted(obj.accepts_interfaces) + except AttributeError: + try: + impl = obj.__select__.search_selector(implements) + if impl: + return sorted(impl.expected_ifaces) + except AttributeError: + pass # old-style vobject classes with no accepts_interfaces + except: + print 'bad selector %s on %s' % (obj.__select__, obj) + raise + return () class CubicWebRegistry(VRegistry): """extend the generic VRegistry with some cubicweb specific stuff""" - + def __init__(self, config, debug=None, initlog=True): if initlog: # first init log service @@ -35,32 +48,34 @@ self.schema = None self.reset() self.initialized = False - + def items(self): return [item for item in self._registries.items() if not item[0] in ('propertydefs', 'propertyvalues')] def values(self): - return [value for key,value in self._registries.items() + return [value for key, value in self._registries.items() if not key in ('propertydefs', 'propertyvalues')] - + def reset(self): self._registries = {} self._lastmodifs = {} - # two special registries, propertydefs which care all the property definitions, and - # propertyvals which contains values for those properties + self._needs_iface = {} + # two special registries, propertydefs which care all the property + # definitions, and propertyvals which contains values for those + # properties self._registries['propertydefs'] = {} self._registries['propertyvalues'] = self.eprop_values = {} for key, propdef in self.config.eproperty_definitions(): self.register_property(key, **propdef) - + def set_schema(self, schema): """set application'schema and load application objects""" self.schema = schema clear_cache(self, 'rqlhelper') # now we can load application's web objects self.register_objects(self.config.vregistry_path()) - + def update_schema(self, schema): """update .schema attribute on registered objects, necessary for some tests @@ -72,56 +87,75 @@ for objects in regcontent.values(): for obj in objects: obj.schema = schema - - def register_objects(self, path, force_reload=None): - """overriden to handle type class cache issue""" - 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') - # remove vobjects that don't support any available interface - interfaces = set() - for classes in self.get('etypes', {}).values(): - for cls in classes: - interfaces.update(cls.__implements__) - if not self.config.cleanup_interface_sobjects: + + def register_if_interface_found(self, obj, ifaces, **kwargs): + """register an object but remove it if no entity class implements one of + the given interfaces + """ + self.register(obj, **kwargs) + if not isinstance(ifaces, (tuple, list)): + self._needs_iface[obj] = (ifaces,) + else: + self._needs_iface[obj] = ifaces + + def register(self, obj, **kwargs): + if kwargs.get('registryname', obj.__registry__) == 'etypes': + if obj.id != 'Any' and not obj.id in self.schema: + self.error('don\'t register %s, %s type not defined in the ' + 'schema', obj, obj.id) return - for registry, regcontent in self._registries.items(): - if registry in ('propertydefs', 'propertyvalues', 'etypes'): - continue - for oid, objects in regcontent.items(): - for obj in reversed(objects[:]): - if not obj in objects: - continue # obj has been kicked by a previous one - accepted = set(getattr(obj, 'accepts_interfaces', ())) - if accepted: - for accepted_iface in accepted: - for found_iface in interfaces: - if issubclass(found_iface, accepted_iface): - # consider priority if necessary - if hasattr(obj.__registerer__, 'remove_all_equivalents'): - registerer = obj.__registerer__(self, obj) - registerer.remove_all_equivalents(objects) - break - else: - self.debug('kicking vobject %s (unsupported interface)', obj) - objects.remove(obj) - # if objects is empty, remove oid from registry - if not objects: - del regcontent[oid] + kwargs['clear'] = True + super(CubicWebRegistry, self).register(obj, **kwargs) + # XXX bw compat + ifaces = use_interfaces(obj) + if ifaces: + self._needs_iface[obj] = ifaces + + 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): + self.initialization_completed() + # call vreg_initialization_completed on appobjects and 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() + # don't check rtags if we don't want to cleanup_interface_sobjects + for rtag in RTAGS: + rtag.init(self.schema, + check=self.config.cleanup_interface_sobjects) - def eid_rset(self, cursor, eid, etype=None): - """return a result set for the given eid without doing actual query - (we have the eid, we can suppose it exists and user has access to the - entity) - """ - msg = '.eid_rset is deprecated, use req.eid_rset' - warn(msg, DeprecationWarning, stacklevel=2) - try: - return cursor.req.eid_rset(eid, etype) - except AttributeError: - # cursor is a session - return cursor.eid_rset(eid, etype) - + 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() + 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 + and self.etype_class(iface) + 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.unregister(obj) + # 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): """return an entity class for the given entity type. @@ -129,6 +163,8 @@ default to a dump of the class registered for 'Any' """ etype = str(etype) + if etype == 'Any': + return self.select(self.registry_objects('etypes', 'Any'), 'Any') eschema = self.schema.eschema(etype) baseschemas = [eschema] + eschema.ancestors() # browse ancestors from most specific to most generic and @@ -136,18 +172,22 @@ for baseschema in baseschemas: btype = str(baseschema) try: - return self.select(self.registry_objects('etypes', btype), etype) + cls = self.select(self.registry_objects('etypes', btype), etype) + break except ObjectNotFound: pass - # no entity class for any of the ancestors, fallback to the default one - return self.select(self.registry_objects('etypes', 'Any'), etype) + else: + # no entity class for any of the ancestors, fallback to the default + # one + cls = self.select(self.registry_objects('etypes', 'Any'), etype) + return cls def render(self, registry, oid, req, **context): """select an object in a given registry and render it - registry: the registry's name - oid : the view to call - - req : the HTTP request + - req : the HTTP request """ objclss = self.registry_objects(registry, oid) try: @@ -155,14 +195,14 @@ except KeyError: rset = None selected = self.select(objclss, req, rset, **context) - return selected.dispatch(**context) - - def main_template(self, req, oid='main', **context): + return selected.render(**context) + + def main_template(self, req, oid='main-template', **context): """display query by calling the given template (default to main), and returning the output as a string instead of requiring the [w]rite method as argument """ - res = self.render('templates', oid, req, **context) + res = self.render('views', oid, req, **context) if isinstance(res, unicode): return res.encode(req.encoding) assert isinstance(res, str) @@ -176,7 +216,7 @@ return [x for x in sorted(self.possible_objects(registry, *args, **kwargs), key=lambda x: x.propval('order')) if x.propval('visible')] - + def possible_actions(self, req, rset, **kwargs): if rset is None: actions = self.possible_vobjects('actions', req, rset, **kwargs) @@ -186,7 +226,7 @@ for action in actions: result.setdefault(action.category, []).append(action) return result - + def possible_views(self, req, rset, **kwargs): """return an iterator on possible views for this result set @@ -204,7 +244,7 @@ except Exception: self.exception('error while trying to list possible %s views for %s', vid, rset) - + def select_box(self, oid, *args, **kwargs): """return the most specific view according to the result set""" try: @@ -218,7 +258,7 @@ return self.select_object('actions', oid, *args, **kwargs) except NoSelectableObject: return - + def select_component(self, cid, *args, **kwargs): """return the most specific component according to the result set""" try: @@ -231,7 +271,7 @@ views = self.registry_objects('views', __vid) return self.select(views, req, rset, **kwargs) - + # properties handling ##################################################### def user_property_keys(self, withsitewide=False): @@ -245,7 +285,7 @@ """register a given property""" properties = self._registries['propertydefs'] assert type in YAMS_TO_PY - properties[key] = {'type': type, 'vocabulary': vocabulary, + properties[key] = {'type': type, 'vocabulary': vocabulary, 'default': default, 'help': help, 'sitewide': sitewide} @@ -263,7 +303,7 @@ 'default': None, 'vocabulary': None, 'help': _('%s software version of the database') % soft} raise UnknownProperty('unregistered property %r' % key) - + def property_value(self, key): try: return self._registries['propertyvalues'][key] @@ -286,7 +326,7 @@ if not value in vocab: raise ValueError(_('unauthorized value')) return value - + def init_properties(self, propvalues): """init the property values registry using the given set of couple (key, value) """ @@ -302,37 +342,6 @@ self.warning('%s (you should probably delete that property ' 'from the database)', ex) - - def property_value_widget(self, propkey, req=None, **attrs): - """return widget according to key's type / vocab""" - from cubicweb.web.widgets import StaticComboBoxWidget, widget_factory - if req is None: - tr = unicode - else: - tr = req._ - try: - pdef = self.property_info(propkey) - except UnknownProperty, ex: - self.warning('%s (you should probably delete that property ' - 'from the database)', ex) - return widget_factory(self, 'EProperty', self.schema['value'], 'String', - description=u'', **attrs) - req.form['value'] = pdef['default'] # XXX hack to pass the default value - vocab = pdef['vocabulary'] - if vocab is not None: - if callable(vocab): - # list() just in case its a generator function - vocabfunc = lambda **kwargs: list(vocab(propkey, req)) - else: - vocabfunc = lambda **kwargs: vocab - w = StaticComboBoxWidget(self, 'EProperty', self.schema['value'], 'String', - vocabfunc=vocabfunc, description=tr(pdef['help']), - **attrs) - else: - w = widget_factory(self, 'EProperty', self.schema['value'], pdef['type'], - description=tr(pdef['help']), **attrs) - return w - def parse(self, session, rql, args=None): rqlst = self.rqlhelper.parse(rql) def type_from_eid(eid, session=session): @@ -377,8 +386,8 @@ vobject.schema = self.schema vobject.config = self.config return super(MulCnxCubicWebRegistry, self).select(vobjects, *args, **kwargs) - -from mx.DateTime import DateTime, Time, DateTimeDelta + +from datetime import datetime, date, time, timedelta YAMS_TO_PY = { 'Boolean': bool, @@ -387,9 +396,9 @@ 'Bytes': Binary, 'Int': int, 'Float': float, - 'Date': DateTime, - 'Datetime': DateTime, - 'Time': Time, - 'Interval': DateTimeDelta, + 'Date': date, + 'Datetime': datetime, + 'Time': time, + 'Interval': timedelta, } diff -r 49075f57cf2c -r aa09e20dd8c0 dbapi.py --- a/dbapi.py Tue May 05 17:18:49 2009 +0200 +++ b/dbapi.py Thu May 14 12:48:11 2009 +0200 @@ -5,18 +5,18 @@ (most parts of this document are reported here in docstrings) :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" -from logging import getLogger, StreamHandler +from logging import getLogger from time import time, clock 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 """ @@ -42,7 +42,7 @@ from cubicweb.server.repository import Repository return Repository(config, vreg=vreg) else: # method == 'pyro' - from Pyro import core, naming, config as pyroconfig + from Pyro import core, naming from Pyro.errors import NamingError, ProtocolError core.initClient(banner=0) nsid = ':%s.%s' % (config['pyro-ns-group'], database) @@ -54,16 +54,16 @@ except ProtocolError: raise ConnectionError('Could not connect to the Pyro name server ' '(host: %s:%i)' % (nshost, nsport)) - except NamingError, ex: + except NamingError: raise ConnectionError('Could not get repository for %s ' '(not registered in Pyro), ' '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,41 +333,37 @@ 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""" - try: - self._repo.check_session(self.sessionid) - except AttributeError: - # XXX backward compat for repository running cubicweb < 2.48.3 - self._repo.session_data(self.sessionid) + self._repo.check_session(self.sessionid) 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 @@ -377,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: @@ -418,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: @@ -434,9 +430,9 @@ eid, login, groups, properties = self._repo.user_info(self.sessionid, props) if req is None: req = self.request() - rset = req.eid_rset(eid, 'EUser') - user = self.vreg.etype_class('EUser')(req, rset, row=0, groups=groups, - properties=properties) + rset = req.eid_rset(eid, 'CWUser') + user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups, + properties=properties) user['login'] = login # cache login return user @@ -447,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 @@ -469,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. """ @@ -480,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 @@ -514,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. @@ -526,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(). @@ -539,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) @@ -547,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. """ @@ -579,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: @@ -610,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. """ @@ -620,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 @@ -648,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. """ @@ -669,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 49075f57cf2c -r aa09e20dd8c0 debian/changelog --- a/debian/changelog Tue May 05 17:18:49 2009 +0200 +++ b/debian/changelog Thu May 14 12:48:11 2009 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.2.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Thu, 14 May 2009 12:31:06 +0200 + cubicweb (3.1.4-1) unstable; urgency=low * new upstream release diff -r 49075f57cf2c -r aa09e20dd8c0 debian/control --- a/debian/control Tue May 05 17:18:49 2009 +0200 +++ b/debian/control Thu May 14 12:48:11 2009 +0200 @@ -10,7 +10,6 @@ Homepage: http://www.cubicweb.org XS-Python-Version: >= 2.4, << 2.6 - Package: cubicweb Architecture: all XB-Python-Version: ${python:Versions} @@ -61,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. . @@ -76,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.38.1), python-yams (>= 0.20.2), python-rql (>= 0.20.2), python-simplejson (>= 1.3) -Recommends: python-psyco +Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.40.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 49075f57cf2c -r aa09e20dd8c0 debian/cubicweb-ctl.postinst --- a/debian/cubicweb-ctl.postinst Tue May 05 17:18:49 2009 +0200 +++ b/debian/cubicweb-ctl.postinst Thu May 14 12:48:11 2009 +0200 @@ -13,25 +13,29 @@ if [ "$1" = configure ]; then # XXX bw compat: erudi -> cubicweb migration if [ -e "/etc/erudi.d/" ]; then - mv /etc/erudi.d/* /etc/cubicweb.d/ - echo 'moved /etc/erudi.d/* to /etc/cubicweb.d/' - sed -i s/ginco/cubicweb/g /etc/*/*.py - sed -i s/erudi/cubicweb/ */*.conf + mv /etc/erudi.d/* /etc/cubicweb.d/ && ( + echo 'moved /etc/erudi.d/* to /etc/cubicweb.d/' + sed -i s/ginco/cubicweb/g /etc/*/*.py + sed -i s/erudi/cubicweb/ */*.conf + ) || true # empty dir fi if [ -e "/var/log/erudi/" ]; then - mv /var/log/erudi/* /var/log/cubicweb/ - echo 'moved /var/log/erudi/* to /var/log/cubicweb/' + mv /var/log/erudi/* /var/log/cubicweb/ && ( + echo 'moved /var/log/erudi/* to /var/log/cubicweb/' + ) || true # empty dir fi if [ -e "/var/lib/erudi/backup" ]; then - mv /var/lib/erudi/backup/* /var/lib/cubicweb/backup/ - echo 'moved /var/lib/erudi/backup/* to /var/lib/cubicweb/backup/' + mv /var/lib/erudi/backup/* /var/lib/cubicweb/backup/ && ( + echo 'moved /var/lib/erudi/backup/* to /var/lib/cubicweb/backup/' + ) || true # empty dir fi if [ -e "/var/lib/erudi/instances" ]; then - mv /var/lib/erudi/instances/* /var/lib/cubicweb/instances/ - echo 'moved /var/lib/erudi/instances/* to /var/lib/cubicweb/instances/' + mv /var/lib/erudi/instances/* /var/lib/cubicweb/instances/ && ( + echo 'moved /var/lib/erudi/instances/* to /var/lib/cubicweb/instances/' + ) || true # empty dir fi fi - + #DEBHELPER# - + exit 0 diff -r 49075f57cf2c -r aa09e20dd8c0 debian/cubicweb-web.postinst --- a/debian/cubicweb-web.postinst Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -#! /bin/sh -e - -ln -sf /usr/share/fckeditor/fckeditor.js /usr/share/cubicweb/cubes/shared/data - -#DEBHELPER# - -exit 0 diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/__init__.py --- a/devtools/__init__.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/__init__.py Thu May 14 12:48:11 2009 +0200 @@ -1,19 +1,19 @@ """Test tools for cubicweb :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" import os import logging +from datetime import timedelta from os.path import (abspath, join, exists, basename, dirname, normpath, split, isfile, isabs) -from mx.DateTime import strptime, DateTimeDelta - from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError +from cubicweb.utils import strptime from cubicweb.toolsutils import read_config from cubicweb.cwconfig import CubicWebConfiguration, merge_options from cubicweb.server.serverconfig import ServerConfiguration @@ -59,10 +59,10 @@ 'group': 'main', 'inputlevel': 1, }), )) - + if not os.environ.get('APYCOT_ROOT'): REGISTRY_DIR = normpath(join(CW_SOFTWARE_ROOT, '../cubes')) - + def __init__(self, appid, log_threshold=logging.CRITICAL+10): ServerConfiguration.__init__(self, appid) self.global_set_option('log-file', None) @@ -71,7 +71,7 @@ self.load_cwctl_plugins() anonymous_user = TwistedConfiguration.anonymous_user.im_func - + @property def apphome(self): if exists(self.appid): @@ -79,7 +79,7 @@ # application cube test return abspath('..') appdatahome = apphome - + def main_config_file(self): """return application's control configuration file""" return join(self.apphome, '%s.conf' % self.name) @@ -116,7 +116,7 @@ if not sources: sources = DEFAULT_SOURCES return sources - + def load_defaults(self): super(TestServerConfiguration, self).load_defaults() # note: don't call global set option here, OptionManager may not yet be initialized @@ -146,25 +146,25 @@ def available_languages(self, *args): return ('en', 'fr', 'de') - + def ext_resources_file(self): """return application's external resources file""" return join(self.apphome, 'data', 'external_resources') - + def pyro_enabled(self): # but export PYRO_MULTITHREAD=0 or you get problems with sqlite and threads return True class ApptestConfiguration(BaseApptestConfiguration): - + def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None): BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold) self.init_repository = sourcefile is None self.sourcefile = sourcefile import re self.global_set_option('embed-allowed', re.compile('.*')) - + class RealDatabaseConfiguration(ApptestConfiguration): init_repository = False @@ -180,7 +180,7 @@ 'password': u'gingkow', }, } - + def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None): ApptestConfiguration.__init__(self, appid) self.init_repository = False @@ -191,7 +191,7 @@ By default, we run tests with the sqlite DB backend. One may use its own configuration by just creating a 'sources' file in the test directory from wich tests are - launched. + launched. """ self._sources = self.sourcesdef return self._sources @@ -220,11 +220,11 @@ """ return type('MyRealDBConfig', (RealDatabaseConfiguration,), {'sourcesdef': read_config(filename)}) - + class LivetestConfiguration(BaseApptestConfiguration): init_repository = False - + def __init__(self, cube=None, sourcefile=None, pyro_name=None, log_threshold=logging.CRITICAL): TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold) @@ -254,7 +254,7 @@ return False CubicWebConfiguration.cls_adjust_sys_path() - + def install_sqlite_path(querier): """This patch hotfixes the following sqlite bug : - http://www.sqlite.org/cvstrac/tktview?tn=1327,33 @@ -271,6 +271,7 @@ for cellindex, (value, vtype) in enumerate(zip(row, rowdesc)): if vtype in ('Date', 'Datetime') and type(value) is unicode: found_date = True + value = value.rsplit('.', 1)[0] try: row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S') except: @@ -284,7 +285,7 @@ row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S') if vtype == 'Interval' and type(value) is int: found_date = True - row[cellindex] = DateTimeDelta(0, 0, 0, value) + row[cellindex] = timedelta(0, value, 0) # XXX value is in number of seconds? if not found_date: break return rset @@ -330,7 +331,7 @@ os.remove('%s-cube' % dbfile) except OSError: pass - + def init_test_database_sqlite(config, source, vreg=None): """initialize a fresh sqlite databse used for testing purpose""" import shutil diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/_apptest.py --- a/devtools/_apptest.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/_apptest.py Thu May 14 12:48:11 2009 +0200 @@ -1,7 +1,7 @@ """Hidden internals for the devtools.apptest module :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" @@ -20,11 +20,11 @@ from cubicweb.devtools import ApptestConfiguration, init_test_database from cubicweb.devtools.fake import FakeRequest - -SYSTEM_ENTITIES = ('EGroup', 'EUser', - 'EFRDef', 'ENFRDef', - 'EConstraint', 'EConstraintType', 'EProperty', - 'EEType', 'ERType', + +SYSTEM_ENTITIES = ('CWGroup', 'CWUser', + 'CWAttribute', 'CWRelation', + 'CWConstraint', 'CWConstraintType', 'CWProperty', + 'CWEType', 'CWRType', 'State', 'Transition', 'TrInfo', 'RQLExpression', ) @@ -35,7 +35,7 @@ 'is', 'is_instance_of', 'owned_by', 'created_by', 'specializes', # workflow related 'state_of', 'transition_of', 'initial_state', 'allowed_transition', - 'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state', + 'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state', 'condition', # permission 'in_group', 'require_group', 'require_permission', @@ -46,11 +46,11 @@ 'relation_type', 'from_entity', 'to_entity', 'constrained_by', 'cstrtype', 'widget', # deducted from other relations - 'primary_email', + 'primary_email', ) def unprotected_entities(app_schema, strict=False): - """returned a Set of each non final entity type, excluding EGroup, and EUser... + """returned a Set of each non final entity type, excluding CWGroup, and CWUser... """ if strict: protected_entities = yams.schema.BASE_TYPES @@ -58,16 +58,17 @@ protected_entities = yams.schema.BASE_TYPES.union(set(SYSTEM_ENTITIES)) entities = set(app_schema.entities()) return entities - protected_entities - + def ignore_relations(*relations): + global SYSTEM_RELATIONS SYSTEM_RELATIONS += relations class TestEnvironment(object): """TestEnvironment defines a context (e.g. a config + a given connection) in which the tests are executed """ - + def __init__(self, appid, reporter=None, verbose=False, configcls=ApptestConfiguration, requestcls=FakeRequest): config = configcls(appid) @@ -83,14 +84,13 @@ self.restore_database() if verbose: print "init done" - login = source['db-user'] config.repository = lambda x=None: self.repo self.app = CubicWebPublisher(config, vreg=vreg) self.verbose = verbose schema = self.vreg.schema # else we may run into problems since email address are ususally share in app tests # XXX should not be necessary anymore - schema.rschema('primary_email').set_rproperty('EUser', 'EmailAddress', 'composite', False) + schema.rschema('primary_email').set_rproperty('CWUser', 'EmailAddress', 'composite', False) self.deletable_entities = unprotected_entities(schema) def restore_database(self): @@ -114,12 +114,12 @@ self.cnx.vreg = self.vreg self.cnx.login = source['db-user'] self.cnx.password = source['db-password'] - + def create_user(self, login, groups=('users',), req=None): req = req or self.create_request() cursor = self._orig_cnx.cursor(req) - rset = cursor.execute('INSERT EUser X: X login %(login)s, X upassword %(passwd)s,' + rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s,' 'X in_state S WHERE S name "activated"', {'login': unicode(login), 'passwd': login.encode('utf8')}) user = rset.get_entity(0, 0) @@ -140,7 +140,7 @@ if login == self.vreg.config.anonymous_user()[0]: self.cnx.anonymous_connection = True return self.cnx - + def restore_connection(self): if not self.cnx is self._orig_cnx: try: @@ -157,7 +157,7 @@ """ req = req or self.create_request(rql=rql) return self.cnx.cursor(req).execute(unicode(rql), args, eidkey) - + def create_request(self, rql=None, **kwargs): """executes , builds a resultset, and returns a couple (rset, req) where req is a FakeRequest @@ -167,14 +167,14 @@ req = self.requestcls(self.vreg, form=kwargs) req.set_connection(self.cnx) return req - + def get_rset_and_req(self, rql, optional_args=None, args=None, eidkey=None): """executes , builds a resultset, and returns a couple (rset, req) where req is a FakeRequest """ return (self.execute(rql, args, eidkey), self.create_request(rql=rql, **optional_args or {})) - + def check_view(self, rql, vid, optional_args, template='main'): """checks if vreg.view() raises an exception in this environment @@ -183,7 +183,7 @@ """ return self.call_view(vid, rql, template=template, optional_args=optional_args) - + def call_view(self, vid, rql, template='main', optional_args=None): """shortcut for self.vreg.view()""" assert template @@ -227,23 +227,22 @@ yield action class ExistingTestEnvironment(TestEnvironment): - + def __init__(self, appid, sourcefile, verbose=False): config = ApptestConfiguration(appid, sourcefile=sourcefile) if verbose: print "init test database ..." source = config.sources()['system'] self.vreg = CubicWebRegistry(config) - repo, self.cnx = init_test_database(driver=source['db-driver'], - vreg=self.vreg) + self.cnx = init_test_database(driver=source['db-driver'], + vreg=self.vreg)[1] if verbose: - print "init done" + print "init done" self.app = CubicWebPublisher(config, vreg=self.vreg) self.verbose = verbose # this is done when the publisher is opening a connection self.cnx.vreg = self.vreg - login = source['db-user'] - + def setup(self, config=None): """config is passed by TestSuite but is ignored in this environment""" cursor = self.cnx.cursor() @@ -255,4 +254,3 @@ cursor.execute('DELETE Any X WHERE X eid > %(x)s', {'x' : self.last_eid}, eid_key='x') print "cleaning done" self.cnx.commit() - diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/apptest.py --- a/devtools/apptest.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/apptest.py Thu May 14 12:48:11 2009 +0200 @@ -1,7 +1,7 @@ """This module provides misc utilities to test applications :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" @@ -32,11 +32,11 @@ @property def message(self): return message_from_string(self.msg) - + def __repr__(self): return '' % (','.join(self.recipients), self.message.get('Subject')) - + class MockSMTP: def __init__(self, server, port): pass @@ -100,7 +100,7 @@ env = None configcls = ApptestConfiguration requestcls = FakeRequest - + # user / session management ############################################### def user(self, req=None): @@ -118,13 +118,13 @@ def restore_connection(self): self.env.restore_connection() - + # db api ################################################################## @nocoverage def cursor(self, req=None): return self.env.cnx.cursor(req or self.request()) - + @nocoverage def execute(self, *args, **kwargs): return self.env.execute(*args, **kwargs) @@ -132,19 +132,19 @@ @nocoverage def commit(self): self.env.cnx.commit() - + @nocoverage def rollback(self): try: self.env.cnx.rollback() except ProgrammingError: pass - + # other utilities ######################################################### def set_debug(self, debugmode): from cubicweb.server import set_debug set_debug(debugmode) - + @property def config(self): return self.vreg.config @@ -152,7 +152,7 @@ def session(self): """return current server side session (using default manager account)""" return self.env.repo._sessions[self.env.cnx.sessionid] - + def request(self, *args, **kwargs): """return a web interface request""" return self.env.create_request(*args, **kwargs) @@ -160,16 +160,16 @@ @nocoverage def rset_and_req(self, *args, **kwargs): return self.env.get_rset_and_req(*args, **kwargs) - + def entity(self, rql, args=None, eidkey=None, req=None): return self.execute(rql, args, eidkey, req=req).get_entity(0, 0) - + def etype_instance(self, etype, req=None): req = req or self.request() e = self.env.vreg.etype_class(etype)(req, None, None) e.eid = None return e - + def add_entity(self, etype, **kwargs): rql = ['INSERT %s X' % etype] @@ -185,15 +185,15 @@ sub_rql = [] for key, value in kwargs.iteritems(): # entities - if hasattr(value, 'eid'): + if hasattr(value, 'eid'): new_value = "%s__" % key.upper() - + entities[new_value] = value.eid rql_args[new_value] = value.eid - + sub_rql.append("X %s %s" % (key, new_value)) # final attributes - else: + else: sub_rql.append('X %s %%(%s)s' % (key, key)) rql_args[key] = value rql.append(', '.join(sub_rql)) @@ -215,8 +215,8 @@ self.vreg.config.global_set_option(optname, value) def pviews(self, req, rset): - return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset)) - + return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset)) + def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')): return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) if a.category not in skipcategories] @@ -224,7 +224,7 @@ def pactions_by_cats(self, req, rset, categories=('addrelated',)): return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) if a.category in categories] - + paddrelactions = deprecated_function(pactions_by_cats) def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')): @@ -234,17 +234,17 @@ res.setdefault(a.category, []).append(a.__class__) return res - + def remote_call(self, fname, *args): """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 # default test setup and teardown ######################################### - + def setup_database(self): pass @@ -264,7 +264,7 @@ self.setup_database() self.commit() MAILBOX[:] = [] # reset mailbox - + @nocoverage def tearDown(self): self.rollback() @@ -355,13 +355,13 @@ """ __metaclass__ = autorepo repo_config = None # set a particular config instance if necessary - + # user / session management ############################################### def create_user(self, user, groups=('users',), password=None, commit=True): if password is None: password = user - eid = self.execute('INSERT EUser X: X login %(x)s, X upassword %(p)s,' + eid = self.execute('INSERT CWUser X: X login %(x)s, X upassword %(p)s,' 'X in_state S WHERE S name "activated"', {'x': unicode(user), 'p': password})[0][0] groups = ','.join(repr(group) for group in groups) @@ -369,9 +369,9 @@ {'x': eid}) if commit: self.commit() - self.session.reset_pool() + self.session.reset_pool() return eid - + def login(self, login, password=None): cnx = repo_connect(self.repo, unicode(login), password or login, ConnectionProperties('inmemory')) @@ -380,7 +380,7 @@ def current_session(self): return self.repo._sessions[self.cnxs[-1].sessionid] - + def restore_connection(self): assert len(self.cnxs) == 1, self.cnxs cnx = self.cnxs.pop() @@ -388,7 +388,7 @@ cnx.close() except Exception, ex: print "exception occured while closing connection", ex - + # db api ################################################################## def execute(self, rql, args=None, eid_key=None): @@ -400,27 +400,27 @@ # application entities for convenience self.session.set_pool() return rset - + def commit(self): self.__commit(self.cnxid) - self.session.set_pool() - + self.session.set_pool() + def rollback(self): self.__rollback(self.cnxid) - self.session.set_pool() - + self.session.set_pool() + def close(self): self.__close(self.cnxid) # other utilities ######################################################### - + def set_debug(self, debugmode): from cubicweb.server import set_debug set_debug(debugmode) - + def set_option(self, optname, value): self.vreg.config.global_set_option(optname, value) - + def add_entity(self, etype, **kwargs): restrictions = ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs) rql = 'INSERT %s X' % etype @@ -434,7 +434,7 @@ user = unicode(config.sources()['system']['db-user']) passwd = config.sources()['system']['db-password'] return user, passwd - + def close_connections(self): for cnx in self.cnxs: try: @@ -446,10 +446,10 @@ pactions = EnvBasedTC.pactions.im_func pactionsdict = EnvBasedTC.pactionsdict.im_func - + # default test setup and teardown ######################################### copy_schema = False - + def _prepare(self): MAILBOX[:] = [] # reset mailbox if hasattr(self, 'cnxid'): @@ -494,18 +494,15 @@ @property def schema(self): return self.repo.schema - + def setUp(self): self._prepare() self.session.set_pool() self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0] - #self.maxeid = self.execute('Any MAX(X)') - - def tearDown(self, close=True): + + def tearDown(self): self.close_connections() self.rollback() self.session.unsafe_execute('DELETE Any X WHERE X eid > %(x)s', {'x': self.maxeid}) self.commit() - #if close: - # self.close() - + diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/cwtwill.py --- a/devtools/cwtwill.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/cwtwill.py Thu May 14 12:48:11 2009 +0200 @@ -1,4 +1,9 @@ -"""cubicweb extensions for twill""" +"""cubicweb extensions for twill + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" import re from urllib import quote @@ -24,9 +29,9 @@ # if url is specified linkurl must match if url and linkurl != url: continue - return + return raise AssertionError('link %s (%s) not found' % (text, url)) - + def view(rql, vid=''): """ @@ -56,7 +61,7 @@ twc.go('view?rql=%s&vid=edition' % quote(rql)) - + def setvalue(formname, fieldname, value): """ @@ -104,5 +109,5 @@ browser._browser.form = form browser.submit(submit_button) - + # missing actions: delete, copy, changeview diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/devctl.py --- a/devtools/devctl.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/devctl.py Thu May 14 12:48:11 2009 +0200 @@ -2,24 +2,25 @@ cubes development :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" import sys -from os import walk, mkdir, chdir, listdir, getcwd +from datetime import datetime +from os import mkdir, chdir from os.path import join, exists, abspath, basename, normpath, split, isdir from logilab.common import STD_BLACKLIST from logilab.common.modutils import get_module_files from logilab.common.textutils import get_csv +from logilab.common.clcommands import register_commands -from cubicweb import CW_SOFTWARE_ROOT as BASEDIR +from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage from cubicweb.__pkginfo__ import version as cubicwebversion -from cubicweb import BadCommandUsage -from cubicweb.toolsutils import Command, register_commands, confirm, copy_skeleton +from cubicweb.toolsutils import Command, confirm, copy_skeleton from cubicweb.web.webconfig import WebConfiguration from cubicweb.server.serverconfig import ServerConfiguration @@ -35,11 +36,11 @@ if cube is None: self._cubes = () else: - self._cubes = self.expand_cubes(self.my_cubes(cube)) + self._cubes = self.reorder_cubes(self.expand_cubes(self.my_cubes(cube))) def my_cubes(self, cube): return (cube,) + self.cube_dependencies(cube) + self.cube_recommends(cube) - + @property def apphome(self): return None @@ -76,7 +77,12 @@ if mod.__file__.startswith(path): del sys.modules[name] break - + # fresh rtags + from cubicweb import rtags + from cubicweb.web import uicfg + rtags.RTAGS[:] = [] + reload(uicfg) + def generate_schema_pot(w, cubedir=None): """generate a pot file with schema specific i18n messages @@ -85,30 +91,31 @@ """ from cubicweb.cwvreg import CubicWebRegistry cube = cubedir and split(cubedir)[-1] - config = DevDepConfiguration(cube) - cleanup_sys_modules(config) + libconfig = DevDepConfiguration(cube) + libconfig.cleanup_interface_sobjects = False + cleanup_sys_modules(libconfig) if cubedir: - libschema = config.load_schema() config = DevCubeConfiguration(cube) - schema = config.load_schema() + config.cleanup_interface_sobjects = False else: - schema = config.load_schema() - libschema = None - config.cleanup_interface_sobjects = False + config = libconfig + libconfig = None + schema = config.load_schema(remove_unused_rtypes=False) vreg = CubicWebRegistry(config) # set_schema triggers objects registrations vreg.set_schema(schema) w(DEFAULT_POT_HEAD) - _generate_schema_pot(w, vreg, schema, libschema=libschema, cube=cube) - -def _generate_schema_pot(w, vreg, schema, libschema=None, cube=None): - from mx.DateTime import now + _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube) + + +def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None): from cubicweb.common.i18n import add_msg - w('# schema pot file, generated on %s\n' % now().strftime('%Y-%m-%d %H:%M:%S')) + w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S')) w('# \n') w('# singular and plural forms for each entity type\n') w('\n') - if libschema is not None: + if libconfig is not None: + libschema = libconfig.load_schema(remove_unused_rtypes=False) entities = [e for e in schema.entities() if not e in libschema] else: entities = schema.entities() @@ -128,7 +135,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() @@ -143,46 +150,70 @@ add_msg(w, rschema.description) w('# add related box generated message\n') w('\n') + actionbox = vreg['boxes']['edit_box'][0] for eschema in schema.entities(): if eschema.is_final(): continue - entity = vreg.etype_class(eschema)(None, None) - for x, rschemas in (('subject', eschema.subject_relations()), + for role, rschemas in (('subject', eschema.subject_relations()), ('object', eschema.object_relations())): for rschema in rschemas: if rschema.is_final(): continue - for teschema in rschema.targets(eschema, x): - if defined_in_library(libschema, eschema, rschema, teschema, x): - continue - if entity.relation_mode(rschema.type, teschema.type, x) == 'create': - if x == 'subject': - label = 'add %s %s %s %s' % (eschema, rschema, teschema, x) - label2 = "creating %s (%s %%(linkto)s %s %s)" % (teschema, eschema, rschema, teschema) + if libconfig is not None: + librschema = libschema.get(rschema) + for teschema in rschema.targets(eschema, role): + if libconfig is not None and librschema is not None: + if role == 'subject': + subjtype, objtype = eschema, teschema else: - label = 'add %s %s %s %s' % (teschema, rschema, eschema, x) - label2 = "creating %s (%s %s %s %%(linkto)s)" % (teschema, teschema, rschema, eschema) + subjtype, objtype = teschema, eschema + if librschema.has_rdef(subjtype, objtype): + continue + if actionbox.appearsin_addmenu.etype_get(eschema, rschema, + role, teschema): + if role == 'subject': + label = 'add %s %s %s %s' % (eschema, rschema, + teschema, role) + label2 = "creating %s (%s %%(linkto)s %s %s)" % ( + teschema, eschema, rschema, teschema) + else: + label = 'add %s %s %s %s' % (teschema, rschema, + eschema, role) + label2 = "creating %s (%s %s %s %%(linkto)s)" % ( + teschema, teschema, rschema, eschema) add_msg(w, label) add_msg(w, label2) - cube = (cube and 'cubes.%s.' % cube or 'cubicweb.') + #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.') done = set() + if libconfig is not None: + from cubicweb.cwvreg import CubicWebRegistry + libvreg = CubicWebRegistry(libconfig) + libvreg.set_schema(libschema) # trigger objects registration + # prefill done set + list(_iter_vreg_objids(libvreg, done)) + for objid in _iter_vreg_objids(vreg, done): + add_msg(w, '%s_description' % objid) + add_msg(w, objid) + +def _iter_vreg_objids(vreg, done, prefix=None): for reg, objdict in vreg.items(): for objects in objdict.values(): for obj in objects: objid = '%s_%s' % (reg, obj.id) if objid in done: - continue - if obj.__module__.startswith(cube) and obj.property_defs: - add_msg(w, '%s_description' % objid) - add_msg(w, objid) + break + if obj.property_defs: + yield objid done.add(objid) + break - -def defined_in_library(libschema, etype, rtype, tetype, x): - """return true if the given relation definition exists in cubicweb's library""" + +def defined_in_library(etype, rtype, tetype, role): + """return true if the given relation definition exists in cubicweb's library + """ if libschema is None: return False - if x == 'subject': + if role == 'subject': subjtype, objtype = etype, tetype else: subjtype, objtype = tetype, etype @@ -211,7 +242,7 @@ class UpdateCubicWebCatalogCommand(Command): """Update i18n catalogs for cubicweb library. - + It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those files to add translations of newly added messages. """ @@ -251,8 +282,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)) @@ -281,7 +316,7 @@ """ name = 'i18nupdate' arguments = '[...]' - + def run(self, args): """run the command with its specific arguments""" if args: @@ -291,74 +326,86 @@ cubes = [cubepath for cubepath in cubes if exists(join(cubepath, 'i18n'))] update_cubes_catalogs(cubes) + def update_cubes_catalogs(cubes): + toedit = [] + for cubedir in cubes: + if not isdir(cubedir): + print 'not a directory', cubedir + continue + try: + toedit += update_cube_catalogs(cubedir) + except Exception: + import traceback + traceback.print_exc() + print 'error while updating catalogs for', cubedir + # instructions pour la suite + print '*' * 72 + print 'you can now edit the following files:' + print '* ' + '\n* '.join(toedit) + + +def update_cube_catalogs(cubedir): import shutil from tempfile import mktemp from logilab.common.fileutils import ensure_fs_mode from logilab.common.shellutils import find, rm from cubicweb.common.i18n import extract_from_tal, execute toedit = [] - for cubedir in cubes: - cube = basename(normpath(cubedir)) - if not isdir(cubedir): - print 'unknown cube', cube - continue - tempdir = mktemp() - mkdir(tempdir) - print '*' * 72 - print 'updating %s cube...' % cube - chdir(cubedir) - potfiles = [join('i18n', scfile) for scfile in ('entities.pot',) - if exists(join('i18n', scfile))] - print '******** extract schema messages' - schemapot = join(tempdir, 'schema.pot') - potfiles.append(schemapot) - # explicit close necessary else the file may not be yet flushed when - # we'll using it below - schemapotstream = file(schemapot, 'w') - generate_schema_pot(schemapotstream.write, cubedir) - schemapotstream.close() - print '******** extract TAL messages' - tali18nfile = join(tempdir, 'tali18n.py') - extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile) - print '******** extract Javascript messages' - jsfiles = [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')] - if jsfiles: - tmppotfile = join(tempdir, 'js.pot') - execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s' - % (tmppotfile, ' '.join(jsfiles))) - # no pot file created if there are no string to translate - if exists(tmppotfile): - potfiles.append(tmppotfile) - print '******** create cube specific catalog' - tmppotfile = join(tempdir, 'generated.pot') - cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',)) - cubefiles.append(tali18nfile) - execute('xgettext --no-location --omit-header -k_ -o %s %s' - % (tmppotfile, ' '.join(cubefiles))) - if exists(tmppotfile): # doesn't exists of no translation string found + cube = basename(normpath(cubedir)) + tempdir = mktemp() + mkdir(tempdir) + print '*' * 72 + print 'updating %s cube...' % cube + chdir(cubedir) + potfiles = [join('i18n', scfile) for scfile in ('entities.pot',) + if exists(join('i18n', scfile))] + print '******** extract schema messages' + schemapot = join(tempdir, 'schema.pot') + potfiles.append(schemapot) + # explicit close necessary else the file may not be yet flushed when + # we'll using it below + schemapotstream = file(schemapot, 'w') + generate_schema_pot(schemapotstream.write, cubedir) + schemapotstream.close() + print '******** extract TAL messages' + tali18nfile = join(tempdir, 'tali18n.py') + extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile) + print '******** extract Javascript messages' + jsfiles = [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')] + if jsfiles: + tmppotfile = join(tempdir, 'js.pot') + execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s' + % (tmppotfile, ' '.join(jsfiles))) + # no pot file created if there are no string to translate + if exists(tmppotfile): potfiles.append(tmppotfile) - potfile = join(tempdir, 'cube.pot') - print '******** merging .pot files' - execute('msgcat %s > %s' % (' '.join(potfiles), potfile)) - print '******** merging main pot file with existing translations' - chdir('i18n') - for lang in LANGS: - print '****', lang - cubepo = '%s.po' % lang - if not exists(cubepo): - shutil.copy(potfile, cubepo) - else: - execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo)) - ensure_fs_mode(cubepo) - shutil.move('%snew' % cubepo, cubepo) - toedit.append(abspath(cubepo)) - # cleanup - rm(tempdir) - # instructions pour la suite - print '*' * 72 - print 'you can now edit the following files:' - print '* ' + '\n* '.join(toedit) + print '******** create cube specific catalog' + tmppotfile = join(tempdir, 'generated.pot') + cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',)) + cubefiles.append(tali18nfile) + execute('xgettext --no-location --omit-header -k_ -o %s %s' + % (tmppotfile, ' '.join(cubefiles))) + if exists(tmppotfile): # doesn't exists of no translation string found + potfiles.append(tmppotfile) + potfile = join(tempdir, 'cube.pot') + print '******** merging .pot files' + execute('msgcat %s > %s' % (' '.join(potfiles), potfile)) + print '******** merging main pot file with existing translations' + chdir('i18n') + for lang in LANGS: + print '****', lang + cubepo = '%s.po' % lang + if not exists(cubepo): + shutil.copy(potfile, cubepo) + else: + execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo)) + ensure_fs_mode(cubepo) + shutil.move('%snew' % cubepo, cubepo) + toedit.append(abspath(cubepo)) + # cleanup + rm(tempdir) + return toedit class LiveServerCommand(Command): @@ -367,7 +414,7 @@ name = 'live-server' arguments = '' options = () - + def run(self, args): """run the command with its specific arguments""" from cubicweb.devtools.livetest import runserver @@ -415,7 +462,7 @@ ), ) - + def run(self, args): if len(args) != 1: raise BadCommandUsage("exactly one argument (cube name) is expected") @@ -449,7 +496,7 @@ distname = 'cubicweb-' + distname else: distname = 'cubicweb-%s' % cubename.lower() - + longdesc = shortdesc = raw_input('Enter a short description for your cube: ') if verbose: longdesc = raw_input('Enter a long description (or nothing if you want to reuse the short one): ') @@ -461,14 +508,13 @@ dependancies = ', '.join(repr(cube) for cube in includes) else: dependancies = '' - from mx.DateTime import now context = {'cubename' : cubename, 'distname' : distname, 'shortdesc' : shortdesc, 'longdesc' : longdesc or shortdesc, 'dependancies' : dependancies, 'version' : cubicwebversion, - 'year' : str(now().year), + 'year' : str(datetime.now().year), 'author': self['author'], 'author-email': self['author-email'], 'author-web-site': self['author-web-site'], @@ -488,7 +534,7 @@ elif ans == 's': break return includes - + class ExamineLogCommand(Command): """Examine a rql log file. @@ -507,7 +553,7 @@ name = 'exlog' options = ( ) - + def run(self, args): if args: raise BadCommandUsage("no argument expected") @@ -541,7 +587,7 @@ print 'Percentage;Cumulative Time;Occurences;Query' for time, occ, rql in stat: print '%.2f;%.2f;%s;%s' % (time/total_time, time, occ, rql) - + register_commands((UpdateCubicWebCatalogCommand, UpdateTemplateCatalogCommand, LiveServerCommand, diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/fake.py --- a/devtools/fake.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/fake.py Thu May 14 12:48:11 2009 +0200 @@ -1,7 +1,7 @@ """Fake objects to ease testing of cubicweb without a fully working environment :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" @@ -24,13 +24,13 @@ self.apphome = apphome self._cubes = cubes self['auth-mode'] = 'cookie' - self['uid'] = None + self['uid'] = None self['base-url'] = BASE_URL self['rql-cache-size'] = 100 - + def cubes(self, expand=False): return self._cubes - + def sources(self): return {} @@ -41,7 +41,7 @@ self.properties = {'ui.encoding': 'UTF8', 'ui.language': 'en', } - + def property_value(self, key): return self.properties[key] @@ -51,10 +51,10 @@ 'views' : [Mock(id='primary'), Mock(id='secondary'), Mock(id='oneline'), Mock(id='list')], } - + def registry_objects(self, name, oid=None): return self._registries[name] - + def etype_class(self, etype): class Entity(dict): e_schema = self.schema[etype] @@ -112,15 +112,15 @@ def set_header(self, header, value): """set an output HTTP header""" pass - + def add_header(self, header, value): """set an output HTTP header""" pass - + def remove_header(self, header): """remove an output HTTP header""" pass - + def get_header(self, header, default=None): """return the value associated with the given input header, raise KeyError if the header is not set @@ -169,7 +169,7 @@ self.is_internal_session = False self.is_super_session = self.user.eid == -1 self._query_data = {} - + def execute(self, *args): pass def commit(self, *args): @@ -186,7 +186,7 @@ def set_entity_cache(self, entity): pass - + class FakeRepo(object): querier = None def __init__(self, schema, vreg=None, config=None): @@ -199,8 +199,9 @@ def internal_session(self): return FakeSession(self) - - def extid2eid(self, source, extid, etype, session, insert=True): + + def extid2eid(self, source, extid, etype, session, insert=True, + recreate=False): try: return self.extids[extid] except KeyError: @@ -213,7 +214,7 @@ self.eids[eid] = extid source.after_entity_insertion(session, extid, entity) return eid - + def eid2extid(self, source, eid, session=None): return self.eids[eid] @@ -228,7 +229,7 @@ def __init__(self, uri): self.uri = uri - + class FakePool(object): def source(self, uri): return FakeSource(uri) diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/fill.py --- a/devtools/fill.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/fill.py Thu May 14 12:48:11 2009 +0200 @@ -2,16 +2,16 @@ """This modules defines func / methods for creating test repositories :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" from random import randint, choice from copy import deepcopy +from datetime import datetime, date, timedelta +from decimal import Decimal -from mx.DateTime import DateTime, DateTimeDelta -from decimal import Decimal from yams.constraints import (SizeConstraint, StaticVocabularyConstraint, IntervalBoundConstraint) from rql.utils import decompose_b26 as base_decompose_b26 @@ -33,7 +33,7 @@ if isinstance(cst, StaticVocabularyConstraint): return cst.vocabulary() return None - + def get_max_length(eschema, attrname): """returns the maximum length allowed for 'attrname'""" @@ -75,7 +75,7 @@ value = self.__generate_value(attrname, index, **kwargs) _GENERATED_VALUES.setdefault((self.e_schema.type, attrname), set()).add(value) return value - + def __generate_value(self, attrname, index, **kwargs): """generates a consistent value for 'attrname'""" attrtype = str(self.e_schema.destination(attrname)).lower() @@ -100,7 +100,7 @@ if choices is None: return None return unicode(choice(choices)) # FIXME - + def generate_string(self, attrname, index, format=None): """generates a consistent value for 'attrname' if it's a string""" # First try to get choices @@ -133,7 +133,7 @@ def generate_password(self, attrname, index): """generates a consistent value for 'attrname' if it's a password""" return u'toto' - + def generate_integer(self, attrname, index): """generates a consistent value for 'attrname' if it's an integer""" choosed = self.generate_choice(attrname, index) @@ -145,29 +145,29 @@ else: maxvalue = maxvalue or index return randint(minvalue or 0, maxvalue) - + generate_int = generate_integer - + def generate_float(self, attrname, index): """generates a consistent value for 'attrname' if it's a float""" return float(randint(-index, index)) - + def generate_decimal(self, attrname, index): """generates a consistent value for 'attrname' if it's a float""" return Decimal(str(self.generate_float(attrname, index))) - + def generate_date(self, attrname, index): """generates a random date (format is 'yyyy-mm-dd')""" - return DateTime(randint(2000, 2004), randint(1, 12), randint(1, 28)) + return date(randint(2000, 2004), randint(1, 12), randint(1, 28)) def generate_time(self, attrname, index): """generates a random time (format is ' HH:MM')""" - return DateTimeDelta(0, 11, index%60) #'11:%02d' % (index % 60) - + return timedelta(0, 11, index%60) #'11:%02d' % (index % 60) + def generate_datetime(self, attrname, index): """generates a random date (format is 'yyyy-mm-dd HH:MM')""" - return DateTime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60) - + return datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60) + def generate_bytes(self, attrname, index, format=None): # modpython way @@ -175,7 +175,7 @@ fakefile.filename = "file_%s" % attrname fakefile.value = fakefile.getvalue() return fakefile - + def generate_boolean(self, attrname, index): """generates a consistent value for 'attrname' if it's a boolean""" return index % 2 == 0 @@ -185,7 +185,7 @@ # need this method else stupid values will be set which make mtconverter # raise exception return u'application/octet-stream' - + def generate_Any_content_format(self, index, **kwargs): # content_format attribute of EmailPart has no vocabulary constraint, we # need this method else stupid values will be set which make mtconverter @@ -236,7 +236,7 @@ returns acceptable values for this attribute """ # XXX HACK, remove or fix asap - if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'EGroup', 'EUser')): + if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')): return [] queries = [] for index in xrange(entity_num): @@ -250,7 +250,7 @@ args)) assert not 'eid' in args, args else: - queries.append(('INSERT %s X' % etype, {})) + queries.append(('INSERT %s X' % etype, {})) return queries @@ -365,7 +365,7 @@ continue subjcard, objcard = rschema.rproperty(subj, obj, 'cardinality') # process mandatory relations first - if subjcard in '1+' or objcard in '1+': + if subjcard in '1+' or objcard in '1+': queries += self.make_relation_queries(sedict, oedict, rschema, subj, obj) else: @@ -374,7 +374,7 @@ queries += self.make_relation_queries(sedict, oedict, rschema, subj, obj) return queries - + def qargs(self, subjeids, objeids, subjcard, objcard, subjeid, objeid): if subjcard in '?1': subjeids.remove(subjeid) @@ -411,7 +411,7 @@ subjeids.remove(subjeid) if not subjeids: check_card_satisfied(objcard, objeids, subj, rschema, obj) - return + return if not objeids: check_card_satisfied(subjcard, subjeids, subj, rschema, obj) return @@ -452,7 +452,7 @@ used.add( (subjeid, objeid) ) yield q, self.qargs(subjeids, objeids, subjcard, objcard, subjeid, objeid) - + def check_card_satisfied(card, remaining, subj, rschema, obj): if card in '1+' and remaining: raise Exception("can't satisfy cardinality %s for relation %s %s %s" @@ -466,8 +466,8 @@ while objeid == avoid: # avoid infinite recursion like in X comment X objeid = choice(values) return objeid - - + + # UTILITIES FUNCS ############################################################## def make_tel(num_tel): diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/htmlparser.py --- a/devtools/htmlparser.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/htmlparser.py Thu May 14 12:48:11 2009 +0200 @@ -1,20 +1,17 @@ """defines a validating HTML parser used in web application tests""" import re -from StringIO import StringIO from lxml import etree -from lxml.builder import E -from cubicweb.common.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE, CW_XHTML_EXTENSIONS - -STRICT_DOCTYPE = str(STRICT_DOCTYPE % CW_XHTML_EXTENSIONS).strip() -TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE % CW_XHTML_EXTENSIONS).strip() +from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE +STRICT_DOCTYPE = str(STRICT_DOCTYPE) +TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE) ERR_COUNT = 0 class Validator(object): - + def parse_string(self, data, sysid=None): try: data = self.preprocess_data(data) @@ -55,24 +52,11 @@ for blockquote in blockquotes: parent = blockquote.getparent() parent.remove(blockquote) -## # for each blockquote, wrap unauthorized child in a div -## for blockquote in blockquotes: -## if len(blockquote): -## needs_wrap = [(index, child) for index, child in enumerate(blockquote) -## if child.tag not in expected] -## for index, child in needs_wrap: -## # the child is automatically popped from blockquote when -## # its parent is changed -## div = E.div(child) -## blockquote.insert(index, div) -## elif blockquote.text: -## div = E.div(blockquote.text) -## blockquote.text = None -## blockquote.append(div) data = etree.tostring(tree) - return '%s\n%s' % (STRICT_DOCTYPE, data) + return '%s\n%s' % ( + STRICT_DOCTYPE, data) - + class SaxOnlyValidator(Validator): def __init__(self): @@ -85,7 +69,7 @@ Validator.__init__(self) self.parser = etree.HTMLParser() - + class PageInfo(object): """holds various informations on the view's output""" @@ -103,7 +87,7 @@ self.h4_tags = self.find_tag('h4') self.input_tags = self.find_tag('input') self.title_tags = [self.h1_tags, self.h2_tags, self.h3_tags, self.h4_tags] - + def find_tag(self, tag): """return a list which contains text of all "tag" elements """ if self.default_ns is None: @@ -113,14 +97,14 @@ if tag in ('a', 'input'): return [(elt.text, elt.attrib) for elt in self.etree.iterfind(iterstr)] return [u''.join(elt.xpath('.//text()')) for elt in self.etree.iterfind(iterstr)] - + def appears(self, text): """returns True if appears in the page""" return text in self.raw_text def __contains__(self, text): return text in self.source - + def has_title(self, text, level=None): """returns True if text @@ -150,7 +134,7 @@ if sre.match(title): return True return False - + def has_link(self, text, url=None): """returns True if text was found in the page""" for link_text, attrs in self.a_tags: @@ -164,7 +148,7 @@ except KeyError: continue return False - + def has_link_regexp(self, pattern, url=None): """returns True if pattern was found in the page""" sre = re.compile(pattern) diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/livetest.py --- a/devtools/livetest.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/livetest.py Thu May 14 12:48:11 2009 +0200 @@ -1,4 +1,9 @@ -"""provide utilies for web (live) unit testing""" +"""provide utilies for web (live) unit testing + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" import socket import logging @@ -36,17 +41,14 @@ """Indicate which resource to use to process down the URL's path""" if len(segments) and segments[0] == 'data': # Anything in data/ is treated as static files - dirlist = [self.data_dir, join(dirname(cubicweb.web.__file__), 'data')] - for alternative in dirlist: - filepath = join(alternative, *segments[1:]) - if exists(filepath): - self.info('publish static file: %s', '/'.join(segments)) - return static.File(filepath), () + datadir = self.config.locate_resource(segments[1]) + if datadir: + return static.File(str(datadir), segments[1:]) # Otherwise we use this single resource return self, () - - - + + + def make_site(cube, options=None): from cubicweb.etwist import twconfig # trigger configuration registration sourcefile = options.sourcefile @@ -78,7 +80,7 @@ def saveconf(templhome, port, user, passwd): import pickle conffile = file(join(templhome, 'test', 'livetest.conf'), 'w') - + pickle.dump((port, user, passwd, get_starturl(port, user, passwd)), conffile) conffile.close() @@ -102,8 +104,8 @@ from twill import browser as twb twc.OUT = new_output twb.OUT = new_output - - + + class LiveTestCase(TestCase): sourcefile = None @@ -121,7 +123,7 @@ def tearDown(self): self.teardown_db(self.cnx) - + def setup_db(self, cnx): """override setup_db() to setup your environment""" @@ -144,5 +146,3 @@ if __name__ == '__main__': runserver() - - diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/repotest.py --- a/devtools/repotest.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/repotest.py Thu May 14 12:48:11 2009 +0200 @@ -43,7 +43,7 @@ 'expected %s queries, got %s' % (len(equeries), len(queries))) for i, (rql, sol) in enumerate(queries): self.assertEquals(rql, equeries[i][0]) - self.assertEquals(sol, equeries[i][1]) + self.assertEquals(sorted(sol), sorted(equeries[i][1])) idx = 2 else: idx = 1 @@ -103,7 +103,7 @@ class RQLGeneratorTC(TestCase): schema = None # set this in concret test - + def setUp(self): self.rqlhelper = RQLHelper(self.schema, special_relations={'eid': 'uid', 'has_text': 'fti'}) @@ -114,7 +114,7 @@ def tearDown(self): ExecutionPlan._check_permissions = _orig_check_permissions rqlannotation._select_principal = _orig_select_principal - + def _prepare(self, rql): #print '******************** prepare', rql union = self.rqlhelper.parse(rql) @@ -133,7 +133,7 @@ class BaseQuerierTC(TestCase): repo = None # set this in concret test - + def setUp(self): self.o = self.repo.querier self.session = self.repo._sessions.values()[0] @@ -148,7 +148,7 @@ return self.session.unsafe_execute('Any MAX(X)')[0][0] def cleanup(self): self.session.unsafe_execute('DELETE Any X WHERE X eid > %s' % self.maxeid) - + def tearDown(self): undo_monkey_patch() self.session.rollback() @@ -159,7 +159,7 @@ def set_debug(self, debug): set_debug(debug) - + def _rqlhelper(self): rqlhelper = self.o._rqlhelper # reset uid_func so it don't try to get type from eids @@ -175,8 +175,8 @@ for select in rqlst.children: select.solutions.sort() return self.o.plan_factory(rqlst, kwargs, self.session) - - def _prepare(self, rql, kwargs=None): + + def _prepare(self, rql, kwargs=None): plan = self._prepare_plan(rql, kwargs) plan.preprocess(plan.rqlst) rqlst = plan.rqlst.children[0] @@ -195,10 +195,10 @@ def execute(self, rql, args=None, eid_key=None, build_descr=True): return self.o.execute(self.session, rql, args, eid_key, build_descr) - + def commit(self): self.session.commit() - self.session.set_pool() + self.session.set_pool() class BasePlannerTC(BaseQuerierTC): @@ -228,10 +228,10 @@ variantes = _orig_build_variantes(self, newsolutions) sortedvariantes = [] for variante in variantes: - orderedkeys = sorted((k[1], k[2], v) for k,v in variante.iteritems()) + orderedkeys = sorted((k[1], k[2], v) for k, v in variante.iteritems()) variante = DumbOrderedDict(sorted(variante.iteritems(), - lambda a,b: cmp((a[0][1],a[0][2],a[1]), - (b[0][1],b[0][2],b[1])))) + lambda a, b: cmp((a[0][1],a[0][2],a[1]), + (b[0][1],b[0][2],b[1])))) sortedvariantes.append( (orderedkeys, variante) ) return [v for ok, v in sorted(sortedvariantes)] @@ -241,7 +241,7 @@ def _check_permissions(*args, **kwargs): res, restricted = _orig_check_permissions(*args, **kwargs) - res = DumbOrderedDict(sorted(res.iteritems(), lambda a,b: cmp(a[1], b[1]))) + res = DumbOrderedDict(sorted(res.iteritems(), lambda a, b: cmp(a[1], b[1]))) return res, restricted def _dummy_check_permissions(self, rqlst): @@ -267,10 +267,10 @@ from cubicweb.server.msplanner import PartPlanInformation except ImportError: class PartPlanInformation(object): - def merge_input_maps(*args): + def merge_input_maps(self, *args): pass def _choose_term(self, sourceterms): - pass + pass _orig_merge_input_maps = PartPlanInformation.merge_input_maps _orig_choose_term = PartPlanInformation._choose_term @@ -309,4 +309,3 @@ ExecutionPlan.init_temp_table = _orig_init_temp_table PartPlanInformation.merge_input_maps = _orig_merge_input_maps PartPlanInformation._choose_term = _orig_choose_term - diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/stresstester.py --- a/devtools/stresstester.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/stresstester.py Thu May 14 12:48:11 2009 +0200 @@ -5,13 +5,13 @@ OPTIONS: -h / --help Display this help message and exit. - + -u / --user Connect as instead of being prompted to give it. -p / --password Automatically give for authentication instead of being prompted to give it. - + -n / --nb-times Repeat queries times. -t / --nb-threads @@ -21,7 +21,7 @@ -o / --report-output Write profiler report into rather than on stdout -Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. http://www.logilab.fr/ -- mailto:contact@logilab.fr """ @@ -49,7 +49,7 @@ self._times = times self._queries = queries self._reporter = reporter - + def run(self): cursor = self._cursor times = self._times @@ -80,7 +80,7 @@ threads and can write a report that summarizes all profile informations """ profiler_lock = threading.Lock() - + def __init__(self, queries): self._queries = tuple(queries) self._profile_results = [(0., 0)] * len(self._queries) @@ -111,8 +111,8 @@ table_layout = Table(3, rheaders = True, children = table_elems) TextWriter().format(table_layout, output) # output.write('\n'.join(tmp_output)) - - + + def run(args): """run the command line tool""" try: @@ -150,7 +150,7 @@ user = raw_input('login: ') if password is None: password = getpass('password: ') - from cubicweb.cwconfig import application_configuration + from cubicweb.cwconfig import application_configuration config = application_configuration(args[0]) # get local access to the repository print "Creating repo", prof_file @@ -176,7 +176,7 @@ else: QueryExecutor(repo_cursor, repeat, queries, reporter = reporter).run() reporter.dump_report(report_output) - - + + if __name__ == '__main__': run(sys.argv[1:]) diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/test/data/schema/relations.rel --- a/devtools/test/data/schema/relations.rel Tue May 05 17:18:49 2009 +0200 +++ b/devtools/test/data/schema/relations.rel Thu May 14 12:48:11 2009 +0200 @@ -23,11 +23,11 @@ Project uses Project Version version_of Project inline -Version todo_by EUser +Version todo_by CWUser Comment about Bug inline Comment about Story inline Comment about Comment inline -EUser interested_in Project +CWUser interested_in Project diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/test/data/views/bug.py --- a/devtools/test/data/views/bug.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/test/data/views/bug.py Thu May 14 12:48:11 2009 +0200 @@ -1,6 +1,7 @@ """only for unit tests !""" -from cubicweb.common.view import EntityView +from cubicweb.view import EntityView +from cubicweb.selectors import implements HTML_PAGE = u""" @@ -11,7 +12,7 @@ class SimpleView(EntityView): id = 'simple' - accepts = ('Bug',) + __select__ = implements('Bug',) def call(self, **kwargs): self.cell_call(0, 0) @@ -21,7 +22,7 @@ class RaisingView(EntityView): id = 'raising' - accepts = ('Bug',) + __select__ = implements('Bug',) def cell_call(self, row, col): raise ValueError() diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/test/runtests.py --- a/devtools/test/runtests.py Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -from logilab.common.testlib import main - -if __name__ == '__main__': - import sys, os - main(os.path.dirname(sys.argv[0]) or '.') diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/test/unittest_dbfill.py --- a/devtools/test/unittest_dbfill.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/test/unittest_dbfill.py Thu May 14 12:48:11 2009 +0200 @@ -31,7 +31,7 @@ return getattr(self, '_available_%s_%s' % (etype, attrname))(etype, attrname) except AttributeError: return None - + def _available_Person_firstname(self, etype, attrname): return [f.strip() for f in file(osp.join(DATADIR, 'firstnames.txt'))] @@ -51,11 +51,11 @@ year = date.year month = date.month day = date.day - self.failUnless(day in range(1, 29), '%s not in [0;28]' % day) + self.failUnless(day in range(1, 29), '%s not in [0;28]' % day) self.failUnless(month in range(1, 13), '%s not in [1;12]' % month) self.failUnless(year in range(2000, 2005), '%s not in [2000;2004]' % year) - + def test_string(self): """test string generation""" @@ -89,7 +89,7 @@ for index in range(5): date_value = self.person_valgen._generate_value('birthday', index) self._check_date(date_value) - + def test_phone(self): """tests make_tel utility""" self.assertEquals(make_tel(22030405), '22 03 04 05') @@ -102,14 +102,14 @@ u'yo') self.assertEquals(self.person_valgen._generate_value('description', 12), u'yo') - - + + class ConstraintInsertionTC(TestCase): def test_writeme(self): self.skip('Test automatic insertion / Schema Constraints') - + if __name__ == '__main__': unittest_main() diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/test/unittest_fill.py --- a/devtools/test/unittest_fill.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/test/unittest_fill.py Thu May 14 12:48:11 2009 +0200 @@ -20,7 +20,7 @@ for attrname in attrvalues - set(self.attrvalues): delattr(_ValueGenerator, attrname) - + def test_autoextend(self): self.failIf('generate_server' in dir(ValueGenerator)) class MyValueGenerator(ValueGenerator): diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/test/unittest_testlib.py --- a/devtools/test/unittest_testlib.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/test/unittest_testlib.py Thu May 14 12:48:11 2009 +0200 @@ -22,14 +22,14 @@ def test_error_view(self): self.add_entity('Bug', title=u"bt") self.view('raising', self.execute('Bug B'), template=None) - + def test_correct_view(self): - self.view('primary', self.execute('EUser U'), template=None) - + self.view('primary', self.execute('CWUser U'), template=None) + tests = [MyWebTest('test_error_view'), MyWebTest('test_correct_view')] result = self.runner.run(TestSuite(tests)) self.assertEquals(result.testsRun, 2) - self.assertEquals(len(result.errors), 0) + self.assertEquals(len(result.errors), 0) self.assertEquals(len(result.failures), 1) @@ -97,13 +97,13 @@ def test_source1(self): """make sure source is stored correctly""" self.assertEquals(self.page_info.source, HTML_PAGE2) - + def test_source2(self): """make sure source is stored correctly - raise exception""" parser = htmlparser.DTDValidator() self.assertRaises(AssertionError, parser.parse_string, HTML_PAGE_ERROR) - + def test_has_title_no_level(self): """tests h? tags information""" self.assertEquals(self.page_info.has_title('Test'), True) @@ -128,7 +128,7 @@ self.assertEquals(self.page_info.has_title_regexp('h[23] title', 2), True) self.assertEquals(self.page_info.has_title_regexp('h[23] title', 3), True) self.assertEquals(self.page_info.has_title_regexp('h[23] title', 4), False) - + def test_appears(self): """tests PageInfo.appears()""" self.assertEquals(self.page_info.appears('CW'), True) @@ -151,4 +151,3 @@ if __name__ == '__main__': unittest_main() - diff -r 49075f57cf2c -r aa09e20dd8c0 devtools/testlib.py --- a/devtools/testlib.py Tue May 05 17:18:49 2009 +0200 +++ b/devtools/testlib.py Thu May 14 12:48:11 2009 +0200 @@ -1,7 +1,7 @@ """this module contains base classes for web tests :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" @@ -13,8 +13,6 @@ from logilab.common.testlib import InnerTest from logilab.common.pytest import nocoverage -from rql import parse - from cubicweb.devtools import VIEW_VALIDATORS from cubicweb.devtools.apptest import EnvBasedTC from cubicweb.devtools._apptest import unprotected_entities, SYSTEM_RELATIONS @@ -24,8 +22,6 @@ from cubicweb.sobjects.notification import NotificationView from cubicweb.vregistry import NoSelectableObject -from cubicweb.web.action import Action -from cubicweb.web.views.basetemplates import TheMainTemplate ## TODO ############### @@ -114,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 @@ -149,7 +145,7 @@ if rschema.is_final() or rschema in ignored_relations: continue rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema) - existingrels.setdefault(rschema.type, set()).update((x,y) for x, y in rset) + existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset) q = make_relations_queries(self.schema, edict, cu, ignored_relations, existingrels=existingrels) for rql, args in q: @@ -158,7 +154,7 @@ self.commit() @nocoverage - def _check_html(self, output, view, template='main'): + def _check_html(self, output, view, template='main-template'): """raises an exception if the HTML is invalid""" try: validatorclass = self.vid_validators[view.id] @@ -175,7 +171,7 @@ return validator.parse_string(output.strip()) - def view(self, vid, rset, req=None, template='main', **kwargs): + def view(self, vid, rset, req=None, template='main-template', **kwargs): """This method tests the view `vid` on `rset` using `template` If no error occured while rendering the view, the HTML is analyzed @@ -184,12 +180,12 @@ :returns: an instance of `cubicweb.devtools.htmlparser.PageInfo` encapsulation the generated HTML """ - req = req or rset.req + req = req or rset and rset.req or self.request() # print "testing ", vid, # 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 @@ -197,24 +193,16 @@ self.set_description("testing %s, mod=%s (%s)" % (vid, view.__module__, rset.printable_rql())) else: self.set_description("testing %s, mod=%s (no rset)" % (vid, view.__module__)) - viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs) if template is None: # raw view testing, no template - viewfunc = view.dispatch - elif template == 'main': - _select_view_and_rset = TheMainTemplate._select_view_and_rset - # patch TheMainTemplate.process_rql to avoid recomputing resultset - def __select_view_and_rset(self, view=view, rset=rset): - self.rset = rset - return view, rset - TheMainTemplate._select_view_and_rset = __select_view_and_rset - try: - return self._test_view(viewfunc, view, template, **kwargs) - finally: - if template == 'main': - TheMainTemplate._select_view_and_rset = _select_view_and_rset + viewfunc = view.render + else: + templateview = self.vreg.select_view(template, req, rset, view=view, **kwargs) + kwargs['view'] = view + viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs) + return self._test_view(viewfunc, view, template, kwargs) - def _test_view(self, viewfunc, view, template='main', **kwargs): + def _test_view(self, viewfunc, view, template='main-template', kwargs={}): """this method does the actual call to the view If no error occured while rendering the view, the HTML is analyzed @@ -249,21 +237,23 @@ output = '\n'.join(line_template % (idx + 1, line) for idx, line in enumerate(output) if line_context_filter(idx+1, position)) - msg+= '\nfor output:\n%s' % output + msg += '\nfor output:\n%s' % output raise AssertionError, msg, tcbk 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() for etype in etypes: yield self.execute('Any X LIMIT %s WHERE X is %s' % (limit, etype)) - etype1 = etypes.pop() - etype2 = etypes.pop() + try: + etype2 = etypes.pop() + except KeyError: + etype2 = etype1 # test a mixed query (DISTINCT/GROUP to avoid getting duplicate # X which make muledit view failing for instance (html validation fails # because of some duplicate "id" attributes) @@ -272,7 +262,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 @@ -309,7 +299,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() @@ -318,7 +308,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,23 +322,19 @@ backup_rset = rset._prepare_copy(rset.rows, rset.description) yield InnerTest(self._testname(rset, view.id, 'view'), self.view, view.id, rset, - rset.req.reset_headers(), 'main') + rset.req.reset_headers(), 'main-template') # We have to do this because some views modify the # resultset's syntax tree rset = backup_rset for action in self.list_actions_for(rset): - # XXX this seems a bit dummy - #yield InnerTest(self._testname(rset, action.id, 'action'), - # self.failUnless, - # isinstance(action, Action)) yield InnerTest(self._testname(rset, action.id, 'action'), action.url) for box in self.list_boxes_for(rset): - yield InnerTest(self._testname(rset, box.id, 'box'), box.dispatch) + yield InnerTest(self._testname(rset, box.id, 'box'), box.render) @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""" @@ -365,7 +351,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(): @@ -390,7 +376,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 49075f57cf2c -r aa09e20dd8c0 doc/book/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/README Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,43 @@ +==== +Book +==== + +---- +Part +---- + +Chapter +======= + +Level 1 section +--------------- + +Level 2 section +~~~~~~~~~~~~~~~ + +Level 3 section +``````````````` + + + +*CubicWeb* + + +inline directives: + :file: + :envvar: + :command: + + :ref:, :mod: + + +XXX +* lien vers cw.cwconfig.CW_CUBES_PATH par ex. + + +.. sourcecode:: python + + class SomePythonCode: + ... + +.. XXX a comment diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/_maybe_to_integrate/D050-architecture.en.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/_maybe_to_integrate/D050-architecture.en.txt Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,14 @@ +.. -*- coding: utf-8 -*- + + +Server Architecture +------------------- + +.. image:: images/server-class-diagram.png + +`Diagramme ArgoUML`_ + +[FIXME] +Make a downloadable source of zargo file. + +.. _`Diagramme ArgoUML`: cubicweb.zargo diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/_maybe_to_integrate/rss-xml.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/_maybe_to_integrate/rss-xml.rst Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,45 @@ +.. -*- coding: utf-8 -*- + +RSS Channel +----------- + +Assuming you have several blog entries, click on the title of the +search box in the left column. A larger search box should appear. Enter:: + + Any X ORDERBY D WHERE X is BlogEntry, X creation_date D + +and you get a list of blog entries. + +Click on your login at the top right corner. Chose "user preferences", +then "boxes", then "possible views box" and check "visible = yes" +before validating your changes. + +Enter the same query in the search box and you will see the same list, +plus a box titled "possible views" in the left column. Click on +"entityview", then "RSS". + +You just applied the "RSS" view to the RQL selection you requested. + +That's it, you have a RSS channel for your blog. + +Try again with:: + + Any X ORDERBY D WHERE X is BlogEntry, X creation_date D, + X entry_of B, B title "MyLife" + +Another RSS channel, but a bit more focused. + +A last one for the road:: + + Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15 + +displayed with the RSS view, that's a channel for the last fifteen +comments posted. + +[WRITE ME] + +* show that the RSS view can be used to display an ordered selection + of blog entries, thus providing a RSS channel + +* show that a different selection (by category) means a different channel + diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/_maybe_to_integrate/template.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/_maybe_to_integrate/template.rst Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,20 @@ + + +Templates +--------- + +*Templates* are specific views that do not depend on a result set. The basic +class `Template` (`cubicweb.common.view`) is derived from the class `View`. + +To build a HTML page, a *main template* is used. In general, the template of +identifier `main` is the one to use (it is not used in case an error is raised or for +the login form for example). This template uses other templates in addition +to the views which depends on the content to generate the HTML page to return. + +A *template* is responsible for: + +1. executing RQL query of data to render if necessary +2. identifying the view to use to render data if it is not specified +3. composing the HTML page to return + +You will find out more about templates in :ref:`templates`. diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/_maybe_to_integrate/treemixin.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/_maybe_to_integrate/treemixin.rst Thu May 14 12:48:11 2009 +0200 @@ -0,0 +1,100 @@ + +Class `TreeMixIn` +----------------- + +This class provides a tree interface. This mixin has to be inherited +explicitly and configured using the tree_attribute, parent_target and +children_target class attribute to benefit from this default implementation. + +This class provides the following methods: + + * `different_type_children(entities=True)`, returns children entities + of different type as this entity. According to the `entities` parameter, + returns entity objects (if entity=True) or the equivalent result set. + + * `same_type_children(entities=True)`, returns children entities of + the same type as this entity. According to the `entities` parameter, + return entity objects (if entity=True) or the equivalent result set. + + * `iterchildren( _done=None)`, iters on the children of the entity. + + * `prefixiter( _done=None)` + + * `path()`, returns the list of eids from the root object to this object. + + * `iterparents()`, iters on the parents of the entity. + + * `notification_references(view)`, used to control References field + of email send on notification for this entity. `view` is the notification view. + Should return a list of eids which can be used to generate message ids + of previously sent email. + +`TreeMixIn` implements also the ITree interface (``cubicweb.interfaces``): + + * `parent()`, returns the parent entity if any, else None (e.g. if we are on the + root) + + * `children(entities=True, sametype=False)`, returns children entities + according to the `entities` parameter, return entity objects or the + equivalent result set. + + * `children_rql()`, returns the RQL query corresponding to the children + of the entity. + + * `is_leaf()`, returns True if the entity does not have any children. + + * `is_root()`, returns True if the entity does not have any parent. + + * `root()`, returns the root object of the tree representation of + the entity and its related entities. + +Example of use +`````````````` + +Imagine you defined three types of entities in your schema, and they +relates to each others as follows in ``schema.py``:: + + class Entity1(EntityType): + title = String() + is_related_to = SubjectRelation('Entity2', 'subject') + + class Entity2(EntityType): + title = String() + belongs_to = SubjectRelation('Entity3', 'subject') + + class Entity3(EntityType): + name = String() + +You would like to create a view that applies to both entity types +`Entity1` and `Entity2` and which lists the entities they are related to. +That means when you view `Entity1` you want to list all `Entity2`, and +when you view `Entity2` you want to list all `Entity3`. + +In ``entities.py``:: + + class Entity1(TreeMixIn, AnyEntity): + id = 'Entity1' + __implements__ = AnyEntity.__implements__ + (ITree,) + __rtags__ = {('is_related_to', 'Entity2', 'object'): 'link'} + tree_attribute = 'is_related_to' + + def children(self, entities=True): + return self.different_type_children(entities) + + class Entity2(TreeMixIn, AnyEntity): + id = 'Entity2' + __implements__ = AnyEntity.__implements__ + (ITree,) + __rtags__ = {('belongs_to', 'Entity3', 'object'): 'link'} + tree_attribute = 'belongs_to' + + def children(self, entities=True): + return self.different_type_children(entities) + +Once this is done, you can define your common view as follows:: + + class E1E2CommonView(baseviews.PrimaryView): + accepts = ('Entity11, 'Entity2') + + def render_entity_relations(self, entity, siderelations): + self.wview('list', entity.children(entities=False)) + diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A000-introduction.en.txt --- a/doc/book/en/A000-introduction.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _Part1: - -=================================== -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 -overview of its features and list its key concepts. - - -.. toctree:: - :maxdepth: 2 - - A010-book-map.en.txt - A020-tutorial.en.txt - A030-foundation.en.txt diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A010-book-map.en.txt --- a/doc/book/en/A010-book-map.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -.. -*- coding: utf-8 -*- - -Book map -========= - -[WRITE ME] - -* explain how to use this book and what chapters to read in what order depending on the - objectives of the reader - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A020-tutorial.en.txt --- a/doc/book/en/A020-tutorial.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _Tutorial: - -Tutorial -======== - -`CubicWeb` is a semantic web application framework that favors reuse and -object-oriented design. - -A `cube` is a component that includes a model defining the data types and a set of -views to display the data. - -An application is a `cube`, but usually an application is built by assembling -a few smaller cubes. - -An `instance` is a specific installation of an application and includes -configuration files. - - -This tutorial will show how to create a `cube` and how to use it as an -application to run an `instance`. - -.. include:: Z013-blog-less-ten-minutes.en.txt -.. include:: A02a-create-cube.en.txt -.. include:: A02b-components.en.txt -.. include:: A02c-maintemplate.en.txt -.. include:: A02d-conclusion.en.txt diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A02a-create-cube.en.txt --- a/doc/book/en/A02a-create-cube.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,273 +0,0 @@ -.. -*- coding: utf-8 -*- - -Create your cube ----------------- - -The packages ``cubicweb`` and ``cubicweb-dev`` installs a command line tool -for `CubicWeb` called ``cubicweb-ctl``. This tool provides a wide range of -commands described in details in :ref:`cubicweb-ctl`. - -Once your `CubicWeb` development environment is set up, you can create a new -cube:: - - cubicweb-ctl newcube blog - -This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial -installation, ``/usr/share/cubicweb/cubes`` for debian packages installation) -a directory named ``blog`` reflecting the structure described in :ref:`cubesConcepts`. - -.. _DefineDataModel: - -Define your data model ----------------------- - -The data model or schema is the core of your `CubicWeb` application. -It defines the type of content your application will handle. - -The data model of your cube ``blog`` is defined in the file ``schema.py``: - -:: - - class Blog(EntityType): - title = String(maxsize=50, required=True) - description = String() - - class BlogEntry(EntityType): - title = String(required=True, fulltextindexed=True, maxsize=256) - publish_date = Date(default='TODAY') - content = String(required=True, fulltextindexed=True) - entry_of = SubjectRelation('Blog', cardinality='?*') - - -A Blog has a title and a description. The title is a string that is -required by the class EntityType and must be less than 50 characters. -The description is a string that is not constrained. - -A BlogEntry has a title, a publish_date and a content. The title is a -string that is required and must be less than 100 characters. The -publish_date is a Date with a default value of TODAY, meaning that -when a BlogEntry is created, its publish_date will be the current day -unless it is modified. The content is a string that will be indexed in -the full-text index and has no constraint. - -A BlogEntry also has a relationship ``entry_of`` that links it to a -Blog. The cardinality ``?*`` means that a BlogEntry can be part of -zero or one Blog (``?`` means `zero or one`) and that a Blog can -have any number of BlogEntry (``*`` means `any number including -zero`). For completeness, remember that ``+`` means `one or more`. - - -Create your instance --------------------- - -To use this cube as an application and create a new instance named ``blogdemo``, do:: - - cubicweb-ctl create blog blogdemo - - -This command will create the corresponding database and initialize it. - -Welcome to your web application -------------------------------- - -Start your application in debug mode with the following command: :: - - cubicweb-ctl start -D blogdemo - - -You can now access your web application to create blogs and post messages -by visiting the URL http://localhost:8080/. - -A login form will appear. By default, the application will not allow anonymous -users to enter the application. To login, you need then use the admin account -you created at the time you initialized the database with ``cubicweb-ctl -create``. - -.. image:: images/login-form.png - - -Once authenticated, you can start playing with your application -and create entities. - -.. image:: images/blog-demo-first-page.png - -Please notice that so far, the `CubicWeb` franework managed all aspects of -the web application based on the schema provided at first. - - -Add entities ------------- - -We will now add entities in our web application. - -Add a Blog -~~~~~~~~~~ - -Let us create a few of these entities. Click on the `[+]` at the left of the -link Blog on the home page. Call this new Blog ``Tech-blog`` and type in -``everything about technology`` as the description, then validate the form by -clicking on ``Validate``. - -.. image:: images/cbw-create-blog.en.png - :alt: from to create blog - -Click on the logo at top left to get back to the home page, then -follow the Blog link that will list for you all the existing Blog. -You should be seeing a list with a single item ``Tech-blog`` you -just created. - -.. image:: images/cbw-list-one-blog.en.png - :alt: displaying a list of a single blog - -Clicking on this item will get you to its detailed description except -that in this case, there is not much to display besides the name and -the phrase ``everything about technology``. - -Now get back to the home page by clicking on the top-left logo, then -create a new Blog called ``MyLife`` and get back to the home page -again to follow the Blog link for the second time. The list now -has two items. - -.. image:: images/cbw-list-two-blog.en.png - :alt: displaying a list of two blogs - -Add a BlogEntry -~~~~~~~~~~~~~~~ - -Get back to the home page and click on [+] at the left of the link -BlogEntry. Call this new entry ``Hello World`` and type in some text -before clicking on ``Validate``. You added a new blog entry without -saying to what blog it belongs. There is a box on the left entitled -``actions``, click on the menu item ``modify``. You are back to the form -to edit the blog entry you just created, except that the form now has -another section with a combobox titled ``add relation``. Chose -``entry_of`` in this menu and a second combobox appears where you pick -``MyLife``. - -You could also have, at the time you started to fill the form for a -new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the -combobox titled ``add relation`` would have showed up. - - -.. image:: images/cbw-add-relation-entryof.en.png - :alt: editing a blog entry to add a relation to a blog - -Validate the changes by clicking ``Validate``. The entity BlogEntry -that is displayed now includes a link to the entity Blog named -``MyLife``. - -.. image:: images/cbw-detail-one-blogentry.en.png - :alt: displaying the detailed view of a blogentry - -Note that all of this was handled by the framework and that the only input -that was provided so far is the schema. To get a graphical view of the schema, -point your browser to the URL http://localhost:8080/schema - -.. image:: images/cbw-schema.en.png - :alt: graphical view of the schema (aka data-model) - - -.. _DefineViews: - -Define your entity views ------------------------- - -Each entity defined in a model inherits default views allowing -different rendering of the data. You can redefine each of them -according to your needs and preferences. So let's see how the -views are defined. - - -The view selection principle -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A view is defined by a Python class which includes: - - - an identifier (all objects in `CubicWeb` are entered in a registry - and this identifier will be used as a key) - - - a filter to select the result sets it can be applied to - -A view has a set of methods complying -with the `View` class interface (`cubicweb.common.view`). - -`CubicWeb` provides a lot of standard views for the type `EntityView`; -for a complete list, read the code in directory ``cubicweb/web/views/``. - -A view is applied on a `result set` which contains a set of -entities we are trying to display. `CubicWeb` uses a selector -mechanism which computes for each available view a score: -the view with the highest score is then used to display the given `result set`. -The standard library of selectors is in -``cubicweb.common.selector`` and a library of methods used to -compute scores is available in ``cubicweb.vregistry.vreq``. - -It is possible to define multiple views for the same identifier -and to associate selectors and filters to allow the application -to find the best way to render the data. - -For example, the view named ``primary`` is the one used to display -a single entity. We will now show you how to customize this view. - - -View customization -~~~~~~~~~~~~~~~~~~ - -If you wish to modify the way a `BlogEntry` is rendered, you will have to -overwrite the `primary` view defined in the module ``views`` of the cube -``cubes/blog/views.py``. - -We can for example add in front of the publication date a prefix specifying -that the date we see is the publication date. - -To do so, please apply the following changes: - -:: - - from cubicweb.web.views import baseviews - - - class BlogEntryPrimaryView(baseviews.PrimaryView): - - accepts = ('BlogEntry',) - - def render_entity_title(self, entity): - self.w(u'

%s

' % html_escape(entity.dc_title())) - - def content_format(self, entity): - return entity.view('reledit', rtype='content_format') - - def cell_call(self, row, col): - entity = self.entity(row, col) - - # display entity attributes with prefixes - self.w(u'

%s

' % entity.title) - self.w(u'

published on %s

' % entity.publish_date.strftime('%Y-%m-%d')) - self.w(u'

%s

' % entity.content) - - # display relations - siderelations = [] - if self.main_related_section: - self.render_entity_relations(entity, siderelations) - -.. note:: - When a view is modified, it is not required to restart the application - server. Save the Python file and reload the page in your web browser - to view the changes. - -You can now see that the publication date has a prefix. - -.. image:: images/cbw-update-primary-view.en.png - :alt: modified primary view - - -The above source code defines a new primary view for ``BlogEntry``. - -Since views are applied to result sets and result sets can be tables of -data, we have to recover the entity from its (row,col)-coordinates. -The view has a ``self.w()`` method that is used to output data, in our -example HTML output. - -You can find more details about views and selectors in :ref:`ViewDefinition`. - - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A02b-components.en.txt --- a/doc/book/en/A02b-components.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _cubes: - -Cubes ------ - -Standard library -~~~~~~~~~~~~~~~~ - -A library of standard cubes are available from `CubicWeb Forge`_ -Cubes provide entities and views. - -The available application entities are: - -* addressbook: PhoneNumber and PostalAddress - -* basket: Basket (like a shopping cart) - -* blog: Blog (a *very* basic blog) - -* classfolder: Folder (to organize things but grouping them in folders) - -* classtags: Tag (to tag anything) - -* file: File (to allow users to upload and store binary or text files) - -* link: Link (to collect links to web resources) - -* mailinglist: MailingList (to reference a mailing-list and the URLs - for its archives and its admin interface) - -* person: Person (easily mixed with addressbook) - -* task: Task (something to be done between start and stop date) - -* zone: Zone (to define places within larger places, for example a - city in a state in a country) - -The available system entities are: - -* comment: Comment (to attach comment threads to entities) - - -Adding comments to BlogDemo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To import a cube in your application just change the line in the -``__pkginfo__.py`` file and verify that the cube you are planning -to use is listed by the command ``cubicweb-ctl list``. -For example:: - - __use__ = ('comment',) - -will make the ``Comment`` entity available in your ``BlogDemo`` -application. - -Change the schema to add a relationship between ``BlogEntry`` and -``Comment`` and you are done. Since the comment cube defines the -``comments`` relationship, adding the line:: - - comments = ObjectRelation('Comment', cardinality='1*', composite='object') - -to the definition of a ``BlogEntry`` will be enough. - -Synchronize the data model -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Once you modified your data model, you need to synchronize the -database with your model. For this purpose, `CubicWeb` provides -a very useful command ``cubicweb-ctl shell blogdemo`` which -launches an interactive migration Python shell. (see -:ref:`cubicweb-ctl` for more details)) -As you modified a relation from the `BlogEntry` schema, -run the following command: -:: - - synchronize_rschema('BlogEntry') - -You can now start your application and add comments to each -`BlogEntry`. diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A02c-maintemplate.en.txt --- a/doc/book/en/A02c-maintemplate.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -.. -*- coding: utf-8 -*- - -Templates ---------- - -Look at ``cubicweb/web/views/basetemplates.py`` and you will -find the base templates used to generate HTML for your application. - -A page is composed as indicated on the schema below: - -.. image:: images/lax-book.06-main-template-layout.en.png - -In this section we will demonstrate a change in one of the main -interesting template from the three you will look for, -that is to say, the HTMLPageHeader, the HTMLPageFooter -and the TheMainTemplate. - - -Customize a template -~~~~~~~~~~~~~~~~~~~~ - -Based on the diagram below, each template can be overriden -by your customized template. To do so, we recommand you create -a Python module ``blog.views.templates`` to keep it organized. -In this module you will have to import the parent class you are -interested as follows: :: - - from cubicweb.web.views.basetemplates import HTMLPageHeader, \ - HTMLPageFooter, TheMainTemplate - -and then create your sub-class:: - - class MyBlogHTMLPageHeader(HTMLPageHeader): - ... - -Customize header -````````````````` - -Let's now move the search box in the header and remove the login form -from the header. We'll show how to move it to the left column of the application. - -Let's say we do not want anymore the login menu to be in the header - -First, to remove the login menu, we just need to comment out the display of the -login graphic component such as follows: :: - - class MyBlogHTMLPageHeader(HTMLPageHeader): - - def main_header(self, view): - """build the top menu with authentification info and the rql box""" - self.w(u'
%s
   
\n') - self.w(u'\n') - # appliname and breadcrumbs - self.w(u'') - # logged user and help - #self.w(u'') - # lastcolumn - self.w(u'\n') - self.w(u'\n') - self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden', - title=False, message=False) - - - -.. image:: images/lax-book.06-header-no-login.en.png - -Customize footer -```````````````` - -If you want to change the footer for example, look -for HTMLPageFooter and override it in your views file as in: :: - - from cubicweb.web.views.basetemplates import HTMLPageFooter - - class MyHTMLPageFooter(HTMLPageFooter): - - def call(self, **kwargs): - self.w(u'') - -Updating a view does not require any restart of the server. By reloading -the page you can see your new page footer. - - -TheMainTemplate -``````````````` - -.. _TheMainTemplate: - -The MainTemplate is a bit complex as it tries to accomodate many -different cases. We are now about to go through it and cutomize entirely -our application. - -TheMainTemplate is responsible for the general layout of the entire application. -It defines the template of ``id = main`` that is used by the application. Is -also defined in ``cubicweb/web/views/basetemplates.py`` another template that can -be used based on TheMainTemplate called SimpleMainTemplate which does not have -a top section. - -.. image:: images/lax-book.06-simple-main-template.en.png - -XXX -[WRITE ME] - -* customize MainTemplate and show that everything in the user - interface can be changed - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A02d-conclusion.en.txt --- a/doc/book/en/A02d-conclusion.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -.. -*- coding: utf-8 -*- - -What's next? ------------- - -We demonstrated how from a straight out of the box `CubicWeb` -installation, you can build your web-application based on a -schema. It's all already there: views, templates, permissions, -etc. The step forward is now for you to customize according -to your needs. - -More than a web application, many features are available to -extend your application, for example: RSS channel integration -(:ref:`rss`), hooks (:ref:`hooks`), support of sources such as -Google App Engine (:ref:`gaecontents`) and lots of others to -discover through our book. - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A02d-rss-xml.en.txt --- a/doc/book/en/A02d-rss-xml.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -.. -*- coding: utf-8 -*- - -RSS Channel ------------ - -Assuming you have several blog entries, click on the title of the -search box in the left column. A larger search box should appear. Enter:: - - Any X ORDERBY D WHERE X is BlogEntry, X creation_date D - -and you get a list of blog entries. - -Click on your login at the top right corner. Chose "user preferences", -then "boxes", then "possible views box" and check "visible = yes" -before validating your changes. - -Enter the same query in the search box and you will see the same list, -plus a box titled "possible views" in the left column. Click on -"entityview", then "RSS". - -You just applied the "RSS" view to the RQL selection you requested. - -That's it, you have a RSS channel for your blog. - -Try again with:: - - Any X ORDERBY D WHERE X is BlogEntry, X creation_date D, - X entry_of B, B title "MyLife" - -Another RSS channel, but a bit more focused. - -A last one for the road:: - - Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15 - -displayed with the RSS view, that's a channel for the last fifteen -comments posted. - -[WRITE ME] - -* show that the RSS view can be used to display an ordered selection - of blog entries, thus providing a RSS channel - -* show that a different selection (by category) means a different channel - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A030-foundation.en.txt --- a/doc/book/en/A030-foundation.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -.. -*- coding: utf-8 -*- - -`CubicWeb` Foundations -====================== - -A little history... -------------------- - -`CubicWeb` is a web application framework developped by Logilab_ since 2001. - -Entirely written in Python, `CubicWeb` publishes data from all sorts -of sources such as SQL database, LDAP directory and versioning system such -as subversion. - -`CubicWeb` user interface was designed to let the final user a huge flexibility -on how to select and how to display content. It allows to browse the knowledge -database and to display the results with the best rendering according to -the context. -This interface flexibility gives back the user the control of the -rendering parameters that are usually reserved for developpers. - - -We can list a couple of web applications developped with `CubicWeb`, an online -public phone directory (see http://www.118000.fr/), a system for managing -digital studies and simulations for a research lab, a tool for shared children -babysitting (see http://garde-partagee.atoukontact.fr/), a tool to manage -software developpment (see http://www.logilab.org), an application for -managing museums collections (see -http://collections.musees-haute-normandie.fr/collections/), etc. - -In 2008, `CubicWeb` was ported for a new type of source : the datastore -from `GoogleAppEngine`_. - -.. include:: A03a-concepts.en.txt diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/A03a-concepts.en.txt --- a/doc/book/en/A03a-concepts.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,536 +0,0 @@ -.. -*- coding: utf-8 -*- - -Concepts --------- - -This section aims to provide you the keys of success with `CubicWeb` -by clarifying the terms specific to our framework. - -Global architecture -~~~~~~~~~~~~~~~~~~~ -.. image:: images/archi_globale.en.png - - -`CubicWeb` framework is a server/client application framework. Those two -parts communicate through RQL (`CubicWeb` query language implementation) -and ResultSet (which will be explained in :ref:`TermsVocabulary`). - -The server manages all interactions with sources. - - -.. note:: - Usually, the client and server sides are integrated in the same - process and interact directly, without the need for distant - calls using Pyro. But, it is important to note that those two - sides, client/server, are disjointed and it is possible to execute - a couple of calls in distinct processes to balance the load of - your web site on one or more machines. - -.. _TermsVocabulary: - -Terms and vocabulary -~~~~~~~~~~~~~~~~~~~~~ - -`CubicWeb` defines its own terminology. To make sure there is no confusion -while reading this book, we strongly recommand you take time to go through -the following definitions that are the basics to understand while -developing with `CubicWeb`. - -*schema* - The schema defines the data model of an application based on entities - and relations, modeled with a comprehensive language made of Python - classes based on `yams`_ library. This is the core piece - of an application. It is initially defined in the file system and is - stored in the database at the time an instance is created. `CubicWeb` - provides a certain number of system entities included automatically - (necessary for the core of `CubicWeb`) and a library of - cubes (which defined application entities) that can be explicitely - included if necessary. - -*entity type* - An entity type is a set of attributes; the essential attribute of - an entity is its key, named eid. - -*relation type* - Entities are linked to each others by relations. In `CubicWeb` - relations are binary: by convention we name the first item of - a relation the `subject` and the second the `object`. - -*final entity type* - Final types correspond to the basic types such as string of characters, - integers... Those types have a main property which is that they can - only be used as `object` of a relation. The attributes of an entity - (non final) are entities (finals). - -*final relation type* - A relation is said final if its `object` is a final type. This is equivalent - to an entity attribute. - -*relation definition* - A relation definition is a 3-uple (subject entity type, relation type, object - entity type), with an associated set of property such as cardinality, constraints... - -*repository* - This is the RQL server side of `CubicWeb`. Be carefull not to get - confused with a Mercurial repository or a debian repository. - -*source* - A data source is a container of data (SGBD, LDAP directory, `Google - App Engine`'s datastore ...) integrated in the - `CubicWeb` repository. This repository has at least one source, `system` which - contains the schema of the application, plain-text index and other - vital informations for the system. - -*configuration* - It is possible to create different configurations for an instance: - - - ``repository`` : repository only, accessible for clients using Pyro - - ``twisted`` : web interface only, access the repository using Pyro - - ``all-in-one`` : web interface and repository in a single process. - The repository could be or not accessible using Pyro. - -*cube* - A cube is a model grouping one or multiple data types and/or views - to provide a specific functionality or a complete `CubicWeb` application - potentially using other cubes. The available cubes are located in the file - system at `/path/to/forest/cubicweb/cubes` for a Mercurial forest installation. - For a debian packages installation they will be located in - `/usr/share/cubicweb/cubes`. - Larger applications can be built quite fast by importing cubes, - adding entities and relationships, overriding the - *views* that display the cubes or by editing informations not provided by - the cubes. - -*instance* - An instance is a specific installation of one or multiple cubes. All the required - configuration files necessary for the well being of your web application - are grouped in an instance. This will refer to the cube(s) your application - is based on. - For example logilab.org and our intranet are two instances of a single - cube "jpl", developped internally. - The instances are defined in the directory `/etc/cubicweb.d`. - -*application* - The term application is sometimes used to talk about an instance - and sometimes to talk of a cube depending on the context. - So we would like to avoid using this term and try to use *cube* and - *instance* instead. - -*result set* - This object contains the results of an RQL query sent to the source - and informations on the query. - -*Pyro* - `Python Remote Object`_, distributed objects system similar to Java's RMI - (Remote Method Invocation), which can be used for the dialog between the web - side of the framework and the RQL repository. - -*query language* - A full-blown query language named RQL is used to formulate requests - to the database or any sources such as LDAP or `Google App Engine`'s - datastore. - -*views* - A view is applied to a `result set` to present it as HTML, XML, - JSON, CSV, etc. Views are implemented as Python classes. There is no - templating language. - -*generated user interface* - A user interface is generated on-the-fly from the schema definition: - entities can be created, displayed, updated and deleted. As display - views are not very fancy, it is usually necessary to develop your - own. Any generated view can be overridden by defining a new one with - the same identifier. - -*rql* - Relation Query Language in order to emphasize the way of browsing relations. - This query language is inspired by SQL but is on a higher level; - its implementation generates SQL. - - -.. _`Python Remote Object`: http://pyro.sourceforge.net/ -.. _`yams`: http://www.logilab.org/project/yams/ - - -`CubicWeb` engine -~~~~~~~~~~~~~~~~~ - -The engine in `CubicWeb` is a set of classes managing a set of objects loaded -dynamically at the startup of `CubicWeb` (*appobjects*). Those dynamic objects, -based on the schema or the library, are building the final application. -The different dynamic components are for example: - -* client and server side - - - entities definition, containing the logic which enables application data manipulation - -* client side - - - *views*, or more specifically - - - boxes - - header and footer - - forms - - page templates - - - *actions* - - *controllers* - -* server side - - - notification hooks - - notification views - -The components of the engine are: - -* a frontal web (only twisted is available so far), transparent for dynamic objects -* an object that encapsulates the configuration -* a `registry` (`cubicweb.cwvreg`) containing the dynamic objects loaded automatically - -Every *appobject* may access to the instance configuration using its *config* attribute -and to the registry using its *vreg* attribute. - -API Python/RQL -~~~~~~~~~~~~~~ - -The Python API developped to interface with RQL is inspired from the standard db-api, -with a Connection object having the methods cursor, rollback and commit essentially. -The most important method is the `execute` method of a cursor : - -`execute(rqlstring, args=None, eid_key=None, build_descr=True)` - -:rqlstring: the RQL query to execute (unicode) -:args: if the query contains substitutions, a dictionary containing the values to use -:eid_key: - an implementation detail of the RQL cache implies that if a substitution - is used to introduce an eid *susceptible to raise the ambiguities in the query - type resolution*, then we have to specify the corresponding key in the dictionary - through this argument - - -The `Connection` object owns the methods `commit` and `rollback`. You *should -never need to use them* during the development of the web interface based on -the `CubicWeb` framework as it determines the end of the transaction depending -on the query execution success. - -.. note:: - While executing update queries (SET, INSERT, DELETE), if a query generates - an error related to security, a rollback is automatically done on the current - transaction. - - -The `Request` class (`cubicweb.web`) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A request instance is created when an HTTP request is sent to the web server. -It contains informations such as form parameters, user authenticated, etc. - -**Globally, a request represents a user query, either through HTTP or not -(we also talk about RQL queries on the server side for example).** - -An instance of `Request` has the following attributes: - -* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated - user -* `form`, dictionary containing the values of a web form -* `encoding`, character encoding to use in the response - -But also: - -:Session data handling: - * `session_data()`, returns a dictionary containing all the session data - * `get_session_data(key, default=None)`, returns a value associated to the given - key or the value `default` if the key is not defined - * `set_session_data(key, value)`, assign a value to a key - * `del_session_data(key)`, suppress the value associated to a key - - -:Cookies handling: - * `get_cookie()`, returns a dictionary containing the value of the header - HTTP 'Cookie' - * `set_cookie(cookie, key, maxage=300)`, adds a header HTTP `Set-Cookie`, - with a minimal 5 minutes length of duration by default (`maxage` = None - returns a *session* cookie which will expire when the user closes the browser - window) - * `remove_cookie(cookie, key)`, forces a value to expire - -:URL handling: - * `url()`, returns the full URL of the HTTP request - * `base_url()`, returns the root URL of the web application - * `relative_path()`, returns the relative path of the request - -:And more...: - * `set_content_type(content_type, filename=None)`, adds the header HTTP - 'Content-Type' - * `get_header(header)`, returns the value associated to an arbitrary header - of the HTTP request - * `set_header(header, value)`, adds an arbitrary header in the response - * `cursor()` returns a RQL cursor on the session - * `execute(*args, **kwargs)`, shortcut to ``.cursor().execute()`` - * `property_value(key)`, properties management (`EProperty`) - * dictionary `data` to store data to share informations between components - *while a request is executed* - -Please note that this class is abstract and that a concrete implementation -will be provided by the *frontend* web used (in particular *twisted* as of -today). For the views or others that are executed on the server side, -most of the interface of `Request` is defined in the session associated -to the client. - -The `AppObject` class -~~~~~~~~~~~~~~~~~~~~~ - -In general: - -* we do not inherit directly from this class but from a more specific - class such as `AnyEntity`, `EntityView`, `AnyRsetView`, - `Action`... - -* to be recordable, a subclass has to define its own register (attribute - `__registry__`) and its identifier (attribute `id`). Usually we do not have - to take care of the register, only the identifier `id`. - -We can find a certain number of attributes and methods defined in this class -and common to all the application objects. - -At the recording, the following attributes are dynamically added to -the *subclasses*: - -* `vreg`, the `vregistry` of the application -* `schema`, the application schema -* `config`, the application configuration - -We also find on instances, the following attributes: - -* `req`, `Request` instance -* `rset`, the *result set* associated to the object if necessary -* `cursor`, rql cursor on the session - - -:URL handling: - * `build_url(method=None, **kwargs)`, returns an absolute URL based on - the given arguments. The *controller* supposed to handle the response, - can be specified through the special parameter `method` (the connection - is theoretically done automatically :). - - * `datadir_url()`, returns the directory of the application data - (contains static files such as images, css, js...) - - * `base_url()`, shortcut to `req.base_url()` - - * `url_quote(value)`, version *unicode safe* of the function `urllib.quote` - -:Data manipulation: - - * `etype_rset(etype, size=1)`, shortcut to `vreg.etype_rset()` - - * `eid_rset(eid, rql=None, descr=True)`, returns a *result set* object for - the given eid - * `entity(row, col=0)`, returns the entity corresponding to the data position - in the *result set* associated to the object - - * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but - also call the method `complete()` on the entity before returning it - -:Data formatting: - * `format_date(date, date_format=None, time=False)` returns a string for a - mx date time according to application's configuration - * `format_time(time)` returns a string for a mx date time according to - application's configuration - -:And more...: - - * `external_resource(rid, default=_MARKER)`, access to a value defined in the - configuration file `external_resource` - - * `tal_render(template, variables)`, renders a precompiled page template with - variables in the given dictionary as context - -.. note:: - When we inherit from `AppObject` (even not directly), you *always* have to use - **super()** to get the methods and attributes of the superclasses, and not - use the class identifier. - For example, instead of writting: :: - - class Truc(PrimaryView): - def f(self, arg1): - PrimaryView.f(self, arg1) - - You'd better write: :: - - class Truc(PrimaryView): - def f(self, arg1): - super(Truc, self).f(arg1) - -.. _cubesConcepts: - -Cubes -~~~~~ - -What is a cube ? -```````````````` - -A cube is a model grouping one or more entity types and/or views associated -in order to provide a specific feature or even a complete application using -other cubes. - -You can decide to write your own set of cubes if you wish to re-use the -entity types you develop. Lots of cubes are available from the `CubicWeb -Forge`_ under a free software license. - -.. _`CubicWeb Forge`: http://www.cubicweb.org/project/ - -.. _foundationsCube: - -Standard structure for a cube -````````````````````````````` - -A cube is structured as follows: - -:: - - mycube/ - | - |-- data/ - | |-- cubes.mycube.css - | |-- cubes.mycube.js - | `-- external_resources - | - |-- debian/ - | |-- changelog - | |-- compat - | |-- control - | |-- copyright - | |-- cubicweb-mycube.prerm - | `-- rules - | - |-- entities.py - | - |-- i18n/ - | |-- en.po - | `-- fr.po - | - |-- __init__.py - | - |-- MANIFEST.in - | - |-- migration/ - | |-- postcreate.py - | `-- precreate.py - | - |-- __pkginfo__.py - | - |-- schema.py - | - |-- setup.py - | - |-- site_cubicweb.py - | - |-- hooks.py - | - |-- test/ - | |-- data/ - | | `-- bootstrap_cubes - | |-- pytestconf.py - | |-- realdb_test_mycube.py - | `-- test_mycube.py - | - `-- views.py - - -We can use subpackages instead of python modules for ``views.py``, ``entities.py``, -``schema.py`` or ``hooks.py``. For example, we could have: - -:: - - mycube/ - | - |-- entities.py - |-- hooks.py - `-- views/ - |-- forms.py - |-- primary.py - `-- widgets.py - - -where : - -* ``schema`` contains the schema definition (server side only) -* ``entities`` contains the entities definition (server side and web interface) -* ``sobjects`` contains hooks and/or views notifications (server side only) -* ``views`` contains the web interface components (web interface only) -* ``test`` contains tests related to the application (not installed) -* ``i18n`` contains message catalogs for supported languages (server side and - web interface) -* ``data`` contains data files for static content (images, css, javascripts) - ...(web interface only) -* ``migration`` contains initialization file for new instances (``postcreate.py``) - and a file containing dependencies of the component depending on the version - (``depends.map``) -* ``debian`` contains all the files managing debian packaging (you will find - the usual files ``control``, ``rules``, ``changelog``... not installed) -* file ``__pkginfo__.py`` provides component meta-data, especially the distribution - and the current version (server side and web interface) or sub-cubes used by - the cube. - - -At least you should have: - -* the file ``__pkginfo__.py`` -* the schema definition - XXX false, we may want to have cubes which are only adding a service, - no persistent data (eg embedding for instance) - - -Standard library -```````````````` - -A library of standard cubes are available from `CubicWeb Forge`_ -Cubes provide entities and views. - -The available application entities are: - -* addressbook_: PhoneNumber and PostalAddress - -* basket_: Basket (like a shopping cart) - -* blog_: Blog (a *very* basic blog) - -* comment_: Comment (to attach comment threads to entities) - -* event_: Event (define events, display them in calendars) - -* file_: File (to allow users to upload and store binary or text files) - -* folder_: Folder (to organize things but grouping them in folders) - -* keyword_: Keyword (to define classification schemes) - -* link_: Link (to collect links to web resources) - -* mailinglist_: MailingList (to reference a mailing-list and the URLs - for its archives and its admin interface) - -* person_: Person (easily mixed with addressbook) - -* tag_: Tag (to tag anything) - -* task_: Task (something to be done between start and stop date) - -* zone_: Zone (to define places within larger places, for example a - city in a state in a country) - -.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook -.. _basket: http://www.cubicweb.org/project/cubicweb-basket -.. _blog: http://www.cubicweb.org/project/cubicweb-blog -.. _comment: http://www.cubicweb.org/project/cubicweb-comment -.. _event: http://www.cubicweb.org/project/cubicweb-event -.. _file: http://www.cubicweb.org/project/cubicweb-file -.. _folder: http://www.cubicweb.org/project/cubicweb-folder -.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword -.. _link: http://www.cubicweb.org/project/cubicweb-link -.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist -.. _person: http://www.cubicweb.org/project/cubicweb-person -.. _tag: http://www.cubicweb.org/project/cubicweb-tag -.. _task: http://www.cubicweb.org/project/cubicweb-task -.. _zone: http://www.cubicweb.org/project/cubicweb-zone diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0-data-model.en.txt --- a/doc/book/en/B0-data-model.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -.. -*- coding: utf-8 -*- - -The data model -++++++++++++++ - -.. toctree:: - :maxdepth: 1 - - B0010-define-schema.en.txt - B0020-define-workflows.en.txt - B0030-data-as-objects.en.txt - B0040-migration.en.txt - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0010-define-schema.en.txt --- a/doc/book/en/B0010-define-schema.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -.. -*- coding: utf-8 -*- - -Data model definition: the *schema* -=================================== - -The **schema** is the core piece of a `CubicWeb` application as it defines -the handled data model. It is based on entity types that are either already -defined in the `CubicWeb` standard library; or more specific types, that -`CubicWeb` expects to find in one or more Python files under the directory -`schema`. - -At this point, it is important to make clear the difference between -*relation type* and *relation definition*: a *relation type* is only a relation -name with potentially other additionnal properties (see XXXX), whereas a -*relation definition* is a complete triplet -" ". -A relation type could have been implied if none is related to a -relation definition of the schema. - - -.. include:: B0011-schema-stdlib.en.txt -.. include:: B0012-schema-definition.en.txt - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0011-schema-stdlib.en.txt --- a/doc/book/en/B0011-schema-stdlib.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -.. -*- coding: utf-8 -*- - -Pre-defined schemas in the library ----------------------------------- - -The library defines a set of entity schemas that are required by the system -or commonly used in `CubicWeb` applications. -Of course, you can extend those schemas if necessary. - - -System schemas -`````````````` -The available system entities are: - -* `EUser`, system users -* `EGroup`, users groups -* `EEType`, entity type -* `ERType`, relation type - -* `State`, workflow state -* `Transition`, workflow transition -* `TrInfo`, record of a transition trafic for an entity - -* `EmailAddress`, email address, used by the system to send notifications - to the users and also used by others optionnals schemas - -* `EProperty`, used to configure the application -* `EPermission`, used to configure the security of the application - -* `Card`, generic documenting card -* `Bookmark`, an entity type used to allow a user to customize his links within - the application - -(The first 'E' in some of the names is the first letter of 'Erudi', -`CubicWeb`'s old name; it might be changed/removed some day.) - -Available cubes -``````````````` - -An application is based on several basic cubes. In the set of available -basic cubes we can find for example : - -* addressbook_: PhoneNumber and PostalAddress - -* basket_: Basket (like a shopping cart) - -* blog_: Blog (a *very* basic blog) - -* comment_: Comment (to attach comment threads to entities) - -* email_: archiving management for emails (`Email`, `Emailpart`, - `Emailthread`) - -* event_: Event (define events, display them in calendars) - -* file_: File (to allow users to upload and store binary or text files) - -* folder_: Folder (to organize things but grouping them in folders) - -* keyword_: Keyword (to define classification schemes) - -* link_: Link (to collect links to web resources) - -* mailinglist_: MailingList (to reference a mailing-list and the URLs - for its archives and its admin interface) - -* person_: Person (easily mixed with addressbook) - -* tag_: Tag (to tag anything) - -* task_: Task (something to be done between start and stop date) - -* zone_: Zone (to define places within larger places, for example a - city in a state in a country) - -.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook -.. _basket: http://www.cubicweb.org/project/cubicweb-basket -.. _blog: http://www.cubicweb.org/project/cubicweb-blog -.. _comment: http://www.cubicweb.org/project/cubicweb-comment -.. _email: http://www.cubicweb.org/project/cubicweb-email -.. _event: http://www.cubicweb.org/project/cubicweb-event -.. _file: http://www.cubicweb.org/project/cubicweb-file -.. _folder: http://www.cubicweb.org/project/cubicweb-folder -.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword -.. _link: http://www.cubicweb.org/project/cubicweb-link -.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist -.. _person: http://www.cubicweb.org/project/cubicweb-person -.. _tag: http://www.cubicweb.org/project/cubicweb-tag -.. _task: http://www.cubicweb.org/project/cubicweb-task -.. _zone: http://www.cubicweb.org/project/cubicweb-zone - -To declare the use of a component, once installed, add the name of the component -to the variable `__use__` in the file `__pkginfo__.py` of your own component. - -.. note:: - The listed cubes above are available as debian-packages on `CubicWeb's forge`_. - -.. _`CubicWeb's forge`: http://www.cubicweb.org/project?vtitle=All%20cubicweb%20projects diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0012-schema-definition.en.txt --- a/doc/book/en/B0012-schema-definition.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,430 +0,0 @@ -.. -*- coding: utf-8 -*- - -Entity type definition ----------------------- - -An entity type is defined by a Python class which inherits from `EntityType`. -The class definition contains the description of attributes and relations -for the defined entity type. -The class name corresponds to the entity type name. It is exepected to be -defined in the module ``mycube.schema``. - - -For example :: - - class Person(EntityType): - """A person with the properties and the relations necessary for my - application""" - - last_name = String(required=True, fulltextindexed=True) - first_name = String(required=True, fulltextindexed=True) - title = String(vocabulary=('Mr', 'Mrs', 'Miss')) - date_of_birth = Date() - works_for = SubjectRelation('Company', cardinality='?*') - - -The entity described above defines three attributes of type String, -last_name, first_name and title, an attribute of type Date for the date of -birth and a relation that connects a `Person` to another entity of type -`Company` through the semantic `works_for` (:ref:`relations`). - -The name of the Python attribute corresponds to the name of the attribute -or the relation in `CubicWeb` application. - -Built-in types for attributes -````````````````````````````` - -All `CubicWeb` built-in types are available : `String`, `Int`, `Float`, -`Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte` -and `Password`. -They are implicitely imported (as well as the special the function "_" -for translation :ref:`internationalisation`). - -An attribute is defined in the schema as follows:: - - attr_name = attr_type(properties*) - -where `attr_type` is one of the type listed above and `properties` is -a list of properties the attribute will comply to (see :ref:`properties` -for more details). - - -Meta-data -````````` - -Each entity type has at least the following meta-relations : - - - `eid` (`Int`): provides the unique numeric identifier of an entity - - - `creation_date` (`Datetime`): date on which the entity has been created - - - `modification_date` (`Datetime`: date on which the entity has been last modified - - - `created_by` (`EUser`): which user created the entity - - - `owned_by` (`EUser`): to whom the entity belongs; by default the - creator but not necessary, and it could have multiple owners - - - `is` (`EEType`): of which type the entity is - -.. _relations: - -Relation definition -``````````````````` - -There are two types of relation you can use to define an entity type: -`ObjectRelation` or `SubjectRelation`. -The first argument of `SubjectRelation` or `ObjectRelation` gives respectively -the object/subject entity type of the relation. This could be : - - * a string corresponding to an entity type - - * a tuple of string corresponding to multiple entity types - - * special string such as follows : - - - "**" : all types of entities - - "*" : all types of non-meta entities - - "@" : all types of meta entities but not system entities (e.g. used for - the basic schema description) - -.. it is possible to use the attribute `meta` to flag an entity type as a `meta` - (e.g. used to describe/categorize other entities) - -.. _properties: - -Optionnal properties -```````````````````` - -Follows the list of properties that are available for defining -attribute and relation of a new entity type. - -* Optional properties for attributes and relations : - - - `description` : a string describing an attribute or a relation. By default - this string will be used in the editing form of the entity, which means - that it is supposed to help the end-user and should be flagged by the - function `_` to be properly internationalized. - - - `constraints` : a list of conditions/constraints that the relation has to - satisfy (c.f. `Contraints`_) - - - `cardinality` : a two character string which specify the cardinality of the - relation. The first character defines the cardinality of the relation on - the subject, and the second on the object. When a relation can have - multiple subjects or objects, the cardinality applies to all, - not on a one-to-one basis (so it must be consistent...). The possible - values are inspired from regular expression syntax : - - * `1`: 1..1 - * `?`: 0..1 - * `+`: 1..n - * `*`: 0..n - - - `meta` : boolean indicating that the relation is a meta-relation (false by - default) - -* Optional properties for attributes : - - - `required` : boolean indicating if the attribute is required (false by default) - - - `unique` : boolean indicating if the value of the attribute has to be unique - or not within all entities of the same type (false by default) - - - `indexed` : boolean indicating if an index needs to be created for this - attribute in the database (false by default). This is useful only if - you know that you will have to run numerous searches on the value of this - attribute. - - - `default` : default value of the attribute. In case of date types, the values - which could be used correspond to the RQL keywords `TODAY` and `NOW`. - - - `vocabulary` : specify static possible values of an attribute - -* Optional properties of type `String` : - - - `fulltextindexed` : boolean indicating if the attribute is part of - the full text index (false by default) (*applicable on the type `Byte` - as well*) - - - `internationalizable` : boolean indicating if the value of the attribute - is internationalizable (false by default) - - - `maxsize` : integer providing the maximum size of the string (no limit by default) - -* Optional properties for relations : - - - `composite` : string indicating that the subject (composite == 'subject') - is composed of the objects of the relations. For the opposite case (when - the object is composed of the subjects of the relation), we just set - 'object' as value. The composition implies that when the relation - is deleted (so when the composite is deleted), the composed are also deleted. - -Constraints -``````````` -By default, the available constraint types are : - -* `SizeConstraint` : allows to specify a minimum and/or maximum size on - string (generic case of `maxsize`) - -* `BoundConstraint` : allows to specify a minimum and/or maximum value on - numeric types - -* `UniqueConstraint` : identical to "unique=True" - -* `StaticVocabularyConstraint` : identical to "vocabulary=(...)" - -* `RQLConstraint` : allows to specify a RQL query that has to be satisfied - by the subject and/or the object of the relation. In this query the variables - `S` and `O` are reserved for the entities subject and object of the - relation. - -* `RQLVocabularyConstraint` : similar to the previous type of constraint except - that it does not express a "strong" constraint, which means it is only used to - restrict the values listed in the drop-down menu of editing form, but it does - not prevent another entity to be selected. - - -Definition of relations ------------------------ - -XXX add note about defining relation type / definition - -A relation is defined by a Python class heriting `RelationType`. The name -of the class corresponds to the name of the type. The class then contains -a description of the properties of this type of relation, and could as well -contain a string for the subject and a string for the object. This allows to create -new definition of associated relations, (so that the class can have the -definition properties from the relation) for example :: - - class locked_by(RelationType): - """relation on all entities indicating that they are locked""" - inlined = True - cardinality = '?*' - subject = '*' - object = 'EUser' - -In addition to the permissions, the properties of the relation types -(shared also by all definition of relation of this type) are : - - -* `inlined` : boolean handling the physical optimization for archiving - the relation in the subject entity table, instead of creating a specific - table for the relation. This applies to the relation when the cardinality - of subject->relation->object is 0..1 (`?`) or 1..1 (`1`) - -* `symmetric` : boolean indicating that the relation is symmetrical, which - means `X relation Y` implies `Y relation X` - -In the case of simultaneous relations definitions, `subject` and `object` -can both be equal to the value of the first argument of `SubjectRelation` -and `ObjectRelation`. - -When a relation is not inlined and not symmetrical, and it does not require -specific permissions, its definition (by using `SubjectRelation` and -`ObjectRelation`) is all we need. - -.. _security: - -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. - - -Updating your application with your new schema -`````````````````````````````````````````````` - -If you modified your schema, the update is not automatic; indeed, this is -in general not a good idea. -Instead, you call a shell on your application, which is a -an interactive python shell, with an appropriate -cubicweb environment :: - - cubicweb-ctl shell myinstance - -and type :: - - add_entity_type('Person') - -And restart your application! diff -r 49075f57cf2c -r aa09e20dd8c0 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 14 12:48:11 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 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0020-define-workflows.en.txt --- a/doc/book/en/B0020-define-workflows.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _Workflow: - -An Example: Workflow definition -=============================== - -General -------- - -A workflow describes how certain entities have to evolve between -different states. Hence we have a set of states, and a "transition graph", -i.e. a list of possible transitions from one state to another state. - -We will define a simple workflow for a blog, with only the following -two states: `submitted` and `published`. So first, we create a simple -`CubicWeb` in ten minutes (see :ref:`BlogTenMinutes`). - -Set-up a workflow ------------------ - -We want to create a workflow to control the quality of the BlogEntry -submitted on your application. When a BlogEntry is created by a user -its state should be `submitted`. To be visible to all, it has to -be in the state `published`. To move it from `submitted` to `published`, -we need a transition that we can call `approve_blogentry`. - -A BlogEntry state should not be modifiable by every user. -So we have to define a group of users, `moderators`, and -this group will have appropriate permissions to publish a BlogEntry. - -There are two ways to create a workflow: from the user interface, -or by defining it in ``migration/postcreate.py``. -This script is executed each time a new ``cubicweb-ctl db-init`` is done. -We strongly recommand to create the workflow in ``migration/postcreate.py`` -and we will now show you how. Read `Under the hood`_ to understand why. - -Update the schema -~~~~~~~~~~~~~~~~~ -If we want a State for our BlogEntry, we have to define a relation -``in_state`` in the schema of BlogEntry. So we add -the line ``in_state (...)``:: - - class BlogEntry(EntityType): - title = String(maxsize=100, required=True) - publish_date = Date(default='TODAY') - text_format = String(meta=True, internationalizable=True, maxsize=50, - default='text/rest', constraints=[format_constraint]) - text = String(fulltextindexed=True) - category = String(vocabulary=('important','business')) - entry_of = SubjectRelation('Blog', cardinality='?*') - in_state = SubjectRelation('State', cardinality='1*') - -As you updated the schema, you have to re-execute ``cubicweb-ctl db-init`` -to initialize the database and migrate your existing entities. - -[WRITE ABOUT MIGRATION] - -Create states, transitions and group permissions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``postcreate.py`` script is executed in a special environment, adding -several `CubicWeb` primitives that can be used. -They are all defined in the ``class ServerMigrationHelper``. -We will only discuss the methods we use to create a workflow in this example. - -To define our workflow for BlogDemo, please add the following lines -to ``migration/postcreate.py``:: - - _ = unicode - - moderators = add_entity('EGroup', name=u"moderators") - -This adds the `moderators` user group. - -:: - - submitted = add_state(_('submitted'), 'BlogEntry', initial=True) - published = add_state(_('published'), 'BlogEntry') - -``add_state`` expects as first argument the name of the state you want -to create, then the entity type on which the state can be applied, -and an optional argument to say if it is supposed to be the initial state -of the entity type. - -:: - - add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),) - - -``add_transition`` expects - - * as the first argument the name of the - transition, then the entity type on which the transition can be applied, - * then the list of states on which the transition can be trigged, - * the target state of the transition, - * and the permissions - (e.g. a list of user groups who can apply the transition; the user - has to belong to at least one of the listed group to perform the action). - -:: - - checkpoint() - -.. note:: - Do not forget to add the `_()` in front of all states and transitions names while creating - a workflow so that they will be identified by the i18n catalog scripts. - -In addition to the user group condition, we could have added a RQL condition. -In this case, the user can only perform the action if -the two conditions are satisfied. - -If we use a RQL condition on a transition, we can use the following -variables: - -* `%(eid)s`, object's eid -* `%(ueid)s`, user executing the query eid -* `%(seid)s`, the object's current state eid - - -.. image:: images/lax-book.03-transitions-view.en.png - -You can notice that in the action box of a BlogEntry, the state -is now listed as well as the possible transitions defined by the workflow. -The transitions will only be displayed for users having the right permissions. -In our example, the transition `approve_blogentry` will only be displayed -for the users belonging to the group `moderators` or `managers`. - - -Under the hood -~~~~~~~~~~~~~~ - -A workflow is a collection of entities of type ``State`` and of type ``Transition`` -which are standard `CubicWeb` entity types. -For instance, the following lines:: - - submitted = add_state(_('submitted'), 'BlogEntry', initial=True) - published = add_state(_('published'), 'BlogEntry') - -will create two entities of type ``State``, one with name 'submitted', and the other -with name 'published'. Whereas:: - - add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),) - -will create an entity of type ``Transition`` with name 'approve_blogentry' which will -be linked to the ``State`` entities created before. -As a consequence, we could use the administration interface to do these operations. -But it is not recommanded because it will be uselessly complicated -and will be only local to your instance. - - -Indeed, if you create the states and transitions through the user interface, -next time you initialize the database -you will have to re-create all the entities. -The user interface should only be a reference for you to view the states -and transitions, but is not the appropriate interface to define your -application workflow. - - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0030-data-as-objects.en.txt --- a/doc/book/en/B0030-data-as-objects.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +0,0 @@ -.. -*- coding: utf-8 -*- - - -Data as objects -=============== - -In this chapter, we will introduce the objects that are used to handle -the data stored in the database. - -Class `Entity` and `AnyEntity` ------------------------------- - -To provide a specific behavior for each entity, we have to define -a class inheriting from `cubicweb.entities.AnyEntity`. In general, we -define this class in a module of `mycube.entities` package of an application -so that it will be available on both server and client side. - -The class `AnyEntity` is loaded dynamically from the class `Entity` -(`cubciweb.common.entity`). We define a sub-class to add methods or to -specialize the handling of a given entity type - -Descriptors are added when classes are registered in order to initialize the class -according to its schema: - -* we can access the defined attributes in the schema thanks to the attributes of - the same name on instances (typed value) - -* we can access the defined relations in the schema thanks to the relations of - the same name on instances (entities instances list) - -The methods defined for `AnyEntity` or `Entity` are the following ones: - -* `has_eid()`, returns true is the entity has an definitive eid (e.g. not in the - creation process) - -* `check_perm(action)`, checks if the user has the permission to execute the - requested action on the entity - -:Formatting and output generation: - - * `view(vid, **kwargs)`, applies the given view to the entity - - * `absolute_url(**kwargs)`, returns an absolute URL to access the primary view - of an entity - - * `rest_path()`, returns a relative REST URL to get the entity - - * `format(attr)`, returns the format (MIME type) of the field given un parameter - - * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`, - returns a string enabling the display of an attribute value in a given format - (the value is automatically recovered if necessary) - - * `display_name(form='')`, returns a string to display the entity type by - specifying the preferred form (`plural` for a plural form) - -:Data handling: - - * `as_rset()`, converts the entity into an equivalent result set simulating the - request `Any X WHERE X eid _eid_` - - * `complete(skip_bytes=True)`, executes a request that recovers in one time - all the missing attributes of an entity - - * `get_value(name)`, returns the value associated to the attribute name given - in parameter - - * `related(rtype, x='subject', limit=None, entities=False)`, returns a list - of entities related to the current entity by the relation given in parameter - - * `unrelated(rtype, targettype, x='subject', limit=None)`, returns a result set - corresponding to the entities not related to the current entity by the - relation given in parameter and satisfying its constraints - - * `set_attributes(**kwargs)`, updates the attributes list with the corresponding - values given named parameters - - * `copy_relations(ceid)`, copies the relations of the entities having the eid - given in the parameters on the current entity - - * `last_modified(view)`, returns the date the object has been modified - (used by HTTP cache handling) - - * `delete()` allows to delete the entity - -:Standard meta-data (Dublin Core): - - * `dc_title()`, returns a unicode string corresponding to the meta-data - `Title` (used by default the first attribute non-meta of the entity - schema) - - * `dc_long_title()`, same as dc_title but can return a more - detailled title - - * `dc_description(format='text/plain')`, returns a unicode string - corresponding to the meta-data `Description` (look for a description - attribute by default) - - * `dc_authors()`, returns a unicode string corresponding to the meta-data - `Authors` (owners by default) - - * `dc_date(date_format=None)`, returns a unicode string corresponding to - the meta-data `Date` (update date by default) - -:Vocabulary control on relations: - - * `vocabulary(rtype, x='subject', limit=None)`, called by the - editing views, it returns a list of couples (label, eid) of entities - that could be related to the entity by the relation `rtype` - * `subject_relation_vocabulary(rtype, limit=None)`, called internally - by `vocabulary` in the case of a subject relation - * `object_relation_vocabulary(rtype, limit=None)`, called internally - by `vocabulary` in the case of an object relation - * `relation_vocabulary(rtype, targettype, x, limit=None)`, called - internally by `subject_relation_vocabulary` and `object_relation_vocabulary` - -Class `TreeMixIn` ------------------ - -This class provides a tree interface. This mixin has to be inherited -explicitly and configured using the tree_attribute, parent_target and -children_target class attribute to benefit from this default implementation. - -This class provides the following methods: - - * `different_type_children(entities=True)`, returns children entities - of different type as this entity. According to the `entities` parameter, - returns entity objects (if entity=True) or the equivalent result set. - - * `same_type_children(entities=True)`, returns children entities of - the same type as this entity. According to the `entities` parameter, - return entity objects (if entity=True) or the equivalent result set. - - * `iterchildren( _done=None)`, iters on the children of the entity. - - * `prefixiter( _done=None)` - - * `path()`, returns the list of eids from the root object to this object. - - * `iterparents()`, iters on the parents of the entity. - - * `notification_references(view)`, used to control References field - of email send on notification for this entity. `view` is the notification view. - Should return a list of eids which can be used to generate message ids - of previously sent email. - -`TreeMixIn` implements also the ITree interface (``cubicweb.interfaces``): - - * `parent()`, returns the parent entity if any, else None (e.g. if we are on the - root) - - * `children(entities=True, sametype=False)`, returns children entities - according to the `entities` parameter, return entity objects or the - equivalent result set. - - * `children_rql()`, returns the RQL query corresponding to the children - of the entity. - - * `is_leaf()`, returns True if the entity does not have any children. - - * `is_root()`, returns True if the entity does not have any parent. - - * `root()`, returns the root object of the tree representation of - the entity and its related entities. - -Example of use -`````````````` - -Imagine you defined three types of entities in your schema, and they -relates to each others as follows in ``schema.py``:: - - class Entity1(EntityType): - title = String() - is_related_to = SubjectRelation('Entity2', 'subject') - - class Entity2(EntityType): - title = String() - belongs_to = SubjectRelation('Entity3', 'subject') - - class Entity3(EntityType): - name = String() - -You would like to create a view that applies to both entity types -`Entity1` and `Entity2` and which lists the entities they are related to. -That means when you view `Entity1` you want to list all `Entity2`, and -when you view `Entity2` you want to list all `Entity3`. - -In ``entities.py``:: - - class Entity1(TreeMixIn, AnyEntity): - id = 'Entity1' - __implements__ = AnyEntity.__implements__ + (ITree,) - __rtags__ = {('is_related_to', 'Entity2', 'object'): 'link'} - tree_attribute = 'is_related_to' - - def children(self, entities=True): - return self.different_type_children(entities) - - class Entity2(TreeMixIn, AnyEntity): - id = 'Entity2' - __implements__ = AnyEntity.__implements__ + (ITree,) - __rtags__ = {('belongs_to', 'Entity3', 'object'): 'link'} - tree_attribute = 'belongs_to' - - def children(self, entities=True): - return self.different_type_children(entities) - -Once this is done, you can define your common view as follows:: - - class E1E2CommonView(baseviews.PrimaryView): - accepts = ('Entity11, 'Entity2') - - def render_entity_relations(self, entity, siderelations): - self.wview('list', entity.children(entities=False)) - - -*rtags* -------- - -*rtags* allow to specify certain behaviors of relations relative to a given -entity type (see later). They are defined on the entity class by the attribute -`rtags` which is a dictionary with as keys the triplets :: - - , , - -and as values a `set` or a tuple of markers defining the properties that -apply to this relation. - -It is possible to simplify this dictionary: - -* if we want to specifiy a single marker, it is not necessary to - use a tuple as value, the marker by itself (character string) - is enough -* if we only care about a single type of relation and not about the target - and the context position (or when this one is not ambigous), we can simply - use the name of the relation type as key -* if we want a marker to apply independently from the target entity type, - we have to use the string `*` as target entity type - - -Please note that this dictionary is *treated at the time the class is created*. -It is automatically merged with the parent class(es) (no need to copy the -dictionary from the parent class to modify it). Also, modifying it after the -class is created will not have any effect... - -.. include:: B0031-define-entities.en.txt - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0031-define-entities.en.txt --- a/doc/book/en/B0031-define-entities.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -.. -*- coding: utf-8 -*- - -Parametrization and specific extensions ---------------------------------------- - -Dynamic default values -`````````````````````` -It is possible to define *static* default values in the schema. -It is also possible to define *dynamic* default values -by defining in the entity class a method `default_` for -a given attribute. - - -Loaded attributes and default sorting management -```````````````````````````````````````````````` - -* The class attribute `fetch_attrs` allows to defined in an entity class - a list of names of attributes or relations that should be automatically - loaded when we recover the entities of this type. In the case of relations, - we are limited to *subject of cardinality `?` or `1`* relations. - -* The class method `fetch_order(attr, var)` expects an attribute (or relation) - name as a parameter and a variable name, and it should return a string - to use in the requirement `ORDER BY` of an RQL query to automatically - sort the list of entities of such type according to this attribute, or - `None` if we do not want to sort on the attribute given in the parameter. - By default, the entities are sorted according to their creation date. - -* The class method `fetch_unrelated_order(attr, var)` is similar to the - method `fetch_order` except that it is essentially used to control - the sorting of drop-down lists enabling relations creation in - the editing view of an entity. - -The function `fetch_config(fetchattrs, mainattr=None)` simplifies the -definition of the attributes to load and the sorting by returning a -list of attributes to pre-load (considering automatically the attributes -of `AnyEntity`) and a sorting function based on the main attribute -(the second parameter if specified otherwisethe first attribute from -the list `fetchattrs`). -This function is defined in `cubicweb.entities`. - -For example: :: - - class Transition(AnyEntity): - """...""" - id = 'Transition' - fetch_attrs, fetch_order = fetch_config(['name']) - -Indicates that for the entity type "Transition", you have to pre-load -the attribute `name` and sort by default on this attribute. - - -Editing forms management -```````````````````````` -It is possible to manage attributes/relations in the simple or multiple -editing form thanks to the following *rtags*: - -* `primary`, indicates that an attribute or a relation has to be - inserted **in the simple or multiple editing forms**. In the case of - a relation, the related entity editing form will be included in the - editing form and represented as a combobox. Each item of the - combobox is a link to an existing entity. - -* `secondary`, indicates that an attribute or a relation has to be - inserted **in the simple editing form only**. In the case of a - relation, the related entity editing form will be included in the - editing form and represented as a combobox. Each item of the combobox - is a link to an existing entity. - -* `inlineview`, includes the target entity's form in the editing form - of the current entity. It allows to create the target entity in the - same time as the current entity. - -* `generic`, indicates that a relation has to be inserted in the simple - editing form, in the generic box of relation creation. - -* `generated`, indicates that an attribute is dynamically computed - or other, and that it should not be displayed in the editing form. - -If necessary, it is possible to overwrite the method -`relation_category(rtype, x='subject')` to dynamically compute -a relation editing category. - -``add_related`` box management -`````````````````````````````` - -The box ``add_related`` is an automatic box that allows to create -an entity automatically related to the initial entity (context in -which the box is displayed). By default, the links generated in this -box are computed from the schema properties of the displayed entity, -but it is possible to explicitely specify them thanks to the -following *rtags*: - -* `link`, indicates that a relation is in general created pointing - to an existing entity and that we should not to display a link - for this relation - -* `create`, indicates that a relation is in general created pointing - to new entities and that we should display a link to create a new - entity and link to it automatically - - -If necessary, it is possible to overwrite the method -`relation_mode(rtype, targettype, x='subject')` to dynamically -compute a relation creation category. - -Please note that if at least one action belongs to the `addrelated` category, -the automatic behavior is desactivated in favor of an explicit behavior -(e.g. display of `addrelated` category actions only). - - -Filtering table forms management -```````````````````````````````` - -By default, the view ``table`` manages automatically a filtering -form of its content. The algorithm is as follows: - -1. we consider that the first column contains the entities to constraint -2. we collect the first entity of the table (row 0) to represent all the - others -3. for all the other variables defined in the original request: - - 1. if the variable is related to the main variable by at least one relation - 2. we call the method ``filterform_vocabulary(rtype, x)`` on the entity, - if nothing is returned (meaning a tuple `Non`, see below), we go to the - next variable, otherwise a form filtering element is created based on - the vocabulary values returned - -4. there are no other limitations to the `RQL`, it can include sorting, grouping - conditions... JavaScript functions are used to regenerate a request based on the - initial request and on the selected values from the filtering form. - -The method ``filterform_vocabulary(rtype, x, var, rqlst, args, cachekey)`` takes -the name of a relation and the target as parameters, -[XXX what does it mean ?] -which indicates of the -entity on which we apply the method is subject or object of the relation. It -has to return: - -* a 2-uple of None if it does not know how to handle the relation - -* a type and a list containing the vocabulary - - * the list has to contain couples (value, label) - * the type indicates if the value designate an integer (`type == 'int'`), - a string (`type =='string'` or a non-final relation (`type == 'eid'`) - -For example in our application managing tickets, we want to be able to filter -them by : - -* type -* priority -* state (in_state) -* tag (tags) -* version (done_in) - -For that we define the following method: :: - - - class Ticket(AnyEntity): - - ... - - def filterform_vocabulary(self, rtype, x, var, rqlst, args, cachekey): - _ = self.req._ - if rtype == 'type': - return 'string', [(x, _(x)) for x in ('bug', 'story')] - if rtype == 'priority': - return 'string', [(x, _(x)) for x in ('minor', 'normal', 'important')] - if rtype == 'done_in': - rql = insert_attr_select_relation(rqlst, var, rtype, 'num') - return 'eid', self.req.execute(rql, args, cachekey) - return super(Ticket, self).filterform_vocabulary(rtype, x, var, rqlst, - args, cachekey) - -.. note:: - Filtering on state and tags is automatically installed, no need to handle it. - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B0040-migration.en.txt --- a/doc/book/en/B0040-migration.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,213 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _migration: - -Migration -========= - -One of the main concept in `CubicWeb` is to create incremental applications. -For this purpose, multiple actions are provided to facilitate the improvement -of an application, and in particular to handle the changes to be applied -to the data model, without loosing existing data. - -The current version of an application model is provided in the file -`__pkginfo__.py` as a tuple of 3 integers. - -Migration scripts management ----------------------------- - -Migration scripts has to be located in the directory `migration` of your -application and named accordingly: - -:: - - [_]_.py - -in which : - -* X.Y.Z is the model version number to which the script enables to migrate. - -* *mode* (between the last "_" and the extension ".py") is used for - distributed installation. It indicates to which part - of the application (RQL server, web server) the script applies. - Its value could be : - - * `common`, applies to the RQL server as well as the web server and updates - files on the hard drive (configuration files migration for example). - - * `web`, applies only to the web server and updates files on the hard drive. - - * `repository`, applies only to the RQL server and updates files on the - hard drive. - - * `Any`, applies only to the RQL server and updates data in the database - (schema and data migration for example). - -Again in the directory `migration`, the file `depends.map` allows to indicate -that for the migration to a particular model version, you always have to first -migrate to a particular `CubicWeb` version. This file can contain comments (lines -starting by `#`) and a dependancy is listed as follows: :: - - : - -For example: :: - - 0.12.0: 2.26.0 - 0.13.0: 2.27.0 - # 0.14 works with 2.27 <= cubicweb <= 2.28 at least - 0.15.0: 2.28.0 - -Base context ------------- - -The following identifiers are pre-defined in migration scripts: - -* `config`, instance configuration - -* `interactive_mode`, boolean indicating that the script is executed in - an interactive mode or not - -* `appltemplversion`, application model version of the instance - -* `templversion`, installed application model version - -* `cubicwebversion`, installed cubicweb version - -* `confirm(question)`, function asking the user and returning true - if the user answers yes, false otherwise (always returns true in - non-interactive mode) - -* the function `_`, it is equivalent to `unicode` allowing to flag the strings - to internationalize in the migration scripts. - -In the `repository` scripts, the following identifiers are also defined: - -* `checkpoint`, request confirming and executing a "commit" at checking point - -* `repo_schema`, instance persisting schema (e.g. instance schema of the - current migration) - -* `newschema`, installed schema on the file system (e.g. schema of - the updated model and cubicweb) - -* `sqlcursor`, SQL cursor for very rare cases where it is really - necessary or beneficial to go through the sql - -* `repo`, repository object - - -Schema migration ----------------- -The following functions for schema migration are available in `repository` -scripts: - -* `add_attribute(etype, attrname, attrtype=None, commit=True)`, adds a new - attribute to an existing entity type. If the attribute type is not specified, - then it is extracted from the updated schema. - -* `drop_attribute(etype, attrname, commit=True)`, removes an attribute from an - existing entity type. - -* `rename_attribute(etype, oldname, newname, commit=True)`, renames an attribute - -* `add_entity_type(etype, auto=True, commit=True)`, adds a new entity type. - If `auto` is True, all the relations using this entity type and having a known - entity type on the other hand will automatically be added. - -* `drop_entity_type(etype, commit=True)`, removes an entity type and all the - relations using it. - -* `rename_entity_type(oldname, newname, commit=True)`, renames an entity type - -* `add_relation_type(rtype, addrdef=True, commit=True)`, adds a new relation - type. If `addrdef` is True, all the relations definitions of this type will - be added. - -* `drop_relation_type(rtype, commit=True)`, removes a relation type and all the - definitions of this type. - -* `rename_relation(oldname, newname, commit=True)`, renames a relation. - -* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new - relation definition. - -* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes - a relation definition. - -* `synchronize_permissions(ertype, commit=True)`, synchronizes permissions on - an entity type or relation type. - -* `synchronize_rschema(rtype, commit=True)`, synchronizes properties and permissions - on a relation type. - -* `synchronize_eschema(etype, commit=True)`, synchronizes properties and persmissions - on an entity type. - -* `synchronize_schema(commit=True)`, synchronizes the persisting schema with the - updated schema (but without adding or removing new entity types, relations types - or even relations definitions). - -* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes - properties of a relation definition by using the named parameters of the properties - to change. - -* `set_widget(etype, rtype, widget, commit=True)`, changes the widget used for the - relation of entity type . - -* `set_size_constraint(etype, rtype, size, commit=True)`, changes the size constraints - for the relation of entity type . - -Data migration --------------- -The following functions for data migration are available in `repository` scripts: - -* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL - query, either to interrogate or update. A result set object is returned. - -* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given - type. The attribute and relation values are specified using the named and - positionned parameters. - -Workflow creation ------------------ - -The following functions for workflow creation are available in `repository` -scripts: - -* `add_state(name, stateof, initial=False, commit=False, **kwargs)`, adds a new state - in the workflow. - -* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`, - adds a new transition in the workflow. - -You can find more details about workflows in the chapter :ref:`Workflow` . - -Configuration migration ------------------------ - -The following functions for configuration migration are available in all -scripts: - -* `option_renamed(oldname, newname)`, indicates that an option has been renamed - -* `option_group_change(option, oldgroup, newgroup)`, indicates that an option does not - belong anymore to the same group. - -* `option_added(oldname, newname)`, indicates that an option has been added. - -* `option_removed(oldname, newname)`, indicates that an option has been deleted. - - -Others migration functions --------------------------- -Those functions are only used for low level operations that could not be -accomplished otherwise or to repair damaged databases during interactive -session. They are available in `repository` scripts: - -* `sqlexec(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query -* `add_entity_type_table(etype, commit=True)` -* `add_relation_type_table(rtype, commit=True)` -* `uninline_relation(rtype, commit=True)` - - -[FIXME] Add explanation on how to use cubicweb-ctl shell diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B081-i18n.en.txt --- a/doc/book/en/B081-i18n.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _Internationalisation: - - -Internationalisation -==================== - -Le système d'internationalisation de l'interface web de CubicWeb est basé sur le -système `GNU gettext`_. - -.. _`GNU gettext`: http://www.gnu.org/software/gettext/ - -Messages à internationaliser ----------------------------- - -Marquage des messages à internaliser -```````````````````````````````````` -Les chaines de caractères à internationaliser sont marqués par l'appel à la -fonction `_` *OU* par la méthode équivalent de la requête dans le code python ou -dans les expressions python de template TAL. - -Dans les templates cubicweb-tal, il est également possible d'insérer une chaine à -traduire via les balises `i18n:content` et `i18n:replace`. - -De plus des messages correspondant aux entités/relations utilisés par le schéma -de l'application seront automatiquement ajoutés. - -Renvoi d'un message internationalisé lors de la construction d'une page -``````````````````````````````````````````````````````````````````````` -La fonction *built-in* `_` ne doit servir qu'**à marquer les messages à -traduire**, non pas à récupérer une traduction. Il faut pour cela utiliser la -méthode `_` de l'objet requête, sans quoi vous récupérerez l'identifiant de -message au lieu de sa traduction dans la langue propre à la requête.1 - - -Gestion des catalogues de traduction ------------------------------------- -Une fois l'application rendu internationalisable coté code, reste à gérer les -catalogues de traductions. cubicweb-ctl intègre pour cela les commandes suivantes : - -* `i18nlibupdate`, met à jour les catalogues de messages *de la librairie - cubicweb*. Sauf si vous développez sur le framework (et non votre propre - application), vous ne devriez pas avoir à utiliser cette commande - -* `i18nupdate`, met à jour les catalogues de messages *du composant* (ou de tous - les composants). A la suite de cette commande, vous devez mettre à jour les - fichiers de traduction *.po* dans le sous-répertoire "i18n" de votre - template. Évidemment les traductions précédentes toujours utilisées ont été - conservées. - -* `i18ncompile`, recompile les catalogues de messages *d'une instance* (ou de - toutes les instances) après mise à jour des catalogues de son composant. Cela - est effectué automatiquement lors d'une création ou d'une mise à jour. Les - catalogues de messages compilés se trouvent dans le répertoire - "i18n//LC_MESSAGES/cubicweb.mo" de l'application où `lang` est - l'identifiant de la langue sur 2 lettres ('en' ou 'fr' par exemple) - - -Le cas classique -```````````````` -Vous avez ajouté et/ou modifié des messages d'un composant utilisé par votre -application (en ajoutant une nouvelle vue ou en ayant modifié le schéma par -exemple) : - -1. `cubicweb-ctl i18nupdate ` -2. éditer les fichiers /xxx.po dans pour y rajouter les traductions - manquantes (`msgstr` vide) -3. `hg ci -m "updated i18n catalogs"` -4. `cubicweb-ctl i18n compile ` - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1-web-interface.en.txt --- a/doc/book/en/B1-web-interface.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -.. -*- coding: utf-8 -*- - -Web interface -+++++++++++++ -.. toctree:: - :maxdepth: 1 - - B1010-request.en.txt - B1020-define-views.en.txt - B1030-form-management.en.txt - B1040-actions.en.txt - B1050-boxes.en.txt - B1060-templates.en.txt - B1070-ui-components.en.txt - B1080-ajax-json.en.txt - B1090-internationalization.en.txt - B1100-online-doc.en.txt - B1110-embedding-external-page.en.txt - B1120-urlrewrite.en.txt - B1130-css.en.txt diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1010-request.en.txt --- a/doc/book/en/B1010-request.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Request -======= - -[WRITE ME] - -* the request object - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1020-define-views.en.txt --- a/doc/book/en/B1020-define-views.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,414 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _ViewDefinition: - -Views definition -================ - -This chapter aims to describe the concept of a `view` used all along -the development of a web application and how it has been implemented -in `CubicWeb`. - -We'll start with a description of the interface providing you with a basic -understanding of the classes and methods available, then detail the view -selection principle which makes `CubicWeb` web interface very flexible. - -A `View` is an object applied to another object such as an entity. - -Basic class for views ---------------------- - -Class `View` (`cubicweb.common.view`) -````````````````````````````````````` - -This class is an abstraction of a view class, used as a base class for every -renderable object such as views, templates, graphic components, etc. - -A `View` is instantiated to render a result set or part of a result set. `View` -subclasses may be parametrized using the following class attributes: - - * `templatable` indicates if the view may be embeded in a main - template or if it has to be rendered standalone (i.e. XML views - must not be embeded in the main template for HTML pages) - * if the view is not templatable, it should set the `content_type` class - attribute to the correct MIME type (text/xhtml by default) - * the `category` attribute may be used in the interface to regroup related - objects together - -At instantiation time, the standard `req`, `rset`, and `cursor` -attributes are added and the `w` attribute will be set at rendering -time. - -A view writes to its output stream thanks to its attribute `w` (`UStreamIO`). - -The basic interface for views is as follows (remember that the result set has a -tabular structure with rows and columns, hence cells): - -* `dispatch(**context)`, render the view by calling `call` or - `cell_call` depending on the given parameters -* `call(**kwargs)`, call the view for a complete result set or null (default - implementation calls `cell_call()` on each cell of the result set) -* `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set -* `url()`, returns the URL enabling us to get the view with the current - result set -* `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier - `__vid` on the given result set. It is possible to give a view identifier - of fallback that will be used if the view requested is not applicable to the - result set - -* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except - the flow is automatically passed in the parameters - -* `html_headers()`, returns a list of HTML headers to set by the main template - -* `page_title()`, returns the title to use in the HTML header `title` - - -Other basic view classes -```````````````````````` -Here are some of the subclasses of `View` defined in `cubicweb.common.view` -that are more concrete as they relate to data rendering within the application: - -* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid) -* `StartupView`, start view that does not require a result set to apply to -* `AnyRsetView`, view applied to any result set -* `EmptyRsetView`, view applied to an empty result set - - -The selection view principle ----------------------------- - -A view is essentially defined by: - -- an identifier (all objects in `CubicWeb` are entered in a registry - and this identifier will be used as a key). This is defined in the class - attribute ``id``. - -- a filter to select the result sets it can be applied to. This is defined in - the class attribute ``__selectors__``, which expects a tuple of selectors - as its value. - - -For a given identifier, multiple views can be defined. `CubicWeb` uses -a selector which computes scores to identify and select the -best view to apply in the given context. The selectors library is in -``cubicweb.common.selector`` and a library of the methods used to -compute scores is in ``cubicweb.vregistry.vreq``. - -.. include:: B1021-views-selectors.en.txt - -Registerer -`````````` -[Registerers are deprecated: they will soon disappear for explicite -registration...] - -A view is also customizable through its attribute ``__registerer__``. -This is used at the time the application is launched to manage how -objects (views, graphic components, actions, etc.) -are registered in the `cubicWeb` registry. - -A `registerer` can, for example, identify when we register an -object that is equivalent to an already registered object, which -could happen when we define two `primary` views for an entity type. - -The purpose of a `registerer` is to control object registry -at the application startup whereas `selectors` control objects -when they are selected for display. - - -.. include:: B1022-views-stdlib.en.txt - - -Examples of views class ------------------------ - -- Using the attribute `templatable` - - :: - - - class RssView(XmlView): - id = 'rss' - title = _('rss') - templatable = False - content_type = 'text/xml' - http_cache_manager = MaxAgeHTTPCacheManager - cache_max_age = 60*60*2 # stay in http cache for 2 hours by default - - - -- Using the attribute `__selectors__` - - :: - - - class SearchForAssociationView(EntityView): - """view called by the edition view when the user asks - to search for something to link to the edited eid - """ - id = 'search-associate' - title = _('search for association') - __selectors__ = (one_line_rset, match_search_state, accept_selector) - accepts = ('Any',) - search_states = ('linksearch',) - - -Rendering methods and attributes for ``PrimaryView`` ----------------------------------------------------- - -By default, `CubicWeb` provides a primary view for each new entity type -you create. The first view you might be interested in modifying. - -Let's have a quick look at the EntityView ``PrimaryView`` as well as -its rendering method:: - - class PrimaryView(EntityView): - """the full view of an non final entity""" - id = 'primary' - title = _('primary') - show_attr_label = True - show_rel_label = True - skip_none = True - skip_attrs = ('eid', 'creation_date', 'modification_date') - skip_rels = () - main_related_section = True - - ... - - def cell_call(self, row, col): - self.row = row - self.render_entity(self.complete_entity(row, col)) - - 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 - self.w(u'
') - self.w(u'
') - self.render_entity_attributes(entity, siderelations) - self.w(u'
') - self.content_navigation_components('navcontenttop') - if self.main_related_section: - self.render_entity_relations(entity, siderelations) - self.w(u'
') - # side boxes - self.w(u'
') - self.render_side_related(entity, siderelations) - self.w(u'
') - self.w(u'
') - self.content_navigation_components('navcontentbottom') - - ... - -``cell_call`` is executed for each entity of a result set and apply ``render_entity``. - -The methods you want to modify while customizing a ``PrimaryView`` are: - -*render_entity_title(self, entity)* - Renders the entity title based on the assumption that the method - ``def content_title(self)`` is implemented for the given entity type. - -*render_entity_metadata(self, entity)* - Renders the entity metadata based on the assumption that the method - ``def summary(self)`` is implemented for the given entity type. - -*render_entity_attributes(self, entity, siderelations)* - Renders all the attribute of an entity with the exception of attribute - of type `Password` and `Bytes`. - -*content_navigation_components(self, context)* - This method is applicable only for entity type implementing the interface - `IPrevNext`. This interface is for entities which can be linked to a previous - and/or next entity. This methods will render the navigation links between - entities of this type, either at the top or at the bottom of the page - given the context (navcontent{top|bottom}). - -*render_entity_relations(self, entity, siderelations)* - Renders all the relations of the entity in the main section of the page. - -*render_side_related(self, entity, siderelations)* - Renders all the relations of the entity in a side box. This is equivalent - to *render_entity_relations* in addition to render the relations - in a box. - -Also, please note that by setting the following attributes in you class, -you can already customize some of the rendering: - -*show_attr_label* - Renders the attribute label next to the attribute value if set to True. - Otherwise, does only display the attribute value. - -*show_rel_label* - Renders the relation label next to the relation value if set to True. - Otherwise, does only display the relation value. - -*skip_none* - Does not render an attribute value that is None if set to True. - -*skip_attrs* - Given a list of attributes name, does not render the value of the attributes listed. - -*skip_rels* - Given a list of relations name, does not render the relations listed. - -*main_related_section* - Renders the relations of the entity if set to True. - -A good practice is for you to identify the content of your entity type for which -the default rendering does not answer your need so that you can focus on the specific -method (from the list above) that needs to be modified. We do not recommand you to -overwrite ``render_entity`` as you might potentially loose the benefits of the side -boxes handling. - -Example of a view customization -------------------------------- - -[FIXME] XXX Example needs to be rewritten as it shows how to modify cell_call which -contredicts our advise of not modifying it. - -We'll show you now an example of a ``primary`` view and how to customize it. - -If you want to change the way a ``BlogEntry`` is displayed, just override -the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py`` :: - - 01. from cubicweb.web.views import baseviews - 02. - 03. class BlogEntryPrimaryView(baseviews.PrimaryView): - 04. - 05. accepts = ('BlogEntry',) - 06. - 07. def cell_call(self, row, col): - 08. entity = self.entity(row, col) - 09. self.w(u'

%s

' % entity.title) - 10. self.w(u'

published on %s in category %s

' % \ - 11. (entity.publish_date.strftime('%Y-%m-%d'), entity.category)) - 12. self.w(u'

%s

' % entity.text) - -The above source code defines a new primary view (`line 03`) for -``BlogEntry`` (`line 05`). - -Since views are applied to result sets which can be tables of -data, we have to recover the entity from its (row,col)-coordinates (`line 08`). -We will get to this in more detail later. - -The view method ``self.w()`` is used to output data. Here `lines -09-12` output HTML tags and values of the entity's attributes. - -When displaying the same blog entry as before, you will notice that the -page is now looking much nicer. [FIXME: it is not clear to what this refers.] - -.. image:: images/lax-book.09-new-view-blogentry.en.png - :alt: blog entries now look much nicer - -Let us now improve the primary view of a blog :: - - 01. class BlogPrimaryView(baseviews.PrimaryView): - 02. - 03. accepts = ('Blog',) - 04. - 05. def cell_call(self, row, col): - 06. entity = self.entity(row, col) - 07. self.w(u'

%s

' % entity.title) - 08. self.w(u'

%s

' % entity.description) - 09. rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid) - 10. self.wview('primary', rset) - -In the above source code, `lines 01-08` are similar to the previous -view we defined. [FIXME: defined where ?] - -At `line 09`, a simple request is made to build a result set with all -the entities linked to the current ``Blog`` entity by the relationship -``entry_of``. The part of the framework handling the request knows -about the schema and infer that such entities have to be of the -``BlogEntry`` kind and retrieves them. - -The request returns a selection of data called a result set. At -`line 10` the view 'primary' is applied to this result set to output -HTML. - -**This is to be compared to interfaces and protocols in object-oriented -languages. Applying a given view called 'a_view' to all the entities -of a result set only requires to have for each entity of this result set, -an available view called 'a_view' which accepts the entity.** - -Assuming we added entries to the blog titled `MyLife`, displaying it -now allows to read its description and all its entries. - -.. image:: images/lax-book.10-blog-with-two-entries.en.png - :alt: a blog and all its entries - -**Before we move forward, remember that the selection/view principle is -at the core of `CubicWeb`. Everywhere in the engine, data is requested -using the RQL language, then HTML/XML/text/PNG is output by applying a -view to the result set returned by the query. That is where most of the -flexibility comes from.** - -[WRITE ME] - -* implementing interfaces, calendar for blog entries -* show that a calendar view can export data to ical - -We will implement the `cubicweb.interfaces.ICalendarable` interfaces on -entities.BlogEntry and apply the OneMonthCalendar and iCalendar views -to result sets like "Any E WHERE E is BlogEntry" - -* create view "blogentry table" with title, publish_date, category - -We will show that by default the view that displays -"Any E,D,C WHERE E publish_date D, E category C" is the table view. -Of course, the same can be obtained by calling -self.wview('table',rset) - -* in view blog, select blogentries and apply view "blogentry table" -* demo ajax by filtering blogentry table on category - -we did the same with 'primary', but with tables we can turn on filters -and show that ajax comes for free. -[FILLME] - - -Templates ---------- - -*Templates* are specific views that do not depend on a result set. The basic -class `Template` (`cubicweb.common.view`) is derived from the class `View`. - -To build a HTML page, a *main template* is used. In general, the template of -identifier `main` is the one to use (it is not used in case an error is raised or for -the login form for example). This template uses other templates in addition -to the views which depends on the content to generate the HTML page to return. - -A *template* is responsible for: - -1. executing RQL query of data to render if necessary -2. identifying the view to use to render data if it is not specified -3. composing the HTML page to return - -You will find out more about templates in :ref:`templates`. - -XML views, binaries... ----------------------- -For views generating other formats than HTML (an image generated dynamically -for example), and which can not simply be included in the HTML page generated -by the main template (see above), you have to: - -* set the attribute `templatable` of the class to `False` -* set, through the attribute `content_type` of the class, the MIME type generated - by the view to `application/octet-stream` - -For views dedicated to binary content creation (like dynamically generated -images), we have to set the attribute `binary` of the class to `True` (which -implies that `templatable == False`, so that the attribute `w` of the view could be -replaced by a binary flow instead of unicode). - -(X)HTML tricks to apply ------------------------ - -Some web browser (Firefox for example) are not happy with empty `
` -(by empty we mean that there is no content in the tag, but there -could be attributes), so we should always use `
` even if -it is empty and not use `
`. - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1021-views-selectors.en.txt --- a/doc/book/en/B1021-views-selectors.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -.. -*- coding: utf-8 -*- - -Selectors -````````` - -Selectors are scoring functions that are called by the view -dispatcher to tell whenever a view can be applied to a given result -set of a request. Selector sets are the glue that tie views to the data -model. Using them appropriately is an essential part of the -construction of well behaved cubes. - -When no score is higher than the others, an exception is raised -``NoSelectableObject`` to let you know that the engine was not able to -identify the view to apply. In such case you would need to review your -design and make sure your views are properly defined. - -`CubicWeb` provides its own set of selectors that you can use and here -is a description of some of the most common used: - -*yes* - This selector accepts everything which basically means to any result - set. - -*none_rset* - This selector accepts no result set, so it can be applied to any - object. - -*rset* - This selector accepts any result set, whatever the number of objects - in the result set. - -*nonempty_rset* - This selector accepts any non empty result set. - -*empty_rset* - This selector accepts empty (only) result set. - -*one_line_rset* - This selector accepts result set with a single line of result. - -*two_lines_rset* - This selector accepts result set with *at least* two lines of result. - -*two_cols_rset* - This selector accepts result set with *at least* one line and two columns of result. - -*anonymous_user* - This selector accepts if user is anonymous. - -*authenticated_user* - This selector accepts if user is authenticated. - - -Of course you will write your own set of selectors as you get familiar with the -framework. - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1022-views-stdlib.en.txt --- a/doc/book/en/B1022-views-stdlib.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -.. -*- coding: utf-8 -*- - -Predefined views in the library -``````````````````````````````` - -`CubicWeb` provides a lot of standard views. You can find them in -``cubicweb/web/views/``. - -A certain number of views are used to build the web interface, which apply -to one or more entities. Their identifier is what distinguish them from -each others and the main ones are: - -*primary* - Primary view of an entity, this is the view called by default when a single - non final entity is in the result set and needs to be displayed. - This view is supposed to render a maximum of informations about the entity. - -*text* - This is the simplest text view for an entity. It displays the - title of an entity. It should not contain HTML. - -*oneline* - This is a hyper linked *text* view. Similar to the `secondary` view, - but called when we want the view to stand on a single line, or just - get a brief view. By default this view uses the - parameter `MAX_LINE_CHAR` to control the result size. - -*secondary* - This is a combinaison of an icon and a *oneline* view. - By default it renders the two first attributes of the entity as a - clickable link redirecting to the primary view. - -*incontext, outofcontext* - Similar to the `secondary` view, but called when an entity is considered - as in or out of context. By default it respectively returns the result of - `textincontext` and `textoutofcontext` wrapped in a link leading to - the primary view of the entity. - -*textincontext, textoutofcontext* - Similar to the `text` view, but called when an entity is considered out or - in context. By default it returns respectively the result of the - methods `.dc_title` and `.dc_long_title` of the entity. - -*list* - This view displays a list of entities by creating a HTML list (`
    `) - and call the view `listitem` for each entity of the result set. - -*listitem* - This view redirects by default to the `outofcontext` view. - -*rss* - Creates a RSS/XML view and call the view `rssitem` for each entity of - the result set. - -*rssitem* - Create a RSS/XML view for each entity based on the results of the dublin core - methods of the entity (`dc_*`) - -*sidebox* - This view displays usually a side box of some related entities - in a primary view. - - -Start view (e.g. views that don't apply to a result set): - -*index* - This view defines the home page of your application. It does not require - a result set to apply to. - -*schema* - A view dedicated to the display of the schema of the application - -Special views: - -*noresult* - This view is the default view used when no result has been found - (e.g. empty result set). - -*final* - Display the value of a cell without trasnformation (in case of a non final - entity, we see the eid). Applicable on any result set. - -*table* - Creates a HTML table (``) and call the view `cell` for each cell of - the result set. Applicable on any result set. - -*cell* - By default redirects to the `final` view if this is a final entity or - `outofcontext` view otherwise - -*null* - This view is the default view used when nothing needs to be rendered. - It is always applicable and it does not return anything diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1030-form-management.en.txt --- a/doc/book/en/B1030-form-management.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -.. -*- coding: utf-8 -*- - -Forms handling -============== - -Automatically generated forms management for handled entities -------------------------------------------------------------- - -XXX FILLME - -* forms ``edition`` and ``creation`` - -The form generated by default does not fit your needs? You are not -required to re-do all by hands! :) - -* rtags primary, secondary, generated, generic, - `Entity.relation_category(rtype, x='subject')` -* inline_view (now a rtag?) -* widget specification - -Editing controller behavior by default (id: `edit`) ---------------------------------------------------- - -Editing control -``````````````` - -Re-requisites: the parameters related to entities to edit are -specified as follows :: - - : - -where entity eid could be a letter in case of an entity to create. We -name those parameters as *qualified*. - -1. Retrieval of entities to edit by looking for the forms parameters - starting by `eid:` and also having a parameter `__type` associated - (also *qualified* by eid) - -2. For all the attributes and the relations of an entity to edit: - - 1. search for a parameter `edits-` or `edito-` - qualified in the case of a relation where the entity is object - 2. if found, the value returned is considered as the initial value - for this relaiton and we then look for the new value(s) in the parameter - (qualified) - 3. if the value returned is different from the initial value, an database update - request is done - -3. For each entity to edit: - - 1. if a qualified parameter `__linkto` is specified, its value has to be - a string (or a list of string) such as: :: - - :: - - where is either `subject` or `object` and each eid could be - separated from the others by a `_`. Target specifies if the *edited entity* - is subject or object of the relation and each relation specified will - be inserted. - - 2. if a qualified parameter `__clone_eid` is specified for an entity, the - relations of the specified entity passed as value of this parameter are - copied on the edited entity. - - 3. if a qualified parameter `__delete` is specified, its value must be - a string or a list of string such as follows: :: - - :: - - where each eid subject or object can be seperated from the other - by `_`. Each relation specified will be deleted. - - 4. if a qualified parameter `__insert` is specified, its value should - follow the same pattern as `__delete`, but each relation specified is - inserted. - -4. If the parameters `__insert` and/or `__delete` are found not qualified, - they are interpreted as explained above (independantly from the number - of entities edited). - -5. If no entity is edited but the form contains the parameters `__linkto` - and `eid`, this one is interpreted by using the value specified for `eid` - to designate the entity on which to add the relations. - - -.. note:: - - * If the parameter `__action_delete` is found, all the entities specified - as to be edited will be deleted. - - * If the parameter`__action_cancel` is found, no action is completed. - - * If the parameter `__action_apply` is found, the editing is applied - normally but the redirection is done on the form - (see :ref:`RedirectionControl`). - - * The parameter `__method` is also supported as for the main template - (XXX not very consistent, maybe __method should be dealed in the view - controller). - - * If no entity is found to be edited and if there is no parameter - `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or - `__insert`, an error is raised. - - * Using the parameter `__message` in the form will allow to use its value - as a message to provide the user once the editing is completed. - - -.. _RedirectionControl: - -Redirection control -``````````````````` -Once editing is completed, there is still an issue left: where should we go -now? If nothing is specified, the controller will do his job but it does not -mean we will be happy with the result. We can control that by using the -following parameters: - -* `__redirectpath`: path of the URL (relative to the root URL of the site, - no form parameters - -* `__redirectparams`: forms parameters to add to the path - -* `__redirectrql`: redirection RQL request - -* `__redirectvid`: redirection view identifier - -* `__errorurl`: initial form URL, used for redirecting in case a validation - error is raised during editing. If this one is not specified, an error page - is displayed instead of going back to the form (which is, if necessary, - responsible for displaying the errors) - -* `__form_id`: initial view form identifier, used if `__action_apply` is - found - -In general we use either `__redirectpath` and `__redirectparams` or -`__redirectrql` and `__redirectvid`. - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1040-actions.en.txt --- a/doc/book/en/B1040-actions.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Actions -========= - -[WRITE ME] - -* talk about actions that appear in the action box - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1050-boxes.en.txt --- a/doc/book/en/B1050-boxes.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Boxes -========= - -[WRITE ME] - -* boxes in the web interface - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1060-templates.en.txt --- a/doc/book/en/B1060-templates.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _templates: - -Templates -========= - -[WRITE ME] - -* talk about main templates, etc. - - - -Look at ``cubicweb/web/views/basetemplates.py`` and you will -find the base templates used to generate HTML for your application. - -A page is composed as indicated on the schema below : - -.. image:: images/lax-book.06-main-template-layout.en.png - -In this section we will go through a couple of the primary templates -you must be interested in, that is to say, the HTMLPageHeader, -the HTMLPageFooter and the TheMainTemplate. - - -HTMLPageHeader --------------- - -Customize header -~~~~~~~~~~~~~~~~ - -Let's now move the search box in the header and remove the login form -from the header. We'll show how to move it to the left column of the application. - -Let's say we do not want anymore the login menu to be in the header, but we -prefer it to be in the left column just below the logo. As the left column is -rendered by ``TheMainTemplate``, we will show how to do it in TheMainTemplate_. - -First, to remove the login menu, we just need to comment out the display of the -login component such as follows : :: - - class MyHTMLPageHeader(HTMLPageHeader): - - def main_header(self, view): - """build the top menu with authentification info and the rql box""" - self.w(u'
    \n') - self.w(u'\n') - # appliname and breadcrumbs - self.w(u'') - # logged user and help - #self.w(u'') - # lastcolumn - self.w(u'\n') - self.w(u'\n') - self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden', - title=False, message=False) - - - -.. image:: images/lax-book.06-header-no-login.en.png - -Let's now move the search box in the top-right header area. To do so, we will -first create a method to get the search box display and insert it in the header -table. - -:: - - from cubicweb.web.views.basetemplates import HTMLPageHeader - class MyHTMLPageHeader(HTMLPageHeader): - def main_header(self, view): - """build the top menu with authentification info and the rql box""" - self.w(u'\n') - self.w(u'\n') - # appliname and breadcrumbs - self.w(u'') - - # logged user and help - #self.w(u'') - - self.w(u'') - # lastcolumn - self.w(u'\n') - self.w(u'\n') - self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden', - title=False, message=False) - - def get_searchbox(self, view, context): - boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset, - view=view, context=context)) - if boxes: - for box in boxes: - if box.id == 'search_box': - box.dispatch(w=self.w, view=view) - - - - -HTMLPageFooter --------------- - -If you want to change the footer for example, look -for HTMLPageFooter and override it in your views file as in : -:: - - form cubicweb.web.views.basetemplates import HTMLPageFooter - class MyHTMLPageFooter(HTMLPageFooter): - def call(self, **kwargs): - self.w(u'') - -Updating a view does not require any restart of the server. By reloading -the page you can see your new page footer. - - -TheMainTemplate ---------------- -.. _TheMainTemplate: - -TheMainTemplate is responsible for the general layout of the entire application. -It defines the template of ``id = main`` that is used by the application. - -The default main template (`cubicweb.web.views.basetemplates.TheMainTemplate`) -builds the page based on the following pattern: - -.. image:: images/main_template_layout.png - -The rectangle containing `view.dispatch()` represents the area where the content -view has to be displayed. The others represents sub-templates called to complete -the page. A default implementation of those is provided in -`cubicweb.views.basetemplates`. You can, of course, overload those sub-templates -to implement your own customization of the HTML page. - -We can also control certain aspects of the main template thanks to the following -forms parameters: - -* `__notemplate`, if present (whatever the value assigned), only the content view - is returned -* `__force_display`, if present and its value is not null, no navigation - whatever the number of entities to display -* `__method`, if the result set to render contains only one entity and this - parameter is set, it refers to a method to call on the entity by passing it - the dictionary of the forms parameters, before going the classic way (through - step 1 and 2 described juste above) - -The MainTemplate is a bit complex as it tries to accomodate many -different cases. We are now about to go through it and cutomize entirely -our application. - - -CSS changes ------------ - -We cannot modify the order in which the application is reading the CSS. In -the case we want to create new CSS style, the best is to define it a in a new -CSS located under ``myapp/data/``. - - -.. [TRANSLATE ME FROM FRENCH] -.. 03-XX-external_resources.fr.txt - -[TODO] -Add login menu in left column - - -[WRITE ME] - -* customize MainTemplate and show that everything in the user - interface can be changed - -[TODO] -Rajouter une section pour definir la terminologie utilisee. -Dans cubicweb-doc rajouter une section pour cubciweb-ctl shell ou -on liste les commandes dispos. diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1070-ui-components.en.txt --- a/doc/book/en/B1070-ui-components.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -Others web interface components -=============================== - -Actions -------- -XXXFILLME - -Component, VComponent ---------------------- -XXXFILLME - -EProperty ---------- -XXXFILLME diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1080-ajax-json.en.txt --- a/doc/book/en/B1080-ajax-json.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -.. -*- coding: utf-8 -*- - -AJAX -==== -JSON bla bla -XXX FILLME - - -Le contrôleur 'json' --------------------- -XXX FILLME - - -API Javascript --------------- -XXX FILLME diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1090-internationalization.en.txt --- a/doc/book/en/B1090-internationalization.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _internationalisation: - - -Internationalisation -==================== - -Cubicweb fully supports the internalization of it's content and interface. - -Cubicweb's interface internationalization is based on the translation project `GNU gettext`_. - -.. _`GNU gettext`: http://www.gnu.org/software/gettext/ - -Cubicweb' internalization involves two steps: - -* in your Python code and cubicweb-tal templates : mark translatable strings - -* in your application : handle the translation catalog - -String internationalization ---------------------------- - -In the Python code and cubicweb-tal templates translatable strings can be -marked in one of the following ways : - - * by using the *built-in* function `_` :: - - class PrimaryView(EntityView): - """the full view of an non final entity""" - id = 'primary' - title = _('primary') - - OR - - * by using the equivalent request's method :: - - class NoResultView(EmptyRsetView): - """default view when no result has been found""" - id = 'noresult' - - def call(self, **kwargs): - self.w(u'
    %s
    \n' - % self.req._('No result matching query')) - -The goal of the *built-in* function `_` is only **to mark the -translatable strings**, it will only return the string to translate -it-self, but not its translation. - -In the other hand the request's method `self.req._` is meant to retrieve the -proper translation of translation strings in the requested language. - -Translations in cubicweb-tal template can also be done with TAL tags -`i18n:content` and `i18n:replace`. - -.. note:: - - We dont need to mark the translation strings of entities/relations - used by a particular application's schema as they are generated - automatically. - - -Handle the translation catalog ------------------------------- - -Once the internationalization is done in your application's code, you need -to populate and update the translation catalog. Cubicweb provides the -following commands for this purpose: - - -* `i18nlibupdate` updates Cubicweb framework's translation - catalogs. Unless you work on the framework development, you don't - need to use this command. - -* `i18nupdate` updates the translation catalogs of *one particular - component* (or of all components). After this command is - executed you must update the translation files *.po* in the "i18n" - directory of your template. This command will of course not remove - existing translations still in use. - -* `i18ncompile` recompile the translation catalogs of *one particular - instance* (or of all instances) after the translation catalogs of - its components have been updated. This command is automatically - called every time you create or update your instance. The compiled - catalogs (*.mo*) are stored in the i18n//LC_MESSAGES of - application where `lang` is the language identifier ('en' or 'fr' - for exemple). - - -Example -``````` -You have added and/or modified some translation strings in your application -(after creating a new view or modifying the application's schema for exemple). -To update the translation catalogs you need to do: - -1. `cubicweb-ctl i18nupdate ` -2. Edit the /xxx.po files and add missing translations (empty `msgstr`) -3. `hg ci -m "updated i18n catalogs"` -4. `cubicweb-ctl i18ncompile ` - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1100-online-doc.en.txt --- a/doc/book/en/B1100-online-doc.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Online documentation system -=========================== - -[WRITE ME] - -* describe the on-line documentation system - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1110-embedding-external-page.en.txt --- a/doc/book/en/B1110-embedding-external-page.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Embedding external pages -======================== - -[WRITE ME] - -* including external content - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1120-urlrewrite.en.txt --- a/doc/book/en/B1120-urlrewrite.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -.. -*- coding: utf-8 -*- - -URL Rewriting -============= - - -[WRITE ME] - -* show how urls are mapped to selections and views and explain URLRewriting - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B1130-css.en.txt --- a/doc/book/en/B1130-css.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -.. -*- coding: utf-8 -*- - -CSS changes -=========== - -XXX FIXME explain CSS used by cubciweb - -We cannot modify the order in which the application is reading the CSS. In -the case we want to create new CSS style, the best is to define it a in a new -CSS located under ``myapp/data/`` and use those new styles while writing -customized views and templates. - -If you want to modify an existing CSS styling property, you will have to use -``!important`` declaration to override the existing property. The application -apply a higher priority on the default CSS and you can not change that. -Customized CSS will not be read first. diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2-repository-customization.en.txt --- a/doc/book/en/B2-repository-customization.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -.. -*- coding: utf-8 -*- - -Repository customization -++++++++++++++++++++++++ -.. toctree:: - :maxdepth: 1 - - B2010-sessions.en.txt - B2020-hooks.en.txt - B2030-notifications.en.txt - B2040-repository-operations.en.txt - B2050-google-appengine.en.txt - B2060-repository-tasks.en.txt - - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2010-sessions.en.txt --- a/doc/book/en/B2010-sessions.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Sessions -======== - -[WRITE ME] - -* authentication and management of sessions - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2020-hooks.en.txt --- a/doc/book/en/B2020-hooks.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _hooks: - -Hooks -===== - -XXX FILLME - -*Hooks* are executed before or after updating an entity or a relation in the -repository. - -Their prototypes are as follows: - - * after_add_entity (session, entity) - * after_update_entity (session, entity) - * after_delete_entity (session, eid) - * before_add_entity (session, entity) - * before_update_entity (session, entity) - * before_delete_entity (session, eid) - - * after_add_relation (session, fromeid, rtype, toeid) - * after_delete_relation (session, fromeid, rtype, toeid) - * before_add_relation (session, fromeid, rtype, toeid) - * before_delete_relation (session, fromeid, rtype, toeid) - - * server_startup - * server_shutdown - - * session_open - * session_close - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2030-notifications.en.txt --- a/doc/book/en/B2030-notifications.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -.. -*- coding: utf-8 -*- - -Notifications management -======================== - -XXX FILLME diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2040-repository-operations.en.txt --- a/doc/book/en/B2040-repository-operations.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Repository operations -====================== - -[WRITE ME] - -* repository operations - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2050-google-appengine.en.txt --- a/doc/book/en/B2050-google-appengine.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _gaecontents: - -========================== -Google AppEngine Datastore -========================== - - -.. include:: B2051-intro.en.txt -.. include:: B2052-install.en.txt diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2051-intro.en.txt --- a/doc/book/en/B2051-intro.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -.. -*- coding: utf-8 -*- - -Introduction -============ - -What is `Google AppEngine` ? ------------------------------- - -`Google AppEngine`_ is provided with a partial port of the `Django` -framework, but Google stated at Google IO 2008 that it would not -support a specific Python web framework and that all -community-supported frameworks would be more than welcome [1]_. - -Therefore `Logilab`_ ported `CubicWeb` to run on top of `Google AppEngine`'s -datastore. - -.. _`Google AppEngine`: http://code.google.com/appengine/docs/whatisgoogleappengine.html -.. _Logilab: http://www.logilab.fr/ -.. [1] for more on this matter, read our blog at http://www.logilab.org/blogentry/5216 - - -Essentials ----------- - -To build a web application for `Google App Engine`'s datastore, you -need to have a good understanding of the main concepts of our -`CubicWeb` framework. - -The main concepts are: - - - *schema* - - - *query language* - - - *result set* - - - *views* - - - *generated user interface* - - - *cube* - -You can find detailled explanation of those concepts in :ref:`TermsVocabulary`. - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2052-install.en.txt --- a/doc/book/en/B2052-install.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,219 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _installation: - -Installation -============ - -Download the source -------------------- - -- The `Google AppEngine SDK` can be downloaded from: - http://code.google.com/appengine/downloads.html - - -Please follow instructions on how to install `CubicWeb` framework -(:ref:`CubicWebInstallation`). - -Once ``cubicweb-ctl`` is installed, then you can create a Google -App Engine extension of our framework by running the command :: - - cubicweb-ctl newgapp - -This will create a directory containing :: - - `-- myapp/ - |-- app.conf - |-- app.yaml - |-- bin/ - | `-- laxctl - |-- boostrap_cubes - |-- cubes/ - | |-- addressbook/ - | .. - | |-- comment - | .. - | `-- zone/ - |-- cubicweb/ - |-- custom.py - |-- cw-cubes/ - |-- dateutil/ - |-- docutils/ - |-- fckeditor/ - |-- i18n/ - |-- index.yaml - |-- loader.py - |-- logilab/ - |-- main.py - |-- migration.py - |-- mx/ - |-- roman.py - |-- rql/ - |-- schema.py - |-- simplejson/ - |-- tools/ - |-- views.py - |-- vobject/ - |-- yams/ - `-- yapps/ - - -This skeleton directory is a working `AppEngine` application. You will -recognize the files ``app.yaml`` and ``main.py``. All the rest is the -`CubicWeb` framework and its third-party libraries. You will notice that -the directory ``cubes`` is a library of reusable cubes. - -The main directories that you should know about are: - - - ``cubes`` : this is a library of reusable yams cubes. To use - those cubes you will list them in the variable - `included-yams-cubes` of ``app.conf``. See also :ref:`cubes`. - - [WHICH OTHER ONES SHOULD BE LISTED HERE?] - -Dependencies ------------- - -Before starting anything, please make sure the following packages are installed: - - yaml : by default google appengine is providing yaml; make sure you can - import it. We recommend you create a symbolic link yaml instead of installing - and using python-yaml: - yaml -> full/path/to/google_appengine/lib/yaml/lib/yaml/ - - gettext - -Setup ------ - -Once you executed ``cubicweb-ctl newgapp ``, you can use that ``myapp/`` -as an application directory and do as follows. - -This installation directory provides a configuration for an instance of `CubicWeb` -ported for Google App Engine. It is installed with its own command ``laxctl`` -which is a port of the command tool ``cubicweb-ctl`` originally developped for -`CubicWeb`. - -You can have the details of available commands by running :: - - $ python myapp/bin/laxctl --help - - -Generating translation files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`CubicWeb` is fully internationalized. Translation catalogs are found in -``myapp/i18n``. To compile the translation files, use the `gettext` tools -or the ``laxctl`` command :: - - $ python myapp/bin/laxctl i18nupdate - $ python myapp/bin/laxctl i18ncompile - -Ignore the errors that print "No translation file found for domain -'cubicweb'". They disappear after the first run of i18ncompile. - -.. note:: The command myapp/bin/laxctl i18nupdate needs to be executed - only if your application is using cubes from cubicweb-apps. - Otherwise, please skip it. - -You will never need to add new entries in the translation catalog. Instead we would -recommand you to use ``self.req._("msgId")`` in your application code -to flag new message id to add to the catalog, where ``_`` refers to -xgettext that is used to collect new strings to translate. -While running ``laxctl i18nupdate``, new string will be added to the catalogs. - -Generating the data directory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to generate the ``myapp/data`` directory that holds the static -files like stylesheets and icons, you need to run the command:: - - $ python myapp/bin/laxctl populatedata - -Generating the schema diagram -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There is a view named ``schema`` that displays a diagram of the -entity-relationship graph defined by the schema. This diagram has to -be generated from the command line:: - - $ python myapp/bin/laxctl genschema - -Application configuration -------------------------- - -Authentication -~~~~~~~~~~~~~~ - -You have the option of using or not google authentication for your application. -This has to be define in ``app.conf`` and ``app.yaml``. - -In ``app.conf`` modify the following variable:: -  - # does this application rely on google authentication service or not. - use-google-auth=no - -In ``app.yaml`` comment the `login: required` set by default in the handler:: - - - url: .* - script: main.py - # comment the line below to allow anonymous access or if you don't want to use - # google authentication service - #login: required - - - - -Quickstart : launch the application ------------------------------------ - -On Mac OS X platforms, drag that directory on the -`GoogleAppEngineLauncher`. - -On Unix and Windows platforms, run it with the dev_appserver:: - - $ python /path/to/google_appengine/dev_appserver.py /path/to/myapp/ - -Once the local server is started, visit `http://MYAPP_URL/_load `_ and sign in as administrator. -This will initialize the repository and enable you to log in into -the application and continue the installation. - -You should be redirected to a page displaying a message `content initialized`. - -Initialize the datastore -~~~~~~~~~~~~~~~~~~~~~~~~ - -You, then, want to visit `http://MYAPP_URL/?vid=authinfo `_ . -If you selected not to use google authentication, you will be prompted to a -login form where you should initialize the administrator login (recommended -to use admin/admin at first). You will then be redirected to a page providing -you the value to provide to ``./bin/laxctl --cookie``. - -If you choosed to use google authentication, then you will not need to set up -and administrator login but you will get the cookie value as well. - -This cookie values needs to be provided to ``laxctl`` commands -in order to handle datastore administration requests. - -.. image:: images/lax-book.02-cookie-values.en.png - :alt: displaying the detailed view of the cookie values returned - - -.. note:: In case you are not redirected to a page providing the - option --cookie value, please visit one more time - `http://MYAPP_URL/?vid=authinfo `_ . - -Once, you have this value, then return to the shell and execute :: - - $ python myapp/bin/laxctl db-init --cookie='dev_appserver_login=test@example.com:True; __session=7bbe973a6705bc5773a640f8cf4326cc' localhost:8080 - -.. note:: In the case you are not using google authentication, the value returned - by `http://MYAPP_URL/?vid=authinfo `_ - will look like : - --cookie='__session=2b45d1a9c36c03d2a30cedb04bc37b6d' - -Log out by clicking in the menu at the top right corner -and restart browsing from `http://MYAPP_URL/ `_ -as a normal user. - -Unless you did something to change it, http://MYAPP_URL should be -http://localhost:8080/ - - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B2060-repository-tasks.en.txt --- a/doc/book/en/B2060-repository-tasks.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Tasks -========= - -[WRITE ME] - -* repository tasks - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B3-test.en.txt --- a/doc/book/en/B3-test.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Tests -+++++ -.. toctree:: - :maxdepth: 1 - - B3010-tests.en.txt - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B3010-tests.en.txt --- a/doc/book/en/B3010-tests.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -.. -*- coding: utf-8 -*- - -Tests -===== - -Unit tests ----------- - -`CubicWeb` framework provides essentially two Python test classes in the -module `cubicweb.devtools.apptest`: - -* `EnvBasedTC`, to simulate a complete environment (web + repository) -* `RepositoryBasedTC`, to simulate a repository environment only - -Thos two classes almost have the same interface and offers numerous methods to -write tests rapidely and efficiently. - -XXX FILLME describe API - -In most of the cases, you will inherit `EnvBasedTC` to write Unittest or -functional tests for your entities, views, hooks, etc... - -Email notifications tests -------------------------- -When running tests potentially generated e-mails are not really -sent but is found in the list `MAILBOX` of module `cubicweb.devtools.apptest`. -This list is reset at each test *setUp* (by the setUp of classes `EnvBasedTC` -and `RepositoryBasedTC`). - - -You can test your notifications by analyzing the contents of this list, which -contains objects with two attributes: -* `recipients`, the list of recipients -* `msg`, object email.Message - -Automatic testing ------------------ -XXXFILLME diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B4-advanced.en.txt --- a/doc/book/en/B4-advanced.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -.. -*- coding: utf-8 -*- - -Advanced -++++++++ -.. toctree:: - :maxdepth: 1 - - B4010-configuration.en.txt - B4020-dbapi.en.txt - B4030-registry.en.txt - B4040-rss-xml.en.txt diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B4010-configuration.en.txt --- a/doc/book/en/B4010-configuration.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -Configuration -------------- - -[WRITE ME] - -* the config object. adding configuration option - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B4020-dbapi.en.txt --- a/doc/book/en/B4020-dbapi.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.. -*- coding: utf-8 -*- - -DB-API -========= - -[WRITE ME] - -* direct connection to the repository - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/B4030-registry.en.txt --- a/doc/book/en/B4030-registry.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -.. -*- coding: utf-8 -*- - -The Registry ------------- - -[WRITE ME] - -* talk about the vreg singleton, appobjects, registration and selection - - -Details of the recording process -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -At startup, the `registry` or registers base, inspects a number of directories -looking for compatible classes definition. After a recording process, the objects -are assigned to registers so that they can be selected dynamically while the -application is running. - -The base class of those objects is `AppRsetObject` (module `cubicweb.common.appobject`). - -XXX registers example -XXX actual details of the recording process! - -Runtime objects selection -~~~~~~~~~~~~~~~~~~~~~~~~~ - -XXX tell why it's a cw foundation! - -Application objects are stored in the registry using a two level hierarchy : - - object's `__registry__` : object's `id` : [list of app objects] - -The following rules are applied to select an object given a register and an id and an input context: -* each object has a selector which may be built from a set of basic (or not :) - - selectors using `chainall` or `chainfirst` combinators - -* a selector return a score >= 0 -* a score of 0 means the objects can't be applied to the input context -* the object with the greatest score is selected. If multiple objects have an - identical score, one of them is selected randomly (this is usually a bug) - -The object's selector is the `__select__` class method on the object's class. - -The score is used to choose the most pertinent objects where there are more than -one selectable object. For instance, if you're selecting the primary -(eg `id = 'primary'`) view (eg `__registry__ = 'view'`) for a result set containing -a `Card` entity, 2 objects will probably be selectable: - -* the default primary view (`accepts = 'Any'`) -* the specific `Card` primary view (`accepts = 'Card'`) - -This is because primary views are using the `accept_selector` which is considering the -`accepts` class attribute of the object's class. Other primary views specific to other -entity types won't be selectable in this case. And among selectable objects, the -accept selector will return a higher score the the second view since it's more -specific, so it will be selected as expected. - -Usually, you won't define it directly but by defining the `__selectors__` tuple -on the class, with :: - - __selectors__ = (sel1, sel2) - -which is equivalent to :: - - __select__ = classmethod(chainall(sel1, sel2)) - -The former is prefered since it's shorter and it's ease overriding in -subclasses (you have access to sub-selectors instead of the wrapping function). - -:chainall(selectors...): if one selector return 0, return 0, else return the sum of scores - -:chainfirst(selectors...): return the score of the first selector which has a non zero score - -XXX describe standard selector (link to generated api doc!) - -Example -```````` - -Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe -vers les entrées de ce blog, non vers l'entité blog elle-même. - -L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité -qui renvoie l'url du flux rss pour l'entité en question. Avec une implémentation -par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce -qu'on veut. - -La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient -plusieurs entités Blog (ou autre chose), car on ne sait pas sur quelle entité appeler -la méthode sus-citée. Dans ce cas, on va conserver le comportement actuel (eg appel -à limited_rql) - -Donc : on veut deux cas ici, l'un pour un rset qui contient une et une seule entité, -l'autre pour un rset qui contient plusieurs entité. - -Donc... On a déja dans web/views/boxes.py la classe RSSIconBox qui fonctionne. Son -sélecteur :: - - class RSSIconBox(ExtResourcesBoxTemplate): - """just display the RSS icon on uniform result set""" - __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (nfentity_selector,) - - -indique qu'il prend en compte : - -* les conditions d'apparition de la boite (faut remonter dans les classes parentes - pour voir le détail) -* nfentity_selector, qui filtre sur des rset contenant une liste d'entité non finale - -ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique -pour le 1er cas :: - - class EntityRSSIconBox(RSSIconBox): - """just display the RSS icon on uniform result set for a single entity""" - __selectors__ = RSSIconBox.__selectors__ + (onelinerset_selector,) - - -Ici, on ajoute onelinerset_selector, qui filtre sur des rset de taille 1. Il faut -savoir que quand on chaine des selecteurs, le score final est la somme des scores -renvoyés par chaque sélecteur (sauf si l'un renvoie zéro, auquel cas l'objet est -non sélectionnable). Donc ici, sur un rset avec plusieurs entités, onelinerset_selector -rendra la classe EntityRSSIconBox non sélectionnable, et on obtiendra bien la -classe RSSIconBox. Pour un rset avec une entité, la classe EntityRSSIconBox aura un -score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée. - -Voili voilou, il reste donc pour finir tout ça : - -* à définir le contenu de la méthode call de EntityRSSIconBox -* fournir l'implémentation par défaut de la méthode renvoyant l'url du flux rss sur - AnyEntity -* surcharger cette methode dans blog.Blog - - -When to use selectors? -``````````````````````` - -Il faut utiliser les sélecteurs pour faire des choses différentes en -fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la -nature de `self.rset` dans un objet, il faut très sérieusement se -poser la question s'il ne vaut pas mieux avoir deux objets différent -avec des sélecteurs approprié. - -If this is so fundamental, why don't I see them more often? -``````````````````````````````````````````````````````````` - -Because you're usually using base classes which are hiding the plumbing -of __registry__ (almost always), id (often when using "standard" object), -register and selector. diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/C000-administration.en.txt --- a/doc/book/en/C000-administration.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _Part3: - -========================== -Part III - Administration -========================== - -This part is for installation and administration of the `CubicWeb` framework and -applications based on that framework. - -.. toctree:: - :maxdepth: 1 - - C010-setup.en.txt - C020-create-instance.en.txt - C030-site-config.en.txt - C040-instance-config.en.txt - C050-rql.en.txt - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/C010-setup.en.txt --- a/doc/book/en/C010-setup.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,207 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _SetUpEnv: - -=================================================== -Installation and set-up of a `CubicWeb` environment -=================================================== - -Installation of `Cubicweb` and its dependencies ------------------------------------------------ - -`CubicWeb` is packaged for Debian and Ubuntu, but can be installed from source -using a tarball or the Mercurial version control system. - -.. _DebianInstallation: - -Debian and Ubuntu packages -``````````````````````````` - -Depending on the distribution you are using, add the appropriate line to your list -of sources (for example by editing ``/etc/apt/sources.list``). - -For Debian Lenny:: - - deb http://ftp.logilab.org/dists/ lenny/ - -For Debian Sid:: - - deb http://ftp.logilab.org/dists/ sid/ - -For Ubuntu Hardy:: - - deb http://ftp.logilab.org/dists/ hardy/ - - -You can now install the required packages with the following command:: - - apt-get update - apt-get install cubicweb cubicweb-dev - -`cubicweb` installs the framework itself, allowing you to create -new applications. - -`cubicweb-dev` installs the development environment allowing you to -develop new cubes. - -There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball. - - -Install from source -``````````````````` - -You can download the archive containing the sources from our `ftp site`_ at:: - - http://ftp.logilab.org/pub/cubicweb/ - -.. _`ftp site`: http://ftp.logilab.org/pub/cubicweb/ - -or keep up to date with on-going development by using Mercurial and its forest -extension:: - - hg fclone http://www.logilab.org/hg/forests/cubicweb - -See :ref:`MercurialPresentation` for more details about Mercurial. - -Postgres installation -````````````````````` - -Please refer to the `Postgresql project online documentation`_. - -.. _`Postgresql project online documentation`: http://www.postgresql.org/ - -You need to install the three following packages: `postgres-8.3`, -`postgres-contrib-8.3` and `postgresql-plpython-8.3`. - - -Then you can install: - -* `pyro` if you wish the repository to be accessible through Pyro - or if the client and the server are not running on the same machine - (in which case the packages will have to be installed on both - machines) - -* `python-ldap` if you plan to use a LDAP source on the server - -.. _ConfigurationEnv: - -Environment configuration -------------------------- - -If you installed `CubicWeb` by cloning the Mercurial forest, then you -will need to update the environment variable PYTHONPATH by adding -the path to the forest ``cubicweb``: - -Add the following lines to either `.bashrc` or `.bash_profile` to configure -your development environment :: - - export PYTHONPATH=/full/path/to/cubicweb-forest - -If you installed the debian packages, no configuration is required. -Your new cubes will be placed in `/usr/share/cubicweb/cubes` and -your applications will be placed in `/etc/cubicweb.d`. - -To use others directories then you will have to configure the -following environment variables as follows:: - - export CW_CUBES_PATH=~/lib/cubes - export CW_REGISTRY=~/etc/cubicweb.d/ - export CW_INSTANCE_DATA=$CW_REGISTRY - export CW_RUNTIME=/tmp - -.. note:: - The values given above are our suggestions but of course - can be different. - - -Databases configuration ------------------------ - - - -.. _ConfigurationPostgres: - -Postgres configuration -`````````````````````` - -.. note:: - If you already have an existing cluster and postgres server - running, you do not need to execute the initilization step - of your Postgres database. - -* First, initialize the database Postgres with the command ``initdb``. - :: - - $ initdb -D /path/to/pgsql - - Once initialized, start the database server Postgres - with the command:: - - $ postgres -D /path/to/psql - - If you cannot execute this command due to permission issues, please - make sure that your username has write access on the database. - :: - - $ chown username /path/to/pgsql - -* The database authentication can be either set to `ident sameuser` - or `md5`. - If set to `md5`, make sure to use an existing user - of your database. - If set to `ident sameuser`, make sure that your - client's operating system user name has a matching user in - the database. If not, please do as follow to create a user:: - - $ su - $ su - postgres - $ createuser -s -P username - - The option `-P` (for password prompt), will encrypt the password with - the method set in the configuration file ``pg_hba.conf``. - If you do not use this option `-P`, then the default value will be null - and you will need to set it with:: - - $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql" - - This login/password will be requested when you will create an - instance with `cubicweb-ctl create` to initialize the database of - your application. - -.. note:: - The authentication method can be configured in ``pg_hba.conf``. - - -.. FIXME Are these steps really necessary? It seemed to work without. - -* Installation of plain-text index extension :: - - cat /usr/share/postgresql/8.3/contrib/tsearch2.sql | psql -U username template1 - -* Installation of plpythonu language by default :: - - createlang -U pgadmin plpythonu template1 - -MySql configuration -``````````````````` -Yout must add the following lines in /etc/mysql/my.cnf file:: - - transaction-isolation = READ-COMMITTED - default-storage-engine=INNODB - default-character-set=utf8 - max_allowed_packet = 128M - -Pyro configuration ------------------- - -If you use Pyro, it is required to have a name server Pyro running on your -network (by default it is detected by a broadcast request). - -To do so, you need to : - -* launch the server manually before starting cubicweb as a server with - `pyro-nsd start` - -* edit the file ``/etc/default/pyro-nsd`` so that the name server pyro - will be launched automatically when the machine fire up - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/C020-create-instance.en.txt --- a/doc/book/en/C020-create-instance.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ -.. -*- coding: utf-8 -*- - -Creation of your first instance -=============================== - -What is an instance? --------------------- - -A `CubicWeb` instance is a directory in ``~/etc/cubicweb.d`` -which enables us to run a web application. An instance is based on -a cube. - -An instance is a container that refers to cubes and configuration -parameters for your web application. - -We recommand not to define schema, entities or views in the instance -file system itself but in the cube, in order to maintain re-usability of -entities and their views. We strongly recommand to develop cubes which -could be used in other instances (modular approach). - - -What is a cube? ---------------- - -A cube defines entities, their views, their schemas and workflows -in an independant directory located in ``/path/to/forest/cubicweb/cubes/`` -for a Mercurial installation or in ``/usr/share/cubicweb/cubes`` for -a debian package installation. - -When an instance is created, you list one or more cubes that your instance -will use. Using a cube means having the entities defined in your cube's schema -available in your instance as well as their views and workflows. - -.. note:: - The commands used below are more detailled in the section dedicated to - :ref:`cubicweb-ctl`. - - -Create a cube -------------- - -Let's start by creating the cube environment in which we will develop :: - - cd ~/hg - - cubicweb-ctl newcube mycube - - # answer questions - hg init moncube - cd mycube - hg add . - hg ci - -If all went well, you should see the cube you just create in the list -returned by `cubicweb-ctl list` in the section *Available components*, -and if it is not the case please refer to :ref:`ConfigurationEnv`. - -To use a cube, you have to list it in the variable ``__use__`` -of the file ``__pkginfo__.py`` of the instance. -This variable is used for the instance packaging (dependencies -handled by system utility tools such as APT) and the usable cubes -at the time the base is created (import_erschema('MyCube') will -not properly work otherwise). - -.. note:: - Please note that if you do not wish to use default directory - for your cubes library, then you want to use the option - --directory to specify where you would like to place - the source code of your cube: - ``cubicweb-ctl newcube --directory=/path/to/cubes/library cube_name`` - -Instance creation ------------------ - -Now that we created our cube, we can create an instance to view our -application in a web browser. To do so we will use a `all-in-on` -configuration to simplify things :: - - cubicweb-ctl create -c all-in-one mycube myinstance - -.. note:: - Please note that we created a new cube for a demo purpose but - you could have use an existing cube available in our standard library - such as blog or person for example. - -A serie of questions will be prompted to you, the default answer is usually -sufficient. You can anyway modify the configuration later on by editing -configuration files. When a user/psswd is requested to access the database -please use the login you create at the time you configured the database -(:ref:`ConfigurationPostgres`). - -It is important to distinguish here the user used to access the database and -the user used to login to the cubicweb application. When a `CubicWeb` application -starts, it uses the login/psswd for the database to get the schema and handle -low level transaction. But, when ``cubicweb-ctl create`` asks for -a manager login/psswd of `CubicWeb`, it refers to an application user you will -use during the development to administrate your web application. It will be -possible, later on, to create others users for your final web application. - -When this command is completed, the definition of your instance is -located in *~/etc/cubicweb.d/myinstance/*. To launch it, you just type :: - - cubicweb-ctl start -D myinstance - -The option `-D` specify the *debug mode* : the instance is not running in -server mode and does not disconnect from the termnial, which simplifies debugging -in case the instance is not properly launched. You can see how it looks by -visiting the URL `http://localhost:8080` (the port number depends of your -configuration). To login, please use the cubicweb administrator login/psswd you -defined when you created the instance. - -To shutdown the instance, Crtl-C in the terminal window is enough. -If you did not use the option `-D`, then type :: - - cubicweb-ctl stop myinstance - -This is it! All is settled down to start developping your data model... - - -Usage of `cubicweb-liveserver` -`````````````````````````````` - -To quickly test a new cube, you can also use the script `cubicweb-liveserver` -which allows to create an application in memory (use of SQLite database by -default) and make it accessible through a web server :: - - cubicweb-ctl live-server mycube - -or by using an existing database (SQLite or Postgres):: - - cubicweb-ctl live-server -s myfile_sources mycube - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/C030-site-config.en.txt --- a/doc/book/en/C030-site-config.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -.. -*- coding: utf-8 -*- - -User interface for web site configuration -========================================= - -.. image:: images/lax-book.03-site-config-panel.en.png - -This panel allows you to configure the appearance of your application site. -Six menus are available and we will go through each of them to explain how -to use them. - -Navigation -~~~~~~~~~~ -This menu provides you a way to adjust some navigation options depending on -your needs, such as the number of entities to display by page of results. -Follows the detailled list of available options : - -* navigation.combobox-limit : maximum number of entities to display in related - combo box (sample format: 23) -* navigation.page-size : maximum number of objects displayed by page of results - (sample format: 23) -* navigation.related-limit : maximum number of related entities to display in - the primary view (sample format: 23) -* navigation.short-line-size : maximum number of characters in short description - (sample format: 23) - -UI -~~ -This menu provides you a way to customize the user interface settings such as -date format or encoding in the produced html. -Follows the detailled list of available options : - -* ui.date-format : how to format date in the ui ("man strftime" for format description) -* ui.datetime-format : how to format date and time in the ui ("man strftime" for format - description) -* ui.default-text-format : default text format for rich text fields. -* ui.encoding : user interface encoding -* ui.fckeditor :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. -* ui.float-format : how to format float numbers in the ui -* ui.language : language of the user interface -* ui.main-template : id of main template used to render pages -* ui.site-title : site title, which is displayed right next to the logo in the header -* ui.time-format : how to format time in the ui ("man strftime" for format description) - - -Actions -~~~~~~~ -This menu provides a way to configure the context in which you expect the actions -to be displayed to the user and if you want the action to be visible or not. -You must have notice that when you view a list of entities, an action box is -available on the left column which display some actions as well as a drop-down -menu for more actions. - -The context available are : - -* mainactions : actions listed in the left box -* moreactions : actions listed in the `more` menu of the left box -* addrelated : add actions listed in the left box -* useractions : actions listed in the first section of drop-down menu - accessible from the right corner user login link -* siteactions : actions listed in the second section of drop-down menu - accessible from the right corner user login link -* hidden : select this to hide the specific action - -Boxes -~~~~~ -The application has already a pre-defined set of boxes you can use right away. -This configuration section allows you to place those boxes where you want in the -application interface to customize it. - -The available boxes are : - -* actions box : box listing the applicable actions on the displayed data - -* boxes_blog_archives_box : box listing the blog archives - -* possible views box : box listing the possible views for the displayed data - -* rss box : RSS icon to get displayed data as a RSS thread - -* search box : search box - -* startup views box : box listing the configuration options available for - the application site, such as `Preferences` and `Site Configuration` - -Components -~~~~~~~~~~ -[WRITE ME] - -Contextual components -~~~~~~~~~~~~~~~~~~~~~ -[WRITE ME] - diff -r 49075f57cf2c -r aa09e20dd8c0 doc/book/en/C040-instance-config.en.txt --- a/doc/book/en/C040-instance-config.en.txt Tue May 05 17:18:49 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -.. -*- coding: utf-8 -*- - - -Configure an instance -===================== - -While creating an instance, a configuration file is generated in:: - - $ (CW_REGISTRY) / / .conf - -For example:: - - /etc/cubicweb.d/JPL/all-in-one.conf - -It is a simple text file format INI. In the following description, -each option name is prefixed with its own section and followed by its -default value if necessary, e.g. "`
    .