appobject.py
changeset 2675 f84ba1a66abb
parent 2658 5535857eeaa5
child 2770 356e9d7c356d
child 4212 ab6573088b4a
equal deleted inserted replaced
2674:ff6114c2c416 2675:f84ba1a66abb
     1 """Base class for dynamically loaded objects manipulated in the web interface
     1 """Base class for dynamically loaded objects accessible through the vregistry.
       
     2 
       
     3 You'll also find some convenience classes to build selectors.
     2 
     4 
     3 :organization: Logilab
     5 :organization: Logilab
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
     6 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     8 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     7 """
     9 """
     8 __docformat__ = "restructuredtext en"
    10 __docformat__ = "restructuredtext en"
     9 
    11 
       
    12 import types
       
    13 from logging import getLogger
    10 from datetime import datetime, timedelta, time
    14 from datetime import datetime, timedelta, time
    11 
    15 
    12 from logilab.common.decorators import classproperty
    16 from logilab.common.decorators import classproperty
    13 from logilab.common.deprecation import deprecated
    17 from logilab.common.deprecation import deprecated
       
    18 from logilab.common.logging_ext import set_log_methods
    14 
    19 
    15 from rql.nodes import VariableRef, SubQuery
    20 from rql.nodes import VariableRef, SubQuery
    16 from rql.stmts import Union, Select
    21 from rql.stmts import Union, Select
    17 
    22 
    18 from cubicweb import Unauthorized, NoSelectableObject
    23 from cubicweb import Unauthorized, NoSelectableObject
    19 from cubicweb.vregistry import VObject, AndSelector
       
    20 from cubicweb.selectors import yes
       
    21 from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime
    24 from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime
    22 
    25 
    23 ONESECOND = timedelta(0, 1, 0)
    26 ONESECOND = timedelta(0, 1, 0)
       
    27 CACHE_REGISTRY = {}
       
    28 
    24 
    29 
    25 class Cache(dict):
    30 class Cache(dict):
    26     def __init__(self):
    31     def __init__(self):
    27         super(Cache, self).__init__()
    32         super(Cache, self).__init__()
    28         _now = datetime.now()
    33         _now = datetime.now()
    29         self.cache_creation_date = _now
    34         self.cache_creation_date = _now
    30         self.latest_cache_lookup = _now
    35         self.latest_cache_lookup = _now
    31 
    36 
    32 CACHE_REGISTRY = {}
    37 
    33 
    38 # selector base classes and operations ########################################
    34 class AppRsetObject(VObject):
    39 
    35     """This is the base class for CubicWeb application objects
    40 def objectify_selector(selector_func):
    36     which are selected according to a request and result set.
    41     """convenience decorator for simple selectors where a class definition
    37 
    42     would be overkill::
    38     Classes are kept in the vregistry and instantiation is done at selection
    43 
    39     time.
    44         @objectify_selector
       
    45         def yes(cls, *args, **kwargs):
       
    46             return 1
       
    47 
       
    48     """
       
    49     return type(selector_func.__name__, (Selector,),
       
    50                 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
       
    51 
       
    52 
       
    53 def _instantiate_selector(selector):
       
    54     """ensures `selector` is a `Selector` instance
       
    55 
       
    56     NOTE: This should only be used locally in build___select__()
       
    57     XXX: then, why not do it ??
       
    58     """
       
    59     if isinstance(selector, types.FunctionType):
       
    60         return objectify_selector(selector)()
       
    61     if isinstance(selector, type) and issubclass(selector, Selector):
       
    62         return selector()
       
    63     return selector
       
    64 
       
    65 
       
    66 class Selector(object):
       
    67     """base class for selector classes providing implementation
       
    68     for operators ``&`` and ``|``
       
    69 
       
    70     This class is only here to give access to binary operators, the
       
    71     selector logic itself should be implemented in the __call__ method
       
    72 
       
    73 
       
    74     a selector is called to help choosing the correct object for a
       
    75     particular context by returning a score (`int`) telling how well
       
    76     the class given as first argument apply to the given context.
       
    77 
       
    78     0 score means that the class doesn't apply.
       
    79     """
       
    80 
       
    81     @property
       
    82     def func_name(self):
       
    83         # backward compatibility
       
    84         return self.__class__.__name__
       
    85 
       
    86     def search_selector(self, selector):
       
    87         """search for the given selector or selector instance in the selectors
       
    88         tree. Return it of None if not found
       
    89         """
       
    90         if self is selector:
       
    91             return self
       
    92         if isinstance(selector, type) and isinstance(self, selector):
       
    93             return self
       
    94         return None
       
    95 
       
    96     def __str__(self):
       
    97         return self.__class__.__name__
       
    98 
       
    99     def __and__(self, other):
       
   100         return AndSelector(self, other)
       
   101     def __rand__(self, other):
       
   102         return AndSelector(other, self)
       
   103 
       
   104     def __or__(self, other):
       
   105         return OrSelector(self, other)
       
   106     def __ror__(self, other):
       
   107         return OrSelector(other, self)
       
   108 
       
   109     def __invert__(self):
       
   110         return NotSelector(self)
       
   111 
       
   112     # XXX (function | function) or (function & function) not managed yet
       
   113 
       
   114     def __call__(self, cls, *args, **kwargs):
       
   115         return NotImplementedError("selector %s must implement its logic "
       
   116                                    "in its __call__ method" % self.__class__)
       
   117 
       
   118 
       
   119 class MultiSelector(Selector):
       
   120     """base class for compound selector classes"""
       
   121 
       
   122     def __init__(self, *selectors):
       
   123         self.selectors = self.merge_selectors(selectors)
       
   124 
       
   125     def __str__(self):
       
   126         return '%s(%s)' % (self.__class__.__name__,
       
   127                            ','.join(str(s) for s in self.selectors))
       
   128 
       
   129     @classmethod
       
   130     def merge_selectors(cls, selectors):
       
   131         """deal with selector instanciation when necessary and merge
       
   132         multi-selectors if possible:
       
   133 
       
   134         AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4))
       
   135         ==> AndSelector(sel1, sel2, sel3, sel4)
       
   136         """
       
   137         merged_selectors = []
       
   138         for selector in selectors:
       
   139             try:
       
   140                 selector = _instantiate_selector(selector)
       
   141             except:
       
   142                 pass
       
   143             #assert isinstance(selector, Selector), selector
       
   144             if isinstance(selector, cls):
       
   145                 merged_selectors += selector.selectors
       
   146             else:
       
   147                 merged_selectors.append(selector)
       
   148         return merged_selectors
       
   149 
       
   150     def search_selector(self, selector):
       
   151         """search for the given selector or selector instance in the selectors
       
   152         tree. Return it of None if not found
       
   153         """
       
   154         for childselector in self.selectors:
       
   155             if childselector is selector:
       
   156                 return childselector
       
   157             found = childselector.search_selector(selector)
       
   158             if found is not None:
       
   159                 return found
       
   160         return None
       
   161 
       
   162 
       
   163 class AndSelector(MultiSelector):
       
   164     """and-chained selectors (formerly known as chainall)"""
       
   165     def __call__(self, cls, *args, **kwargs):
       
   166         score = 0
       
   167         for selector in self.selectors:
       
   168             partscore = selector(cls, *args, **kwargs)
       
   169             if not partscore:
       
   170                 return 0
       
   171             score += partscore
       
   172         return score
       
   173 
       
   174 
       
   175 class OrSelector(MultiSelector):
       
   176     """or-chained selectors (formerly known as chainfirst)"""
       
   177     def __call__(self, cls, *args, **kwargs):
       
   178         for selector in self.selectors:
       
   179             partscore = selector(cls, *args, **kwargs)
       
   180             if partscore:
       
   181                 return partscore
       
   182         return 0
       
   183 
       
   184 class NotSelector(Selector):
       
   185     """negation selector"""
       
   186     def __init__(self, selector):
       
   187         self.selector = selector
       
   188 
       
   189     def __call__(self, cls, *args, **kwargs):
       
   190         score = self.selector(cls, *args, **kwargs)
       
   191         return int(not score)
       
   192 
       
   193     def __str__(self):
       
   194         return 'NOT(%s)' % super(NotSelector, self).__str__()
       
   195 
       
   196 
       
   197 class yes(Selector):
       
   198     """return arbitrary score
       
   199 
       
   200     default score of 0.5 so any other selector take precedence
       
   201     """
       
   202     def __init__(self, score=0.5):
       
   203         self.score = score
       
   204 
       
   205     def __call__(self, *args, **kwargs):
       
   206         return self.score
       
   207 
       
   208 
       
   209 # the base class for all appobjects ############################################
       
   210 
       
   211 class AppObject(object):
       
   212     """This is the base class for CubicWeb application objects which are
       
   213     selected according to a context (usually at least a request and a result
       
   214     set).
       
   215 
       
   216     Concrete application objects classes are designed to be loaded by the
       
   217     vregistry and should be accessed through it, not by direct instantiation.
       
   218 
       
   219     The following attributes should be set on concret appobject classes:
       
   220     :__registry__:
       
   221       name of the registry for this object (string like 'views',
       
   222       'templates'...)
       
   223     :id:
       
   224       object's identifier in the registry (string like 'main',
       
   225       'primary', 'folder_box')
       
   226     :__select__:
       
   227       class'selector
       
   228 
       
   229     Moreover, the `__abstract__` attribute may be set to True to indicate
       
   230     that a appobject is abstract and should not be registered.
    40 
   231 
    41     At registration time, the following attributes are set on the class:
   232     At registration time, the following attributes are set on the class:
    42     :vreg:
   233     :vreg:
    43       the instance's registry
   234       the instance's registry
    44     :schema:
   235     :schema:
    45       the instance's schema
   236       the instance's schema
    46     :config:
   237     :config:
    47       the instance's configuration
   238       the instance's configuration
    48 
   239 
    49     At instantiation time, the following attributes are set on the instance:
   240     At selection time, the following attributes are set on the instance:
    50     :req:
   241     :req:
    51       current request
   242       current request
    52     :rset:
   243     :rset:
    53       result set on which the object is applied
   244       context result set or None
       
   245     :row:
       
   246       if a result set is set and the context is about a particular cell in the
       
   247       result set, and not the result set as a whole, specify the row number we
       
   248       are interested in, else None
       
   249     :col:
       
   250       if a result set is set and the context is about a particular cell in the
       
   251       result set, and not the result set as a whole, specify the col number we
       
   252       are interested in, else None
    54     """
   253     """
       
   254     __registry__ = None
       
   255     id = None
    55     __select__ = yes()
   256     __select__ = yes()
    56 
   257 
    57     @classmethod
   258     @classmethod
    58     def registered(cls, vreg):
   259     def classid(cls):
    59         super(AppRsetObject, cls).registered(vreg)
   260         """returns a unique identifier for the appobject"""
    60         cls.vreg = vreg
   261         return '%s.%s' % (cls.__module__, cls.__name__)
    61         cls.schema = vreg.schema
   262 
    62         cls.config = vreg.config
   263     # XXX bw compat code
       
   264     @classmethod
       
   265     def build___select__(cls):
       
   266         for klass in cls.mro():
       
   267             if klass.__name__ == 'AppObject':
       
   268                 continue # the bw compat __selector__ is there
       
   269             klassdict = klass.__dict__
       
   270             if ('__select__' in klassdict and '__selectors__' in klassdict
       
   271                 and '__selgenerated__' not in klassdict):
       
   272                 raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls)
       
   273             if '__selectors__' in klassdict and '__selgenerated__' not in klassdict:
       
   274                 cls.__selgenerated__ = True
       
   275                 # case where __selectors__ is defined locally (but __select__
       
   276                 # is in a parent class)
       
   277                 selectors = klassdict['__selectors__']
       
   278                 if len(selectors) == 1:
       
   279                     # micro optimization: don't bother with AndSelector if there's
       
   280                     # only one selector
       
   281                     select = _instantiate_selector(selectors[0])
       
   282                 else:
       
   283                     select = AndSelector(*selectors)
       
   284                 cls.__select__ = select
       
   285 
       
   286     @classmethod
       
   287     def registered(cls, registry):
       
   288         """called by the registry when the appobject has been registered.
       
   289 
       
   290         It must return the object that will be actually registered (this may be
       
   291         the right hook to create an instance for example). By default the
       
   292         appobject is returned without any transformation.
       
   293         """
       
   294         cls.build___select__()
       
   295         cls.vreg = registry.vreg
       
   296         cls.schema = registry.schema
       
   297         cls.config = registry.config
    63         cls.register_properties()
   298         cls.register_properties()
    64         return cls
   299         return cls
    65 
   300 
    66     @classmethod
   301     @classmethod
    67     def vreg_initialization_completed(cls):
   302     def vreg_initialization_completed(cls):
    68         pass
   303         pass
    69 
       
    70     @classmethod
       
    71     def selected(cls, *args, **kwargs):
       
    72         """by default web app objects are usually instantiated on
       
    73         selection according to a request, a result set, and optional
       
    74         row and col
       
    75         """
       
    76         assert len(args) <= 2
       
    77         return cls(*args, **kwargs)
       
    78 
   304 
    79     # Eproperties definition:
   305     # Eproperties definition:
    80     # key: id of the property (the actual CWProperty key is build using
   306     # key: id of the property (the actual CWProperty key is build using
    81     #      <registry name>.<obj id>.<property id>
   307     #      <registry name>.<obj id>.<property id>
    82     # value: tuple (property type, vocabfunc, default value, property description)
   308     # value: tuple (property type, vocabfunc, default value, property description)
   109         if not isinstance(selector, tuple):
   335         if not isinstance(selector, tuple):
   110             selector = (selector,)
   336             selector = (selector,)
   111         return selector
   337         return selector
   112 
   338 
   113     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
   339     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
   114         super(AppRsetObject, self).__init__()
   340         super(AppObject, self).__init__()
   115         self.req = req
   341         self.req = req
   116         self.rset = rset
   342         self.rset = rset
   117         self.row = row
   343         self.row = row
   118         self.col = col
   344         self.col = col
   119         self.extra_kwargs = extra
   345         self.extra_kwargs = extra
   149         currently displayed page when necessary
   375         currently displayed page when necessary
   150         """
   376         """
   151         # try to get page boundaries from the navigation component
   377         # try to get page boundaries from the navigation component
   152         # XXX we should probably not have a ref to this component here (eg in
   378         # XXX we should probably not have a ref to this component here (eg in
   153         #     cubicweb.common)
   379         #     cubicweb.common)
   154         nav = self.vreg.select_object('components', 'navigation', self.req,
   380         nav = self.vreg['components'].select_object('navigation', self.req,
   155                                       rset=self.rset)
   381                                                     rset=self.rset)
   156         if nav:
   382         if nav:
   157             start, stop = nav.page_boundaries()
   383             start, stop = nav.page_boundaries()
   158             rql = self._limit_offset_rql(stop - start, start)
   384             rql = self._limit_offset_rql(stop - start, start)
   159         # result set may have be limited manually in which case navigation won't
   385         # result set may have be limited manually in which case navigation won't
   160         # apply
   386         # apply
   186             newunion.append(newselect)
   412             newunion.append(newselect)
   187             rql = rqlst.as_string(kwargs=self.rset.args)
   413             rql = rqlst.as_string(kwargs=self.rset.args)
   188             rqlst.parent = None
   414             rqlst.parent = None
   189         return rql
   415         return rql
   190 
   416 
   191     def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
   417     def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
       
   418              **kwargs):
   192         """shortcut to self.vreg.view method avoiding to pass self.req"""
   419         """shortcut to self.vreg.view method avoiding to pass self.req"""
   193         return self.vreg.render(__vid, self.req, __fallback_vid, rset=rset,
   420         return self.vreg[__registry].render(__vid, self.req, __fallback_oid,
   194                                 **kwargs)
   421                                             rset=rset, **kwargs)
   195 
   422 
   196     def initialize_varmaker(self):
   423     def initialize_varmaker(self):
   197         varmaker = self.req.get_page_data('rql_varmaker')
   424         varmaker = self.req.get_page_data('rql_varmaker')
   198         if varmaker is None:
   425         if varmaker is None:
   199             varmaker = self.req.varmaker
   426             varmaker = self.req.varmaker
   339         """raise an exception if the given rql is not a select query"""
   566         """raise an exception if the given rql is not a select query"""
   340         first = rql.split(' ', 1)[0].lower()
   567         first = rql.split(' ', 1)[0].lower()
   341         if first in ('insert', 'set', 'delete'):
   568         if first in ('insert', 'set', 'delete'):
   342             raise Unauthorized(self.req._('only select queries are authorized'))
   569             raise Unauthorized(self.req._('only select queries are authorized'))
   343 
   570 
   344 
   571 set_log_methods(AppObject, getLogger('cubicweb.appobject'))
   345 class AppObject(AppRsetObject):
       
   346     """base class for application objects which are not selected
       
   347     according to a result set, only by their identifier.
       
   348 
       
   349     Those objects may not have req, rset and cursor set.
       
   350     """
       
   351 
       
   352     @classmethod
       
   353     def selected(cls, *args, **kwargs):
       
   354         """by default web app objects are usually instantiated on
       
   355         selection
       
   356         """
       
   357         return cls(*args, **kwargs)
       
   358 
       
   359     def __init__(self, req=None, rset=None, **kwargs):
       
   360         self.req = req
       
   361         self.rset = rset
       
   362         self.__dict__.update(kwargs)