cwvreg.py
changeset 0 b97547f5f1fa
child 169 0e031b66cb0b
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """extend the generic VRegistry with some cubicweb specific stuff
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 from warnings import warn
       
    10 
       
    11 from logilab.common.decorators import cached, clear_cache
       
    12 
       
    13 from rql import RQLHelper
       
    14 
       
    15 from cubicweb import Binary, UnknownProperty
       
    16 from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject
       
    17 
       
    18 _ = unicode
       
    19 
       
    20 class DummyCursorError(Exception): pass
       
    21 class RaiseCursor:
       
    22     @classmethod
       
    23     def execute(cls, rql, args=None, eid_key=None):
       
    24         raise DummyCursorError()
       
    25 
       
    26 
       
    27 class CubicWebRegistry(VRegistry):
       
    28     """extend the generic VRegistry with some cubicweb specific stuff"""
       
    29     
       
    30     def __init__(self, config, debug=None):
       
    31         # first init log service
       
    32         config.init_log(debug=debug)
       
    33         super(CubicWebRegistry, self).__init__(config)
       
    34         self.schema = None
       
    35         self.reset()
       
    36         self.initialized = False
       
    37         
       
    38     def items(self):
       
    39         return [item for item in self._registries.items()
       
    40                 if not item[0] in ('propertydefs', 'propertyvalues')]
       
    41 
       
    42     def values(self):
       
    43         return [value for key,value in self._registries.items()
       
    44                 if not key in ('propertydefs', 'propertyvalues')]
       
    45     
       
    46     def reset(self):
       
    47         self._registries = {}
       
    48         self._lastmodifs = {}
       
    49         # two special registries, propertydefs which care all the property definitions, and
       
    50         # propertyvals which contains values for those properties
       
    51         self._registries['propertydefs'] = {}
       
    52         self._registries['propertyvalues'] = self.eprop_values = {}
       
    53         for key, propdef in self.config.eproperty_definitions():
       
    54             self.register_property(key, **propdef)
       
    55             
       
    56     def set_schema(self, schema):
       
    57         """set application'schema and load application objects"""
       
    58         self.schema = schema
       
    59         clear_cache(self, 'rqlhelper')
       
    60         # now we can load application's web objects
       
    61         self.register_objects(self.config.vregistry_path())
       
    62         
       
    63     def update_schema(self, schema):
       
    64         """update .schema attribute on registered objects, necessary for some
       
    65         tests
       
    66         """
       
    67         self.schema = schema
       
    68         for registry, regcontent in self._registries.items():
       
    69             if registry in ('propertydefs', 'propertyvalues'):
       
    70                 continue
       
    71             for objects in regcontent.values():
       
    72                 for obj in objects:
       
    73                     obj.schema = schema
       
    74         
       
    75     def register_objects(self, path, force_reload=None):
       
    76         """overriden to handle type class cache issue"""
       
    77         if  super(CubicWebRegistry, self).register_objects(path, force_reload):
       
    78             # clear etype cache if you don't want to run into deep weirdness
       
    79             clear_cache(self, 'etype_class')
       
    80             # remove vobjects that don't support any available interface
       
    81             interfaces = set()
       
    82             for classes in self.get('etypes', {}).values():
       
    83                 for cls in classes:
       
    84                     interfaces.update(cls.__implements__)
       
    85             if not self.config.cleanup_interface_sobjects:
       
    86                 return
       
    87             for registry, regcontent in self._registries.items():
       
    88                 if registry in ('propertydefs', 'propertyvalues', 'etypes'):
       
    89                     continue
       
    90                 for oid, objects in regcontent.items():
       
    91                     for obj in reversed(objects[:]):
       
    92                         if not obj in objects:
       
    93                             continue # obj has been kicked by a previous one
       
    94                         accepted = set(getattr(obj, 'accepts_interfaces', ()))
       
    95                         if accepted:
       
    96                             for accepted_iface in accepted:
       
    97                                 for found_iface in interfaces:
       
    98                                     if issubclass(found_iface, accepted_iface):
       
    99                                         # consider priority if necessary
       
   100                                         if hasattr(obj.__registerer__, 'remove_all_equivalents'):
       
   101                                             registerer = obj.__registerer__(self, obj)
       
   102                                             registerer.remove_all_equivalents(objects)
       
   103                                         break
       
   104                                 else:
       
   105                                     self.debug('kicking vobject %s (unsupported interface)', obj)
       
   106                                     objects.remove(obj)
       
   107                     # if objects is empty, remove oid from registry
       
   108                     if not objects:
       
   109                         del regcontent[oid]
       
   110 
       
   111     def eid_rset(self, cursor, eid, etype=None):
       
   112         """return a result set for the given eid without doing actual query
       
   113         (we have the eid, we can suppose it exists and user has access to the
       
   114         entity)
       
   115         """
       
   116         msg = '.eid_rset is deprecated, use req.eid_rset'
       
   117         warn(msg, DeprecationWarning, stacklevel=2)
       
   118         try:
       
   119             return cursor.req.eid_rset(eid, etype)
       
   120         except AttributeError:
       
   121             # cursor is a session
       
   122             return cursor.eid_rset(eid, etype)
       
   123     
       
   124     @cached
       
   125     def etype_class(self, etype):
       
   126         """return an entity class for the given entity type.
       
   127         Try to find out a specific class for this kind of entity or
       
   128         default to a dump of the class registered for 'Any'
       
   129         """
       
   130         etype = str(etype)
       
   131         eschema = self.schema.eschema(etype)
       
   132         baseschemas = [eschema] + eschema.ancestors()
       
   133         # browse ancestors from most specific to most generic and
       
   134         # try to find an associated custom entity class
       
   135         for baseschema in baseschemas:
       
   136             btype = str(baseschema)
       
   137             try:
       
   138                 return self.select(self.registry_objects('etypes', btype), etype)
       
   139             except ObjectNotFound:
       
   140                 pass
       
   141         # no entity class for any of the ancestors, fallback to the default one
       
   142         return self.select(self.registry_objects('etypes', 'Any'), etype)
       
   143 
       
   144     def render(self, registry, oid, req, **context):
       
   145         """select an object in a given registry and render it
       
   146 
       
   147         - registry: the registry's name
       
   148         - oid : the view to call
       
   149         - req : the HTTP request         
       
   150         """
       
   151         objclss = self.registry_objects(registry, oid)
       
   152         try:
       
   153             rset = context.pop('rset')
       
   154         except KeyError:
       
   155             rset = None
       
   156         selected = self.select(objclss, req, rset, **context)
       
   157         return selected.dispatch(**context)
       
   158         
       
   159     def main_template(self, req, oid='main', **context):
       
   160         """display query by calling the given template (default to main),
       
   161         and returning the output as a string instead of requiring the [w]rite
       
   162         method as argument
       
   163         """
       
   164         res = self.render('templates', oid, req, **context)
       
   165         if isinstance(res, unicode):
       
   166             return res.encode(req.encoding)
       
   167         assert isinstance(res, str)
       
   168         return res
       
   169 
       
   170     def possible_vobjects(self, registry, *args, **kwargs):
       
   171         """return an ordered list of possible app objects in a given registry,
       
   172         supposing they support the 'visible' and 'order' properties (as most
       
   173         visualizable objects)
       
   174         """
       
   175         return [x for x in sorted(self.possible_objects(registry, *args, **kwargs),
       
   176                                   key=lambda x: x.propval('order'))
       
   177                 if x.propval('visible')]    
       
   178         
       
   179     def possible_actions(self, req, rset, **kwargs):
       
   180         if rset is None:
       
   181             actions = self.possible_vobjects('actions', req, rset)
       
   182         else:
       
   183             actions = rset.possible_actions() # cached implementation
       
   184         result = {}
       
   185         for action in actions:
       
   186             result.setdefault(action.category, []).append(action)
       
   187         return result
       
   188         
       
   189     def possible_views(self, req, rset, **kwargs):
       
   190         """return an iterator on possible views for this result set
       
   191 
       
   192         views returned are classes, not instances
       
   193         """
       
   194         for vid, views in self.registry('views').items():
       
   195             if vid[0] == '_':
       
   196                 continue
       
   197             try:
       
   198                 view = self.select(views, req, rset, **kwargs)
       
   199                 if view.linkable():
       
   200                     yield view
       
   201             except NoSelectableObject:
       
   202                 continue
       
   203             
       
   204     def select_box(self, oid, *args, **kwargs):
       
   205         """return the most specific view according to the result set"""
       
   206         try:
       
   207             return self.select_object('boxes', oid, *args, **kwargs)
       
   208         except NoSelectableObject:
       
   209             return
       
   210 
       
   211     def select_action(self, oid, *args, **kwargs):
       
   212         """return the most specific view according to the result set"""
       
   213         try:
       
   214             return self.select_object('actions', oid, *args, **kwargs)
       
   215         except NoSelectableObject:
       
   216             return
       
   217     
       
   218     def select_component(self, cid, *args, **kwargs):
       
   219         """return the most specific component according to the result set"""
       
   220         try:
       
   221             return self.select_object('components', cid, *args, **kwargs)
       
   222         except (NoSelectableObject, ObjectNotFound):
       
   223             return
       
   224 
       
   225     def select_view(self, __vid, req, rset, **kwargs):
       
   226         """return the most specific view according to the result set"""
       
   227         views = self.registry_objects('views', __vid)
       
   228         return self.select(views, req, rset, **kwargs)
       
   229 
       
   230     
       
   231     # properties handling #####################################################
       
   232 
       
   233     def user_property_keys(self, withsitewide=False):
       
   234         if withsitewide:
       
   235             return sorted(self['propertydefs'])
       
   236         return sorted(k for k, kd in self['propertydefs'].iteritems()
       
   237                       if not kd['sitewide'])
       
   238 
       
   239     def register_property(self, key, type, help, default=None, vocabulary=None,
       
   240                           sitewide=False):
       
   241         """register a given property"""
       
   242         properties = self._registries['propertydefs']
       
   243         assert type in YAMS_TO_PY
       
   244         properties[key] = {'type': type, 'vocabulary': vocabulary, 
       
   245                            'default': default, 'help': help,
       
   246                            'sitewide': sitewide}
       
   247 
       
   248     def property_info(self, key):
       
   249         """return dictionary containing description associated to the given
       
   250         property key (including type, defaut value, help and a site wide
       
   251         boolean)
       
   252         """
       
   253         try:
       
   254             return self._registries['propertydefs'][key]
       
   255         except KeyError:
       
   256             if key.startswith('system.version.'):
       
   257                 soft = key.split('.')[-1]
       
   258                 return {'type': 'String', 'sitewide': True,
       
   259                         'default': None, 'vocabulary': None,
       
   260                         'help': _('%s software version of the database') % soft}
       
   261             raise UnknownProperty('unregistered property %r' % key)
       
   262             
       
   263     def property_value(self, key):
       
   264         try:
       
   265             return self._registries['propertyvalues'][key]
       
   266         except KeyError:
       
   267             return self._registries['propertydefs'][key]['default']
       
   268 
       
   269     def typed_value(self, key, value):
       
   270         """value is an unicode string, return it correctly typed. Let potential
       
   271         type error propagates.
       
   272         """
       
   273         pdef = self.property_info(key)
       
   274         try:
       
   275             value = YAMS_TO_PY[pdef['type']](value)
       
   276         except (TypeError, ValueError):
       
   277             raise ValueError(_('bad value'))
       
   278         vocab = pdef['vocabulary']
       
   279         if vocab is not None:
       
   280             if callable(vocab):
       
   281                 vocab = vocab(key, None) # XXX need a req object
       
   282             if not value in vocab:
       
   283                 raise ValueError(_('unauthorized value'))
       
   284         return value
       
   285     
       
   286     def init_properties(self, propvalues):
       
   287         """init the property values registry using the given set of couple (key, value)
       
   288         """
       
   289         self.initialized = True
       
   290         values = self._registries['propertyvalues']
       
   291         for key, val in propvalues:
       
   292             try:
       
   293                 values[key] = self.typed_value(key, val)
       
   294             except ValueError:
       
   295                 self.warning('%s (you should probably delete that property '
       
   296                              'from the database)', ex)
       
   297             except UnknownProperty, ex:
       
   298                 self.warning('%s (you should probably delete that property '
       
   299                              'from the database)', ex)
       
   300 
       
   301 
       
   302     def property_value_widget(self, propkey, req=None, **attrs):
       
   303         """return widget according to key's type / vocab"""
       
   304         from cubicweb.web.widgets import StaticComboBoxWidget, widget_factory
       
   305         if req is None:
       
   306             tr = unicode
       
   307         else:
       
   308             tr = req._
       
   309         try:
       
   310             pdef = self.property_info(propkey)
       
   311         except UnknownProperty, ex:
       
   312             self.warning('%s (you should probably delete that property '
       
   313                          'from the database)', ex)
       
   314             return widget_factory(self, 'EProperty', self.schema['value'], 'String',
       
   315                                   description=u'', **attrs)
       
   316         req.form['value'] = pdef['default'] # XXX hack to pass the default value
       
   317         vocab = pdef['vocabulary']
       
   318         if vocab is not None:
       
   319             if callable(vocab):
       
   320                 # list() just in case its a generator function
       
   321                 vocabfunc = lambda e: list(vocab(propkey, req))
       
   322             else:
       
   323                 vocabfunc = lambda e: vocab
       
   324             w = StaticComboBoxWidget(self, 'EProperty', self.schema['value'], 'String',
       
   325                                      vocabfunc=vocabfunc, description=tr(pdef['help']),
       
   326                                      **attrs)
       
   327         else:
       
   328             w = widget_factory(self, 'EProperty', self.schema['value'], pdef['type'],
       
   329                                description=tr(pdef['help']), **attrs)
       
   330         return w
       
   331 
       
   332     def parse(self, session, rql, args=None):
       
   333         rqlst = self.rqlhelper.parse(rql)
       
   334         def type_from_eid(eid, session=session):
       
   335             return session.describe(eid)[0]
       
   336         self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
       
   337         return rqlst
       
   338 
       
   339     @property
       
   340     @cached
       
   341     def rqlhelper(self):
       
   342         return RQLHelper(self.schema,
       
   343                          special_relations={'eid': 'uid', 'has_text': 'fti'})
       
   344 
       
   345 class MulCnxCubicWebRegistry(CubicWebRegistry):
       
   346     """special registry to be used when an application has to deal with
       
   347     connections to differents repository. This class add some additional wrapper
       
   348     trying to hide buggy class attributes since classes are not designed to be
       
   349     shared.
       
   350     """
       
   351     def etype_class(self, etype):
       
   352         """return an entity class for the given entity type.
       
   353         Try to find out a specific class for this kind of entity or
       
   354         default to a dump of the class registered for 'Any'
       
   355         """
       
   356         usercls = super(MulCnxCubicWebRegistry, self).etype_class(etype)
       
   357         usercls.e_schema = self.schema.eschema(etype)
       
   358         return usercls
       
   359 
       
   360     def select(self, vobjects, *args, **kwargs):
       
   361         """return an instance of the most specific object according
       
   362         to parameters
       
   363 
       
   364         raise NoSelectableObject if not object apply
       
   365         """
       
   366         for vobject in vobjects:
       
   367             vobject.vreg = self
       
   368             vobject.schema = self.schema
       
   369             vobject.config = self.config
       
   370         return super(MulCnxCubicWebRegistry, self).select(vobjects, *args, **kwargs)
       
   371     
       
   372 from mx.DateTime import DateTime, Time, DateTimeDelta
       
   373 
       
   374 YAMS_TO_PY = {
       
   375     'Boolean':  bool,
       
   376     'String' :  unicode,
       
   377     'Password': str,
       
   378     'Bytes':    Binary,
       
   379     'Int':      int,
       
   380     'Float':    float,
       
   381     'Date':     DateTime,
       
   382     'Datetime': DateTime,
       
   383     'Time':     Time,
       
   384     'Interval': DateTimeDelta,
       
   385     }
       
   386