appobject.py
branchtls-sprint
changeset 1715 cba9f175da2d
parent 1637 f5f6f25c2602
child 1723 30c3a713ab61
equal deleted inserted replaced
1714:a721966779be 1715:cba9f175da2d
     6 """
     6 """
     7 __docformat__ = "restructuredtext en"
     7 __docformat__ = "restructuredtext en"
     8 
     8 
     9 from datetime import datetime, timedelta
     9 from datetime import datetime, timedelta
    10 
    10 
    11 from simplejson import dumps
       
    12 
       
    13 from logilab.common.decorators import classproperty
    11 from logilab.common.decorators import classproperty
    14 from logilab.common.deprecation import obsolete
    12 from logilab.common.deprecation import obsolete
    15 
    13 
    16 from rql.nodes import VariableRef, SubQuery
    14 from rql.nodes import VariableRef, SubQuery
    17 from rql.stmts import Union, Select
    15 from rql.stmts import Union, Select
    21 from cubicweb.selectors import yes
    19 from cubicweb.selectors import yes
    22 from cubicweb.utils import UStringIO, ustrftime
    20 from cubicweb.utils import UStringIO, ustrftime
    23 
    21 
    24 ONESECOND = timedelta(0, 1, 0)
    22 ONESECOND = timedelta(0, 1, 0)
    25 
    23 
    26 class Cache(dict):    
    24 class Cache(dict):
    27     def __init__(self):
    25     def __init__(self):
    28         super(Cache, self).__init__()
    26         super(Cache, self).__init__()
    29         self.cache_creation_date = None
    27         self.cache_creation_date = None
    30         self.latest_cache_lookup = datetime.now()
    28         self.latest_cache_lookup = datetime.now()
    31     
    29 
    32 CACHE_REGISTRY = {}
    30 CACHE_REGISTRY = {}
    33 
    31 
    34 class AppRsetObject(VObject):
    32 class AppRsetObject(VObject):
    35     """This is the base class for CubicWeb application objects
    33     """This is the base class for CubicWeb application objects
    36     which are selected according to a request and result set.
    34     which are selected according to a request and result set.
    37     
    35 
    38     Classes are kept in the vregistry and instantiation is done at selection
    36     Classes are kept in the vregistry and instantiation is done at selection
    39     time.
    37     time.
    40     
    38 
    41     At registration time, the following attributes are set on the class:
    39     At registration time, the following attributes are set on the class:
    42     :vreg:
    40     :vreg:
    43       the application's registry
    41       the application's registry
    44     :schema:
    42     :schema:
    45       the application's schema
    43       the application's schema
    60         cls.vreg = vreg
    58         cls.vreg = vreg
    61         cls.schema = vreg.schema
    59         cls.schema = vreg.schema
    62         cls.config = vreg.config
    60         cls.config = vreg.config
    63         cls.register_properties()
    61         cls.register_properties()
    64         return cls
    62         return cls
    65     
    63 
    66     @classmethod
    64     @classmethod
    67     def vreg_initialization_completed(cls):
    65     def vreg_initialization_completed(cls):
    68         pass
    66         pass
    69     
    67 
    70     @classmethod
    68     @classmethod
    71     def selected(cls, *args, **kwargs):
    69     def selected(cls, *args, **kwargs):
    72         """by default web app objects are usually instantiated on
    70         """by default web app objects are usually instantiated on
    73         selection according to a request, a result set, and optional
    71         selection according to a request, a result set, and optional
    74         row and col
    72         row and col
    83     #        possible types are those used by `logilab.common.configuration`
    81     #        possible types are those used by `logilab.common.configuration`
    84     #
    82     #
    85     # notice that when it exists multiple objects with the same id (adaptation,
    83     # notice that when it exists multiple objects with the same id (adaptation,
    86     # overriding) only the first encountered definition is considered, so those
    84     # overriding) only the first encountered definition is considered, so those
    87     # objects can't try to have different default values for instance.
    85     # objects can't try to have different default values for instance.
    88     
    86 
    89     property_defs = {}
    87     property_defs = {}
    90     
    88 
    91     @classmethod
    89     @classmethod
    92     def register_properties(cls):
    90     def register_properties(cls):
    93         for propid, pdef in cls.property_defs.items():
    91         for propid, pdef in cls.property_defs.items():
    94             pdef = pdef.copy() # may be shared
    92             pdef = pdef.copy() # may be shared
    95             pdef['default'] = getattr(cls, propid, pdef['default'])
    93             pdef['default'] = getattr(cls, propid, pdef['default'])
    96             pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
    94             pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
    97             cls.vreg.register_property(cls.propkey(propid), **pdef)
    95             cls.vreg.register_property(cls.propkey(propid), **pdef)
    98         
    96 
    99     @classmethod
    97     @classmethod
   100     def propkey(cls, propid):
    98     def propkey(cls, propid):
   101         return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
    99         return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
   102 
   100 
   103     @classproperty
   101     @classproperty
   107         if isinstance(selector, AndSelector):
   105         if isinstance(selector, AndSelector):
   108             return tuple(selector.selectors)
   106             return tuple(selector.selectors)
   109         if not isinstance(selector, tuple):
   107         if not isinstance(selector, tuple):
   110             selector = (selector,)
   108             selector = (selector,)
   111         return selector
   109         return selector
   112     
   110 
   113     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
   111     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
   114         super(AppRsetObject, self).__init__()
   112         super(AppRsetObject, self).__init__()
   115         self.req = req
   113         self.req = req
   116         self.rset = rset
   114         self.rset = rset
   117         self.row = row
   115         self.row = row
   118         self.col = col
   116         self.col = col
   119         self.extra_kwargs = extra
   117         self.extra_kwargs = extra
   120         
   118 
   121     def get_cache(self, cachename):
   119     def get_cache(self, cachename):
   122         """
   120         """
   123         NOTE: cachename should be dotted names as in :
   121         NOTE: cachename should be dotted names as in :
   124         - cubicweb.mycache
   122         - cubicweb.mycache
   125         - cubes.blog.mycache 
   123         - cubes.blog.mycache
   126         - etc.
   124         - etc.
   127         """
   125         """
   128         if cachename in CACHE_REGISTRY:
   126         if cachename in CACHE_REGISTRY:
   129             cache = CACHE_REGISTRY[cachename]
   127             cache = CACHE_REGISTRY[cachename]
   130         else:
   128         else:
   131             cache = Cache()
   129             cache = Cache()
   132             CACHE_REGISTRY[cachename] = cache
   130             CACHE_REGISTRY[cachename] = cache
   133         _now = datetime.now()
   131         _now = datetime.now()
   134         if _now > cache.latest_cache_lookup + ONESECOND:
   132         if _now > cache.latest_cache_lookup + ONESECOND:
   135             ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', 
   133             ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
   136                                       {'name':cachename}).get_entity(0,0)
   134                                       {'name':cachename}).get_entity(0,0)
   137             cache.latest_cache_lookup = _now
   135             cache.latest_cache_lookup = _now
   138             if not ecache.valid(cache.cache_creation_date):
   136             if not ecache.valid(cache.cache_creation_date):
   139                 cache.clear()
   137                 cache.clear()
   140                 cache.cache_creation_date = _now
   138                 cache.cache_creation_date = _now
   141         return cache
   139         return cache
   142 
   140 
   143     def propval(self, propid):
   141     def propval(self, propid):
   144         assert self.req
   142         assert self.req
   145         return self.req.property_value(self.propkey(propid))
   143         return self.req.property_value(self.propkey(propid))
   146     
   144 
   147     def limited_rql(self):
   145     def limited_rql(self):
   148         """return a printable rql for the result set associated to the object,
   146         """return a printable rql for the result set associated to the object,
   149         with limit/offset correctly set according to maximum page size and
   147         with limit/offset correctly set according to maximum page size and
   150         currently displayed page when necessary
   148         currently displayed page when necessary
   151         """
   149         """
   163         # navigation component doesn't apply and rset has not been limited, no
   161         # navigation component doesn't apply and rset has not been limited, no
   164         # need to limit query
   162         # need to limit query
   165         else:
   163         else:
   166             rql = self.rset.printable_rql()
   164             rql = self.rset.printable_rql()
   167         return rql
   165         return rql
   168     
   166 
   169     def _limit_offset_rql(self, limit, offset):
   167     def _limit_offset_rql(self, limit, offset):
   170         rqlst = self.rset.syntax_tree()
   168         rqlst = self.rset.syntax_tree()
   171         if len(rqlst.children) == 1:
   169         if len(rqlst.children) == 1:
   172             select = rqlst.children[0]
   170             select = rqlst.children[0]
   173             olimit, ooffset = select.limit, select.offset
   171             olimit, ooffset = select.limit, select.offset
   185             newunion = Union()
   183             newunion = Union()
   186             newunion.append(newselect)
   184             newunion.append(newselect)
   187             rql = rqlst.as_string(kwargs=self.rset.args)
   185             rql = rqlst.as_string(kwargs=self.rset.args)
   188             rqlst.parent = None
   186             rqlst.parent = None
   189         return rql
   187         return rql
   190         
   188 
   191     def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
   189     def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
   192         """shortcut to self.vreg.render method avoiding to pass self.req"""
   190         """shortcut to self.vreg.render method avoiding to pass self.req"""
   193         try:
   191         try:
   194             view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
   192             view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
   195         except NoSelectableObject:
   193         except NoSelectableObject:
   196             if __fallback_vid is None:
   194             if __fallback_vid is None:
   197                 raise
   195                 raise
   198             view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
   196             view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
   199         return view.dispatch(**kwargs)
   197         return view.dispatch(**kwargs)
   200     
   198 
   201     # url generation methods ##################################################
   199     # url generation methods ##################################################
   202     
   200 
   203     controller = 'view'
   201     controller = 'view'
   204     
   202 
   205     def build_url(self, method=None, **kwargs):
   203     def build_url(self, method=None, **kwargs):
   206         """return an absolute URL using params dictionary key/values as URL
   204         """return an absolute URL using params dictionary key/values as URL
   207         parameters. Values are automatically URL quoted, and the
   205         parameters. Values are automatically URL quoted, and the
   208         publishing method to use may be specified or will be guessed.
   206         publishing method to use may be specified or will be guessed.
   209         """
   207         """
   215                    not '_restpath' in kwargs:
   213                    not '_restpath' in kwargs:
   216                 method = self.req.relative_path(includeparams=False) or 'view'
   214                 method = self.req.relative_path(includeparams=False) or 'view'
   217         return self.req.build_url(method, **kwargs)
   215         return self.req.build_url(method, **kwargs)
   218 
   216 
   219     # various resources accessors #############################################
   217     # various resources accessors #############################################
   220     
   218 
   221     def entity(self, row, col=0):
   219     def entity(self, row, col=0):
   222         """short cut to get an entity instance for a particular row/column
   220         """short cut to get an entity instance for a particular row/column
   223         (col default to 0)
   221         (col default to 0)
   224         """
   222         """
   225         return self.rset.get_entity(row, col)
   223         return self.rset.get_entity(row, col)
   226     
   224 
   227     def complete_entity(self, row, col=0, skip_bytes=True):
   225     def complete_entity(self, row, col=0, skip_bytes=True):
   228         """short cut to get an completed entity instance for a particular
   226         """short cut to get an completed entity instance for a particular
   229         row (all instance's attributes have been fetched)
   227         row (all instance's attributes have been fetched)
   230         """
   228         """
   231         entity = self.entity(row, col)
   229         entity = self.entity(row, col)
   237         to call it ready to be inserted in html
   235         to call it ready to be inserted in html
   238         """
   236         """
   239         def rqlexec(req, rql, args=None, key=None):
   237         def rqlexec(req, rql, args=None, key=None):
   240             req.execute(rql, args, key)
   238             req.execute(rql, args, key)
   241         return self.user_callback(rqlexec, args, msg)
   239         return self.user_callback(rqlexec, args, msg)
   242         
   240 
   243     def user_callback(self, cb, args, msg=None, nonify=False):
   241     def user_callback(self, cb, args, msg=None, nonify=False):
   244         """register the given user callback and return an url to call it ready to be
   242         """register the given user callback and return an url to call it ready to be
   245         inserted in html
   243         inserted in html
   246         """
   244         """
       
   245         from simplejson import dumps
   247         self.req.add_js('cubicweb.ajax.js')
   246         self.req.add_js('cubicweb.ajax.js')
   248         cbname = self.req.register_onetime_callback(cb, *args)
   247         cbname = self.req.register_onetime_callback(cb, *args)
   249         msg = dumps(msg or '') 
   248         msg = dumps(msg or '')
   250         return "javascript:userCallbackThenReloadPage('%s', %s)" % (
   249         return "javascript:userCallbackThenReloadPage('%s', %s)" % (
   251             cbname, msg)
   250             cbname, msg)
   252 
   251 
   253     # formating methods #######################################################
   252     # formating methods #######################################################
   254 
   253 
   255     def tal_render(self, template, variables):
   254     def tal_render(self, template, variables):
   256         """render a precompiled page template with variables in the given
   255         """render a precompiled page template with variables in the given
   257         dictionary as context
   256         dictionary as context
   258         """
   257         """
   259         from cubicweb.common.tal import CubicWebContext
   258         from cubicweb.ext.tal import CubicWebContext
   260         context = CubicWebContext()
   259         context = CubicWebContext()
   261         context.update({'self': self, 'rset': self.rset, '_' : self.req._,
   260         context.update({'self': self, 'rset': self.rset, '_' : self.req._,
   262                         'req': self.req, 'user': self.req.user})
   261                         'req': self.req, 'user': self.req.user})
   263         context.update(variables)
   262         context.update(variables)
   264         output = UStringIO()
   263         output = UStringIO()
   291         configuration
   290         configuration
   292         """
   291         """
   293         if num:
   292         if num:
   294             return self.req.property_value('ui.float-format') % num
   293             return self.req.property_value('ui.float-format') % num
   295         return u''
   294         return u''
   296     
   295 
   297     # security related methods ################################################
   296     # security related methods ################################################
   298     
   297 
   299     def ensure_ro_rql(self, rql):
   298     def ensure_ro_rql(self, rql):
   300         """raise an exception if the given rql is not a select query"""
   299         """raise an exception if the given rql is not a select query"""
   301         first = rql.split(' ', 1)[0].lower()
   300         first = rql.split(' ', 1)[0].lower()
   302         if first in ('insert', 'set', 'delete'):
   301         if first in ('insert', 'set', 'delete'):
   303             raise Unauthorized(self.req._('only select queries are authorized'))
   302             raise Unauthorized(self.req._('only select queries are authorized'))
   304 
   303 
   305         
   304 
   306 class AppObject(AppRsetObject):
   305 class AppObject(AppRsetObject):
   307     """base class for application objects which are not selected
   306     """base class for application objects which are not selected
   308     according to a result set, only by their identifier.
   307     according to a result set, only by their identifier.
   309     
   308 
   310     Those objects may not have req, rset and cursor set.
   309     Those objects may not have req, rset and cursor set.
   311     """
   310     """
   312     
   311 
   313     @classmethod
   312     @classmethod
   314     def selected(cls, *args, **kwargs):
   313     def selected(cls, *args, **kwargs):
   315         """by default web app objects are usually instantiated on
   314         """by default web app objects are usually instantiated on
   316         selection
   315         selection
   317         """
   316         """