# HG changeset patch
# User sylvain.thenault@logilab.fr
# Date 1242298313 -7200
# Node ID d0173f4eb6471eb89fb9340cd653a398d12ac7bf
# Parent 0c7b091215077dd2299db9f6beff59fddf8f2150# Parent c258394c01483d7fd0e92a3c5c6f5db91ed8550f
merge
diff -r c258394c0148 -r d0173f4eb647 .hgtags
--- a/.hgtags Thu May 14 12:51:38 2009 +0200
+++ b/.hgtags Thu May 14 12:51:53 2009 +0200
@@ -22,3 +22,7 @@
dfaedb0bba88e3a4e931948bb0c6a9587269303f cubicweb-debian-version-3_1_1-1
a9ba200ab15098704a6255387c558c02488551c6 cubicweb-version-3_1_2
a823124b812f4fa494bfceb773f3ca1cd00407e8 cubicweb-debian-version-3_1_2-1
+a5dc91adb7c133f83f5ad9cceb07bc246d21ed01 cubicweb-version-3_1_3
+9e98dec0768b87363a7826a04636dc161ed0ec7d cubicweb-debian-version-3_1_3-1
+e0e0a1c3d80f4fbf4bbd55066278e467b75df8a4 cubicweb-version-3_1_4
+0e132fbae9cc5e004f4b79a8b842addad43519a7 cubicweb-debian-version-3_1_4-1
diff -r c258394c0148 -r d0173f4eb647 MANIFEST.in
--- a/MANIFEST.in Thu May 14 12:51:38 2009 +0200
+++ b/MANIFEST.in Thu May 14 12:51:53 2009 +0200
@@ -25,6 +25,6 @@
recursive-include web/test/data *.js *.css *.png *.gif *.jpg *.ico external_resources
recursive-include devtools/test/data *
-recursive-include skeleton *.py*.css *.js *.po compat *.in *.tmpl
+recursive-include skeleton *.py *.css *.js *.po compat *.in *.tmpl
prune misc/cwfs
diff -r c258394c0148 -r d0173f4eb647 __init__.py
--- a/__init__.py Thu May 14 12:51:38 2009 +0200
+++ b/__init__.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 __pkginfo__.py
--- a/__pkginfo__.py Thu May 14 12:51:38 2009 +0200
+++ b/__pkginfo__.py Thu May 14 12:51:53 2009 +0200
@@ -6,7 +6,7 @@
distname = "cubicweb"
modname = "cubicweb"
-numversion = (3, 1, 2)
+numversion = (3, 2, 0)
version = '.'.join(str(num) for num in numversion)
license = 'LGPL v2'
diff -r c258394c0148 -r d0173f4eb647 _exceptions.py
--- a/_exceptions.py Thu May 14 12:51:38 2009 +0200
+++ b/_exceptions.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 appobject.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/appobject.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/__init__.py
--- a/common/__init__.py Thu May 14 12:51:38 2009 +0200
+++ b/common/__init__.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/appobject.py
--- a/common/appobject.py Thu May 14 12:51:38 2009 +0200
+++ b/common/appobject.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/entity.py
--- a/common/entity.py Thu May 14 12:51:38 2009 +0200
+++ b/common/entity.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/html4zope.py
--- a/common/html4zope.py Thu May 14 12:51:38 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('
')
-
+
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 c258394c0148 -r d0173f4eb647 common/mttransforms.py
--- a/common/mttransforms.py Thu May 14 12:51:38 2009 +0200
+++ b/common/mttransforms.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/registerers.py
--- a/common/registerers.py Thu May 14 12:51:38 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +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
- 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 c258394c0148 -r d0173f4eb647 common/rest.py
--- a/common/rest.py Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 common/schema.py
--- a/common/schema.py Thu May 14 12:51:38 2009 +0200
+++ b/common/schema.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/selectors.py
--- a/common/selectors.py Thu May 14 12:51:38 2009 +0200
+++ b/common/selectors.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/tags.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/common/tags.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/tal.py
--- a/common/tal.py Thu May 14 12:51:38 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'),
- '
')
- 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'
...')
@@ -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 c258394c0148 -r d0173f4eb647 common/test/unittest_utils.py
--- a/common/test/unittest_utils.py Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 common/uilib.py
--- a/common/uilib.py Thu May 14 12:51:38 2009 +0200
+++ b/common/uilib.py Thu May 14 12:51:53 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%s>' % (html_escape(unicode(content)), tag)
+ if escapecontent:
+ content = html_escape(unicode(content))
+ value += u'>%s%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 += '
%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 c258394c0148 -r d0173f4eb647 common/utils.py
--- a/common/utils.py Thu May 14 12:51:38 2009 +0200
+++ b/common/utils.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 common/view.py
--- a/common/view.py Thu May 14 12:51:38 2009 +0200
+++ b/common/view.py Thu May 14 12:51:53 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'
")
-
- 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 c258394c0148 -r d0173f4eb647 cwconfig.py
--- a/cwconfig.py Thu May 14 12:51:38 2009 +0200
+++ b/cwconfig.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 cwctl.py
--- a/cwctl.py Thu May 14 12:51:38 2009 +0200
+++ b/cwctl.py Thu May 14 12:51:53 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,15 +613,17 @@
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
- if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
- self.stop_application(appid)
- 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
- config.set_sources_mode(self.config.ext_sources or ('migration',))
+ try:
+ config.set_sources_mode(self.config.ext_sources or ('migration',))
+ except AttributeError:
+ # not a server config
+ pass
# get application and installed versions for the server and the componants
print 'getting versions configuration from the repository...'
mih = config.migration_handler()
@@ -642,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
@@ -655,6 +659,9 @@
return
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 (cwcfg.mode == 'dev' or self.config.nostartstop):
+ self.stop_application(appid)
# run cubicweb/componants migration scripts
mih.migrate(vcconf, reversed(toupgrade), self.config)
# rewrite main configuration file
@@ -662,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:
@@ -678,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
@@ -701,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,
@@ -710,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
@@ -728,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:
@@ -766,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
@@ -778,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,
@@ -798,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 c258394c0148 -r d0173f4eb647 cwvreg.py
--- a/cwvreg.py Thu May 14 12:51:38 2009 +0200
+++ b/cwvreg.py Thu May 14 12:51:53 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,17 +216,17 @@
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)
+ actions = self.possible_vobjects('actions', req, rset, **kwargs)
else:
- actions = rset.possible_actions() # cached implementation
+ actions = rset.possible_actions(**kwargs) # cached implementation
result = {}
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 e: list(vocab(propkey, req))
- else:
- vocabfunc = lambda e: 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 c258394c0148 -r d0173f4eb647 dbapi.py
--- a/dbapi.py Thu May 14 12:51:38 2009 +0200
+++ b/dbapi.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 debian.hardy/compat
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/compat Thu May 14 12:51:53 2009 +0200
@@ -0,0 +1,1 @@
+5
diff -r c258394c0148 -r d0173f4eb647 debian.hardy/control
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/control Thu May 14 12:51:53 2009 +0200
@@ -0,0 +1,131 @@
+Source: cubicweb
+Section: web
+Priority: optional
+Maintainer: Logilab S.A.
+Uploaders: Sylvain Thenault ,
+ Julien Jehannet ,
+ Aurélien Campéas
+Build-Depends: debhelper (>= 5), python-dev (>=2.4), python-central (>= 0.5)
+Standards-Version: 3.8.0
+Homepage: http://www.cubicweb.org
+XS-Python-Version: >= 2.4, << 2.6
+
+
+Package: cubicweb
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-twisted (= ${source:Version}), cubicweb-client (= ${source:Version})
+XB-Recommends: (postgresql, postgresql-plpython, postgresql-contrib) | mysql | sqlite3
+Recommends: postgresql | mysql | sqlite3
+Description: the complete CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package will install all the components you need to run cubicweb on
+ a single machine. You can also deploy cubicweb by running the different
+ process on different computers, in which case you need to install the
+ corresponding packages on the different hosts.
+
+
+Package: cubicweb-server
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Conflicts: cubicweb-multisources
+Replaces: cubicweb-multisources
+Provides: cubicweb-multisources
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-indexer (>= 0.6.1), python-psycopg2 | python-mysqldb | python-pysqlite2
+Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Description: server part of the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the repository server part of the system.
+ .
+ This package provides the repository server part of the library and
+ necessary shared data files such as the schema library.
+
+
+Package: cubicweb-twisted
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Provides: cubicweb-web-frontend
+Depends: ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web2
+Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Description: twisted-based web interface for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a twisted based HTTP server to serve
+ the adaptative web interface (see cubicweb-web package).
+ .
+ This package provides only the twisted server part of the library.
+
+
+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
+Description: web interface library for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides an adaptative web interface to the CubicWeb server.
+ Install the cubicweb-twisted package to serve this interface via HTTP.
+ .
+ This package provides the web interface part of the library and
+ necessary shared data files such as defaut views, images...
+
+
+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
+Conflicts: cubicweb-core
+Replaces: cubicweb-core
+Description: common library for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the common parts of the library used by both server
+ code and web application code.
+
+
+Package: cubicweb-ctl
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version})
+Description: tool to manage the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a control script to manage (create, upgrade, start,
+ stop, etc) CubicWeb applications. It also include the init.d script
+ to automatically start and stop CubicWeb applications on boot or shutdown.
+
+
+Package: cubicweb-client
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-ctl (= ${source:Version}), pyro
+Description: RQL command line client for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a RQL (Relation Query Language) command line client using
+ pyro to connect to a repository server.
+
+
+Package: cubicweb-dev
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-web (= ${source:Version}), python-pysqlite2
+Suggests: w3c-dtd-xhtml
+Description: tests suite and development tools for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the CubicWeb tests suite and some development tools
+ helping in the creation of application.
+
+
+Package: cubicweb-documentation
+Architecture: all
+Recommends: doc-base
+Description: documentation for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the system's documentation.
diff -r c258394c0148 -r d0173f4eb647 debian.hardy/rules
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/rules Thu May 14 12:51:53 2009 +0200
@@ -0,0 +1,78 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+PY_VERSION:=$(shell pyversions -d)
+
+build: build-stamp
+build-stamp:
+ dh_testdir
+ # XXX doesn't work if logilab-doctools, logilab-xml are not in build depends
+ # and I can't get pbuilder find them in its chroot :(
+ # cd doc && make
+ # FIXME cleanup and use sphinx-build as build-depends ?
+ python setup.py build
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ rm -rf build
+ #rm -rf debian/cubicweb-*/
+ find . -name "*.pyc" -delete
+ rm -f $(basename $(wildcard debian/*.in))
+ dh_clean
+
+install: build $(basename $(wildcard debian/*.in))
+ dh_testdir
+ dh_testroot
+ dh_clean
+ dh_installdirs
+
+ #python setup.py install_lib --no-compile --install-dir=debian/cubicweb-common/usr/lib/python2.4/site-packages/
+ python setup.py -q install --no-compile --prefix=debian/tmp/usr
+
+ # Put all the python library and data in cubicweb-common
+ # and scripts in cubicweb-server
+ dh_install -vi
+ #dh_lintian XXX not before debhelper 7
+
+ # Remove unittests directory (should be available in cubicweb-dev only)
+ rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/server/test
+ rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/sobjects/test
+ rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
+ rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/common/test
+
+ # cubes directory must be managed as a valid python module
+ touch debian/cubicweb-common/usr/share/cubicweb/cubes/__init__.py
+
+%: %.in
+ sed "s/PY_VERSION/${PY_VERSION}/g" < $< > $@
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot -i
+ dh_pycentral -i
+ dh_installinit -i -n --name cubicweb -u"defaults 99"
+ dh_installlogrotate -i
+ dh_installdocs -i -A README
+ dh_installman -i
+ dh_installchangelogs -i
+ dh_link -i
+ dh_compress -i -X.py -X.ini -X.xml
+ dh_fixperms -i
+ dh_installdeb -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+binary-arch:
+
+binary: binary-indep
+.PHONY: build clean binary binary-indep binary-arch
+
diff -r c258394c0148 -r d0173f4eb647 debian/changelog
--- a/debian/changelog Thu May 14 12:51:38 2009 +0200
+++ b/debian/changelog Thu May 14 12:51:53 2009 +0200
@@ -1,3 +1,21 @@
+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
+
+ -- Aurélien Campéas Mon, 06 Apr 2009 14:30:00 +0200
+
+cubicweb (3.1.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault Mon, 06 Apr 2009 08:52:27 +0200
+
cubicweb (3.1.2-1) unstable; urgency=low
* new upstream release
diff -r c258394c0148 -r d0173f4eb647 debian/control
--- a/debian/control Thu May 14 12:51:38 2009 +0200
+++ b/debian/control Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 debian/cubicweb-ctl.postinst
--- a/debian/cubicweb-ctl.postinst Thu May 14 12:51:38 2009 +0200
+++ b/debian/cubicweb-ctl.postinst Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 debian/cubicweb-web.postinst
--- a/debian/cubicweb-web.postinst Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 devtools/__init__.py
--- a/devtools/__init__.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/__init__.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/_apptest.py
--- a/devtools/_apptest.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/_apptest.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/apptest.py
--- a/devtools/apptest.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/apptest.py Thu May 14 12:51:53 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"
@@ -14,6 +14,8 @@
from logilab.common.pytest import nocoverage
from logilab.common.umessage import message_from_string
+from logilab.common.deprecation import deprecated_function
+
from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration
from cubicweb.devtools._apptest import TestEnvironment
from cubicweb.devtools.fake import FakeRequest
@@ -30,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
@@ -98,7 +100,7 @@
env = None
configcls = ApptestConfiguration
requestcls = FakeRequest
-
+
# user / session management ###############################################
def user(self, req=None):
@@ -116,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)
@@ -130,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
@@ -150,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)
@@ -158,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]
@@ -183,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))
@@ -213,11 +215,18 @@
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]
+
+ 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')):
res = {}
for a in self.vreg.possible_vobjects('actions', req, rset):
@@ -225,20 +234,17 @@
res.setdefault(a.category, []).append(a.__class__)
return res
- def paddrelactions(self, req, rset):
- return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
- if a.category == 'addrelated']
-
+
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
@@ -258,7 +264,7 @@
self.setup_database()
self.commit()
MAILBOX[:] = [] # reset mailbox
-
+
@nocoverage
def tearDown(self):
self.rollback()
@@ -349,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)
@@ -363,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'))
@@ -374,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()
@@ -382,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):
@@ -394,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
@@ -428,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:
@@ -440,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'):
@@ -488,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 c258394c0148 -r d0173f4eb647 devtools/cwtwill.py
--- a/devtools/cwtwill.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/cwtwill.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/devctl.py
--- a/devtools/devctl.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/devctl.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/fake.py
--- a/devtools/fake.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/fake.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/fill.py
--- a/devtools/fill.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/fill.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/htmlparser.py
--- a/devtools/htmlparser.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/htmlparser.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/livetest.py
--- a/devtools/livetest.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/livetest.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/repotest.py
--- a/devtools/repotest.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/repotest.py Thu May 14 12:51:53 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
@@ -72,7 +72,7 @@
def __contains__(self, key):
return key in self.iterkeys()
def __getitem__(self, key):
- for key_, value in self.iteritems():
+ for key_, value in list.__iter__(self):
if key == key_:
return value
raise KeyError(key)
@@ -80,6 +80,17 @@
return (x for x, y in list.__iter__(self))
def iteritems(self):
return (x for x in list.__iter__(self))
+ def items(self):
+ return [x for x in list.__iter__(self)]
+
+class DumbOrderedDict2(object):
+ def __init__(self, origdict, sortkey):
+ self.origdict = origdict
+ self.sortkey = sortkey
+ def __getattr__(self, attr):
+ return getattr(self.origdict, attr)
+ def __iter__(self):
+ return iter(sorted(self.origdict, key=self.sortkey))
from logilab.common.testlib import TestCase
@@ -92,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'})
@@ -103,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)
@@ -122,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]
@@ -137,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()
@@ -148,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
@@ -164,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]
@@ -184,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):
@@ -217,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)]
@@ -230,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):
@@ -256,17 +267,17 @@
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_var(self, sourcevars):
- pass
+ def _choose_term(self, sourceterms):
+ pass
_orig_merge_input_maps = PartPlanInformation.merge_input_maps
-_orig_choose_var = PartPlanInformation._choose_var
+_orig_choose_term = PartPlanInformation._choose_term
def _merge_input_maps(*args):
return sorted(_orig_merge_input_maps(*args))
-def _choose_var(self, sourcevars):
+def _choose_term(self, sourceterms):
# predictable order for test purpose
def get_key(x):
try:
@@ -279,17 +290,7 @@
except AttributeError:
# const
return x.value
- varsinorder = sorted(sourcevars, key=get_key)
- if len(self._sourcesvars) > 1:
- for var in varsinorder:
- if not var.scope is self.rqlst:
- return var, sourcevars.pop(var)
- else:
- for var in varsinorder:
- if var.scope is self.rqlst:
- return var, sourcevars.pop(var)
- var = varsinorder[0]
- return var, sourcevars.pop(var)
+ return _orig_choose_term(self, DumbOrderedDict2(sourceterms, get_key))
def do_monkey_patch():
@@ -299,7 +300,7 @@
ExecutionPlan.tablesinorder = None
ExecutionPlan.init_temp_table = _init_temp_table
PartPlanInformation.merge_input_maps = _merge_input_maps
- PartPlanInformation._choose_var = _choose_var
+ PartPlanInformation._choose_term = _choose_term
def undo_monkey_patch():
RQLRewriter.insert_snippets = _orig_insert_snippets
@@ -307,5 +308,4 @@
ExecutionPlan._check_permissions = _orig_check_permissions
ExecutionPlan.init_temp_table = _orig_init_temp_table
PartPlanInformation.merge_input_maps = _orig_merge_input_maps
- PartPlanInformation._choose_var = _orig_choose_var
-
+ PartPlanInformation._choose_term = _orig_choose_term
diff -r c258394c0148 -r d0173f4eb647 devtools/stresstester.py
--- a/devtools/stresstester.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/stresstester.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/test/data/schema/relations.rel
--- a/devtools/test/data/schema/relations.rel Thu May 14 12:51:38 2009 +0200
+++ b/devtools/test/data/schema/relations.rel Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/test/data/views/bug.py
--- a/devtools/test/data/views/bug.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/test/data/views/bug.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/test/runtests.py
--- a/devtools/test/runtests.py Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 devtools/test/unittest_dbfill.py
--- a/devtools/test/unittest_dbfill.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/test/unittest_dbfill.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/test/unittest_fill.py
--- a/devtools/test/unittest_fill.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/test/unittest_fill.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/test/unittest_testlib.py
--- a/devtools/test/unittest_testlib.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/test/unittest_testlib.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 devtools/testlib.py
--- a/devtools/testlib.py Thu May 14 12:51:38 2009 +0200
+++ b/devtools/testlib.py Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 doc/book/README
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/README Thu May 14 12:51:53 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 c258394c0148 -r d0173f4eb647 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:51:53 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 c258394c0148 -r d0173f4eb647 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:51:53 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 c258394c0148 -r d0173f4eb647 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:51:53 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 c258394c0148 -r d0173f4eb647 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:51:53 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 c258394c0148 -r d0173f4eb647 doc/book/en/A000-introduction.en.txt
--- a/doc/book/en/A000-introduction.en.txt Thu May 14 12:51:38 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-===================================
-Part I - Introduction to `CubicWeb`
-===================================
-
-This first part of the book will offer different reading path to
-present you with the `CubicWeb` framework, provide a tutorial to get a quick
-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 c258394c0148 -r d0173f4eb647 doc/book/en/A010-book-map.en.txt
--- a/doc/book/en/A010-book-map.en.txt Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 doc/book/en/A020-tutorial.en.txt
--- a/doc/book/en/A020-tutorial.en.txt Thu May 14 12:51:38 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Overview:
-
-Quick overview of `CubicWeb`
-============================
-
-`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:: A02a-create-cube.en.txt
-.. include:: A02b-components.en.txt
-.. include:: A02c-maintemplate.en.txt
-.. include:: A02d-conclusion.en.txt
diff -r c258394c0148 -r d0173f4eb647 doc/book/en/A02a-create-cube.en.txt
--- a/doc/book/en/A02a-create-cube.en.txt Thu May 14 12:51:38 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Create your cube
-----------------
-
-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``:
-
-::
-
- from cubicweb.schema import format_constraint
-
- 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 a directory ``~/etc/cubicweb.d/blogdemo``
-which will contain all the configuration files required to start
-you web application.
-
-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 entities views
---------------------------
-
-Each entity defined in a model inherits defaults views allowing
-different rendering of the data. You can redefine each of them
-according to your needs and preferences. If you feel like it then
-you have to know how a view is defined.
-
-
-The views 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 resulsets 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, you
-will have to 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 a score used to identify which view
-is the best to apply for the `result set` we are trying to
-display. 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. We will see more details
-on this in :ref:`DefinitionVues`.
-
-For example, the view named ``primary`` is the one used to display
-a single entity. We will now show you hos 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 pulication date a prefix specifying
-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'
' % 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 resultsets and resulsets can be tables of
-data, it is needed to recover the entity from its (row,col)
-coordinates. We will get to this in more detail later.
-
-The view has a ``self.w()`` method that is used to output data. In our
-example we use it to output HTML tags and values of the entity's attributes.
diff -r c258394c0148 -r d0173f4eb647 doc/book/en/A02b-components.en.txt
--- a/doc/book/en/A02b-components.en.txt Thu May 14 12:51:38 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 usefull command ``cubicweb-ctl shell blogdemo`` which
-launches an interactive migration Python shell. (see
-:ref:`cubicweb-ctl-shell` 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 c258394c0148 -r d0173f4eb647 doc/book/en/A02c-maintemplate.en.txt
--- a/doc/book/en/A02c-maintemplate.en.txt Thu May 14 12:51:38 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'
')
- helpcomp = self.vreg.select_component('help', self.req, self.rset)
- if helpcomp: # may not be available if Card is not defined in the schema
- helpcomp.dispatch(w=self.w)
- self.w(u'
')
- # lastcolumn
- self.w(u'
')
- 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 c258394c0148 -r d0173f4eb647 doc/book/en/A02d-conclusion.en.txt
--- a/doc/book/en/A02d-conclusion.en.txt Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 doc/book/en/A02d-rss-xml.en.txt
--- a/doc/book/en/A02d-rss-xml.en.txt Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 doc/book/en/A030-foundation.en.txt
--- a/doc/book/en/A030-foundation.en.txt Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 doc/book/en/A03a-concepts.en.txt
--- a/doc/book/en/A03a-concepts.en.txt Thu May 14 12:51:38 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
-parties communicates through RQL (`CubicWeb` query language implementation)
-and ResultSet (which will be explained in :ref:`TermsVocabulary`).
-
-The server manages all interactions with sources.
-
-
-.. note::
- For real, the client and server sides are integrated in the same
- process and interact directly, without the needs for distants
- calls using Pyro. It is important to note down that those two
- sides, client/server, are disjointed and it is possible to execute
- a couple of calls in distincts 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 as
- it is 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 corresponds 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 others
- vital informations for the system.
-
-*configuration*
- It is possible to create differents 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 functionnality 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 faster by importing cubes,
- adding entities and relationships and overriding the
- views that need to display or edit informations not provided by
- 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 sometime 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 empasize the way of browsing relations.
- This query language is inspired by SQL but is highest 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 dynamics objects,
-based on the schema or the library, are building the final application.
-The differents dymanic 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 dictionnary containing the values to use
-:eid_key:
- an implementation detail of the RQL queries 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 correponding key in the dictionnary
- 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 updates 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 forms 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`, dictionnary containing the values of a web form
-* `encoding`, characters encoding to use in the response
-
-But also:
-
-:Session data handling:
- * `session_data()`, returns a dictionnary 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 dictionnary 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`)
- * dictionnary `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
-others 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 messages 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 embeding 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 c258394c0148 -r d0173f4eb647 doc/book/en/B0-data-model.en.txt
--- a/doc/book/en/B0-data-model.en.txt Thu May 14 12:51:38 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 c258394c0148 -r d0173f4eb647 doc/book/en/B000-development.en.txt
--- a/doc/book/en/B000-development.en.txt Thu May 14 12:51:38 2009 +0200
+++ b/doc/book/en/B000-development.en.txt Thu May 14 12:51:53 2009 +0200
@@ -1,5 +1,6 @@
.. -*- coding: utf-8 -*-
+.. _Part2:
=====================
Part II - Development
diff -r c258394c0148 -r d0173f4eb647 doc/book/en/B0010-define-schema.en.txt
--- a/doc/book/en/B0010-define-schema.en.txt Thu May 14 12:51:38 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Data model definition (*schema*)
-================================
-
-The schema is the core piece of a `CubicWeb` application as it defines
-the data model handled. 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
-"