__init__.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4475 37c413a07216
child 5081 2ea98b8512dd
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
     5 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
     5 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     7 :license: Library General Public License version 2 - http://www.gnu.org/licenses
     7 :license: Library General Public License version 2 - http://www.gnu.org/licenses
     8 """
     8 """
     9 __docformat__ = "restructuredtext en"
     9 __docformat__ = "restructuredtext en"
    10 from cubicweb.__pkginfo__ import version as __version__
       
    11 
    10 
    12 import __builtin__
    11 import __builtin__
    13 # '_' is available in builtins to mark internationalized string but should
    12 # '_' is available in builtins to mark internationalized string but should
    14 # not be used to do the actual translation
    13 # not be used to do the actual translation
    15 if not hasattr(__builtin__, '_'):
    14 if not hasattr(__builtin__, '_'):
    17 
    16 
    18 CW_SOFTWARE_ROOT = __path__[0]
    17 CW_SOFTWARE_ROOT = __path__[0]
    19 
    18 
    20 import sys, os, logging
    19 import sys, os, logging
    21 from StringIO import StringIO
    20 from StringIO import StringIO
    22 from urllib import quote as urlquote, unquote as urlunquote
       
    23 
    21 
    24 from logilab.common.decorators import cached
       
    25 from logilab.common.logging_ext import set_log_methods
    22 from logilab.common.logging_ext import set_log_methods
    26 
    23 
    27 
    24 
    28 if os.environ.get('APYCOT_ROOT'):
    25 if os.environ.get('APYCOT_ROOT'):
    29     logging.basicConfig(level=logging.CRITICAL)
    26     logging.basicConfig(level=logging.CRITICAL)
    30 else:
    27 else:
    31     logging.basicConfig()
    28     logging.basicConfig()
       
    29 
       
    30 from cubicweb.__pkginfo__ import version as __version__
    32 
    31 
    33 
    32 
    34 set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb'))
    33 set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb'))
    35 
    34 
    36 # make all exceptions accessible from the package
    35 # make all exceptions accessible from the package
    55     def write(self, data):
    54     def write(self, data):
    56         assert isinstance(data, (str, buffer)), \
    55         assert isinstance(data, (str, buffer)), \
    57                "Binary objects must use raw strings, not %s" % data.__class__
    56                "Binary objects must use raw strings, not %s" % data.__class__
    58         StringIO.write(self, data)
    57         StringIO.write(self, data)
    59 
    58 
       
    59 # use this dictionary to rename entity types while keeping bw compat
       
    60 ETYPE_NAME_MAP = {}
    60 
    61 
    61 class RequestSessionMixIn(object):
    62 # XXX cubic web cube migration map. See if it's worth keeping this mecanism
    62     """mixin class containing stuff shared by server session and web request
    63 #     to help in cube renaming
    63     """
    64 CW_MIGRATION_MAP = {}
    64     def __init__(self, vreg):
       
    65         self.vreg = vreg
       
    66         try:
       
    67             encoding = vreg.property_value('ui.encoding')
       
    68         except: # no vreg or property not registered
       
    69             encoding = 'utf-8'
       
    70         self.encoding = encoding
       
    71         # cache result of execution for (rql expr / eids),
       
    72         # should be emptied on commit/rollback of the server session / web
       
    73         # connection
       
    74         self.local_perm_cache = {}
       
    75 
       
    76     def property_value(self, key):
       
    77         if self.user:
       
    78             return self.user.property_value(key)
       
    79         return self.vreg.property_value(key)
       
    80 
       
    81     def etype_rset(self, etype, size=1):
       
    82         """return a fake result set for a particular entity type"""
       
    83         from cubicweb.rset import ResultSet
       
    84         rset = ResultSet([('A',)]*size, '%s X' % etype,
       
    85                          description=[(etype,)]*size)
       
    86         def get_entity(row, col=0, etype=etype, req=self, rset=rset):
       
    87             return req.vreg.etype_class(etype)(req, rset, row, col)
       
    88         rset.get_entity = get_entity
       
    89         return self.decorate_rset(rset)
       
    90 
       
    91     def eid_rset(self, eid, etype=None):
       
    92         """return a result set for the given eid without doing actual query
       
    93         (we have the eid, we can suppose it exists and user has access to the
       
    94         entity)
       
    95         """
       
    96         from cubicweb.rset import ResultSet
       
    97         eid = typed_eid(eid)
       
    98         if etype is None:
       
    99             etype = self.describe(eid)[0]
       
   100         rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
       
   101                          [(etype,)])
       
   102         return self.decorate_rset(rset)
       
   103 
       
   104     def empty_rset(self):
       
   105         """return an empty result set. This is used e.g. to substitute
       
   106         to a real result set if the user doesn't have permission to
       
   107         access the results of a query.
       
   108         """
       
   109         from cubicweb.rset import ResultSet
       
   110         return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
       
   111 
       
   112     def entity_from_eid(self, eid, etype=None):
       
   113         try:
       
   114             return self.entity_cache(eid)
       
   115         except KeyError:
       
   116             rset = self.eid_rset(eid, etype)
       
   117             entity = rset.get_entity(0, 0)
       
   118             self.set_entity_cache(entity)
       
   119             return entity
       
   120 
       
   121     def entity_cache(self, eid):
       
   122         raise KeyError
       
   123     def set_entity_cache(self, entity):
       
   124         pass
       
   125 
       
   126     def create_entity(self, etype, _cw_unsafe=False, **kwargs):
       
   127         """add a new entity of the given type
       
   128 
       
   129         Example (in a shell session):
       
   130 
       
   131         c = create_entity('Company', name=u'Logilab')
       
   132         create_entity('Person', works_for=c, firstname=u'John', lastname=u'Doe')
       
   133 
       
   134         """
       
   135         if _cw_unsafe:
       
   136             execute = self.unsafe_execute
       
   137         else:
       
   138             execute = self.execute
       
   139         rql = 'INSERT %s X' % etype
       
   140         relations = []
       
   141         restrictions = set()
       
   142         cachekey = []
       
   143         pending_relations = []
       
   144         for attr, value in kwargs.items():
       
   145             if isinstance(value, (tuple, list, set, frozenset)):
       
   146                 if len(value) == 1:
       
   147                     value = iter(value).next()
       
   148                 else:
       
   149                     del kwargs[attr]
       
   150                     pending_relations.append( (attr, value) )
       
   151                     continue
       
   152             if hasattr(value, 'eid'): # non final relation
       
   153                 rvar = attr.upper()
       
   154                 # XXX safer detection of object relation
       
   155                 if attr.startswith('reverse_'):
       
   156                     relations.append('%s %s X' % (rvar, attr[len('reverse_'):]))
       
   157                 else:
       
   158                     relations.append('X %s %s' % (attr, rvar))
       
   159                 restriction = '%s eid %%(%s)s' % (rvar, attr)
       
   160                 if not restriction in restrictions:
       
   161                     restrictions.add(restriction)
       
   162                 cachekey.append(attr)
       
   163                 kwargs[attr] = value.eid
       
   164             else: # attribute
       
   165                 relations.append('X %s %%(%s)s' % (attr, attr))
       
   166         if relations:
       
   167             rql = '%s: %s' % (rql, ', '.join(relations))
       
   168         if restrictions:
       
   169             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
       
   170         created = execute(rql, kwargs, cachekey).get_entity(0, 0)
       
   171         for attr, values in pending_relations:
       
   172             if attr.startswith('reverse_'):
       
   173                 restr = 'Y %s X' % attr[len('reverse_'):]
       
   174             else:
       
   175                 restr = 'X %s Y' % attr
       
   176             execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
       
   177                 restr, ','.join(str(r.eid) for r in values)),
       
   178                          {'x': created.eid}, 'x')
       
   179         return created
       
   180 
       
   181     # url generation methods ##################################################
       
   182 
       
   183     def build_url(self, *args, **kwargs):
       
   184         """return an absolute URL using params dictionary key/values as URL
       
   185         parameters. Values are automatically URL quoted, and the
       
   186         publishing method to use may be specified or will be guessed.
       
   187         """
       
   188         # use *args since we don't want first argument to be "anonymous" to
       
   189         # avoid potential clash with kwargs
       
   190         assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
       
   191         method = args[0]
       
   192         base_url = kwargs.pop('base_url', None)
       
   193         if base_url is None:
       
   194             base_url = self.base_url()
       
   195         if '_restpath' in kwargs:
       
   196             assert method == 'view', method
       
   197             path = kwargs.pop('_restpath')
       
   198         else:
       
   199             path = method
       
   200         if not kwargs:
       
   201             return u'%s%s' % (base_url, path)
       
   202         return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
       
   203 
       
   204 
       
   205     def build_url_params(self, **kwargs):
       
   206         """return encoded params to incorporate them in an URL"""
       
   207         args = []
       
   208         for param, values in kwargs.items():
       
   209             if not isinstance(values, (list, tuple)):
       
   210                 values = (values,)
       
   211             for value in values:
       
   212                 args.append(u'%s=%s' % (param, self.url_quote(value)))
       
   213         return '&'.join(args)
       
   214 
       
   215     def url_quote(self, value, safe=''):
       
   216         """urllib.quote is not unicode safe, use this method to do the
       
   217         necessary encoding / decoding. Also it's designed to quote each
       
   218         part of a url path and so the '/' character will be encoded as well.
       
   219         """
       
   220         if isinstance(value, unicode):
       
   221             quoted = urlquote(value.encode(self.encoding), safe=safe)
       
   222             return unicode(quoted, self.encoding)
       
   223         return urlquote(str(value), safe=safe)
       
   224 
       
   225     def url_unquote(self, quoted):
       
   226         """returns a unicode unquoted string
       
   227 
       
   228         decoding is based on `self.encoding` which is the encoding
       
   229         used in `url_quote`
       
   230         """
       
   231         if isinstance(quoted, unicode):
       
   232             quoted = quoted.encode(self.encoding)
       
   233         try:
       
   234             return unicode(urlunquote(quoted), self.encoding)
       
   235         except UnicodeDecodeError: # might occurs on manually typed URLs
       
   236             return unicode(urlunquote(quoted), 'iso-8859-1')
       
   237 
       
   238 
       
   239     # session's user related methods #####################################
       
   240 
       
   241     @cached
       
   242     def user_data(self):
       
   243         """returns a dictionnary with this user's information"""
       
   244         userinfo = {}
       
   245         if self.is_internal_session:
       
   246             userinfo['login'] = "cubicweb"
       
   247             userinfo['name'] = "cubicweb"
       
   248             userinfo['email'] = ""
       
   249             return userinfo
       
   250         user = self.actual_session().user
       
   251         userinfo['login'] = user.login
       
   252         userinfo['name'] = user.name()
       
   253         userinfo['email'] = user.get_email()
       
   254         return userinfo
       
   255 
       
   256     def is_internal_session(self):
       
   257         """overrided on the server-side"""
       
   258         return False
       
   259 
       
   260     # abstract methods to override according to the web front-end #############
       
   261 
       
   262     def base_url(self):
       
   263         """return the root url of the instance"""
       
   264         raise NotImplementedError
       
   265 
       
   266     def decorate_rset(self, rset):
       
   267         """add vreg/req (at least) attributes to the given result set """
       
   268         raise NotImplementedError
       
   269 
       
   270     def describe(self, eid):
       
   271         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
       
   272         raise NotImplementedError
       
   273 
       
   274 
       
   275 # XXX 2.45 is allowing nicer entity type names, use this map for bw compat
       
   276 ETYPE_NAME_MAP = {# 3.2 migration
       
   277                   'ECache': 'CWCache',
       
   278                   'EUser': 'CWUser',
       
   279                   'EGroup': 'CWGroup',
       
   280                   'EProperty': 'CWProperty',
       
   281                   'EFRDef': 'CWAttribute',
       
   282                   'ENFRDef': 'CWRelation',
       
   283                   'ERType': 'CWRType',
       
   284                   'EEType': 'CWEType',
       
   285                   'EConstraintType': 'CWConstraintType',
       
   286                   'EConstraint': 'CWConstraint',
       
   287                   'EPermission': 'CWPermission',
       
   288                    # 2.45 migration
       
   289                   'Eetype': 'CWEType',
       
   290                   'Ertype': 'CWRType',
       
   291                   'Efrdef': 'CWAttribute',
       
   292                   'Enfrdef': 'CWRelation',
       
   293                   'Econstraint': 'CWConstraint',
       
   294                   'Econstrainttype': 'CWConstraintType',
       
   295                   'Epermission': 'CWPermission',
       
   296                   'Egroup': 'CWGroup',
       
   297                   'Euser': 'CWUser',
       
   298                   'Eproperty': 'CWProperty',
       
   299                   'Emailaddress': 'EmailAddress',
       
   300                   'Rqlexpression': 'RQLExpression',
       
   301                   'Trinfo': 'TrInfo',
       
   302                   }
       
   303 
       
   304 
       
   305 
       
   306 # XXX cubic web cube migration map
       
   307 CW_MIGRATION_MAP = {'erudi': 'cubicweb',
       
   308 
       
   309                     'eaddressbook': 'addressbook',
       
   310                     'ebasket': 'basket',
       
   311                     'eblog': 'blog',
       
   312                     'ebook': 'book',
       
   313                     'ecomment': 'comment',
       
   314                     'ecompany': 'company',
       
   315                     'econference':  'conference',
       
   316                     'eemail': 'email',
       
   317                     'eevent': 'event',
       
   318                     'eexpense': 'expense',
       
   319                     'efile': 'file',
       
   320                     'einvoice': 'invoice',
       
   321                     'elink': 'link',
       
   322                     'emailinglist': 'mailinglist',
       
   323                     'eperson': 'person',
       
   324                     'eshopcart': 'shopcart',
       
   325                     'eskillmat': 'skillmat',
       
   326                     'etask': 'task',
       
   327                     'eworkcase': 'workcase',
       
   328                     'eworkorder': 'workorder',
       
   329                     'ezone': 'zone',
       
   330                     'i18ncontent': 'i18ncontent',
       
   331                     'svnfile': 'vcsfile',
       
   332 
       
   333                     'eclassschemes': 'keyword',
       
   334                     'eclassfolders': 'folder',
       
   335                     'eclasstags': 'tag',
       
   336 
       
   337                     'jpl': 'jpl',
       
   338                     'jplintra': 'jplintra',
       
   339                     'jplextra': 'jplextra',
       
   340                     'jplorg': 'jplorg',
       
   341                     'jplrecia': 'jplrecia',
       
   342                     'crm': 'crm',
       
   343                     'agueol': 'agueol',
       
   344                     'docaster': 'docaster',
       
   345                     'asteretud': 'asteretud',
       
   346                     }
       
   347 
    65 
   348 def neg_role(role):
    66 def neg_role(role):
   349     if role == 'subject':
    67     if role == 'subject':
   350         return 'object'
    68         return 'object'
   351     return 'subject'
    69     return 'subject'
   359 def target(obj):
    77 def target(obj):
   360     try:
    78     try:
   361         return obj.target
    79         return obj.target
   362     except AttributeError:
    80     except AttributeError:
   363         return neg_role(obj.role)
    81         return neg_role(obj.role)
   364 
       
   365 def underline_title(title, car='-'):
       
   366     return title+'\n'+(car*len(title))
       
   367 
    82 
   368 
    83 
   369 class CubicWebEventManager(object):
    84 class CubicWebEventManager(object):
   370     """simple event / callback manager.
    85     """simple event / callback manager.
   371 
    86