appobject.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4719 aaed3f813ef8
child 5018 2f2d9bc6dca4
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
     9 """
     9 """
    10 __docformat__ = "restructuredtext en"
    10 __docformat__ = "restructuredtext en"
    11 
    11 
    12 import types
    12 import types
    13 from logging import getLogger
    13 from logging import getLogger
    14 from datetime import datetime, timedelta, time
    14 from warnings import warn
    15 
    15 
    16 from logilab.common.decorators import classproperty
       
    17 from logilab.common.deprecation import deprecated
    16 from logilab.common.deprecation import deprecated
    18 from logilab.common.logging_ext import set_log_methods
    17 from logilab.common.logging_ext import set_log_methods
    19 
       
    20 from rql.nodes import VariableRef, SubQuery
       
    21 from rql.stmts import Union, Select
       
    22 
       
    23 from cubicweb import Unauthorized, NoSelectableObject
       
    24 from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime
       
    25 
       
    26 ONESECOND = timedelta(0, 1, 0)
       
    27 CACHE_REGISTRY = {}
       
    28 
       
    29 
       
    30 class Cache(dict):
       
    31     def __init__(self):
       
    32         super(Cache, self).__init__()
       
    33         _now = datetime.now()
       
    34         self.cache_creation_date = _now
       
    35         self.latest_cache_lookup = _now
       
    36 
    18 
    37 
    19 
    38 # selector base classes and operations ########################################
    20 # selector base classes and operations ########################################
    39 
    21 
    40 def objectify_selector(selector_func):
    22 def objectify_selector(selector_func):
    41     """convenience decorator for simple selectors where a class definition
    23     """convenience decorator for simple selectors where a class definition
    42     would be overkill::
    24     would be overkill::
    43 
    25 
    44         @objectify_selector
    26         @objectify_selector
    45         def yes(cls, *args, **kwargs):
    27         def one(cls, *args, **kwargs):
    46             return 1
    28             return 1
    47 
    29 
    48     """
    30     """
    49     return type(selector_func.__name__, (Selector,),
    31     return type(selector_func.__name__, (Selector,),
    50                 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
    32                 {'__doc__': selector_func.__doc__,
       
    33                  '__call__': lambda self, *a, **kw: selector_func(*a, **kw)})
    51 
    34 
    52 
    35 
    53 def _instantiate_selector(selector):
    36 def _instantiate_selector(selector):
    54     """ensures `selector` is a `Selector` instance
    37     """ensures `selector` is a `Selector` instance
    55 
    38 
    98 
    81 
    99     def __and__(self, other):
    82     def __and__(self, other):
   100         return AndSelector(self, other)
    83         return AndSelector(self, other)
   101     def __rand__(self, other):
    84     def __rand__(self, other):
   102         return AndSelector(other, self)
    85         return AndSelector(other, self)
       
    86     def __iand__(self, other):
       
    87         raise NotImplementedError('cant use inplace & (binary and)')
   103 
    88 
   104     def __or__(self, other):
    89     def __or__(self, other):
   105         return OrSelector(self, other)
    90         return OrSelector(self, other)
   106     def __ror__(self, other):
    91     def __ror__(self, other):
   107         return OrSelector(other, self)
    92         return OrSelector(other, self)
       
    93     def __ior__(self, other):
       
    94         raise NotImplementedError('cant use inplace | (binary or)')
   108 
    95 
   109     def __invert__(self):
    96     def __invert__(self):
   110         return NotSelector(self)
    97         return NotSelector(self)
   111 
    98 
   112     # XXX (function | function) or (function & function) not managed yet
    99     # XXX (function | function) or (function & function) not managed yet
   193     def __str__(self):
   180     def __str__(self):
   194         return 'NOT(%s)' % super(NotSelector, self).__str__()
   181         return 'NOT(%s)' % super(NotSelector, self).__str__()
   195 
   182 
   196 
   183 
   197 class yes(Selector):
   184 class yes(Selector):
   198     """return arbitrary score
   185     """Return the score given as parameter, with a default score of 0.5 so any
   199 
   186     other selector take precedence.
   200     default score of 0.5 so any other selector take precedence
   187 
       
   188     Usually used for appobjects which can be selected whatever the context, or
       
   189     also sometimes to add arbitrary points to a score.
       
   190 
       
   191     Take care, `yes(0)` could be named 'no'...
   201     """
   192     """
   202     def __init__(self, score=0.5):
   193     def __init__(self, score=0.5):
   203         self.score = score
   194         self.score = score
   204 
   195 
   205     def __call__(self, *args, **kwargs):
   196     def __call__(self, *args, **kwargs):
   218 
   209 
   219     The following attributes should be set on concret appobject classes:
   210     The following attributes should be set on concret appobject classes:
   220     :__registry__:
   211     :__registry__:
   221       name of the registry for this object (string like 'views',
   212       name of the registry for this object (string like 'views',
   222       'templates'...)
   213       'templates'...)
   223     :id:
   214     :__regid__:
   224       object's identifier in the registry (string like 'main',
   215       object's identifier in the registry (string like 'main',
   225       'primary', 'folder_box')
   216       'primary', 'folder_box')
   226     :__select__:
   217     :__select__:
   227       class'selector
   218       class'selector
   228 
   219 
   229     Moreover, the `__abstract__` attribute may be set to True to indicate
   220     Moreover, the `__abstract__` attribute may be set to True to indicate
   230     that a appobject is abstract and should not be registered.
   221     that a appobject is abstract and should not be registered.
   231 
   222 
   232     At registration time, the following attributes are set on the class:
       
   233     :vreg:
       
   234       the instance's registry
       
   235     :schema:
       
   236       the instance's schema
       
   237     :config:
       
   238       the instance's configuration
       
   239 
       
   240     At selection time, the following attributes are set on the instance:
   223     At selection time, the following attributes are set on the instance:
   241     :req:
   224 
       
   225     :_cw:
   242       current request
   226       current request
   243     :rset:
   227     :cw_extra_kwargs:
       
   228       other received arguments
       
   229 
       
   230     only if rset is found in arguments (in which case rset/row/col will be
       
   231     removed from cwextra_kwargs):
       
   232 
       
   233     :cw_rset:
   244       context result set or None
   234       context result set or None
   245     :row:
   235     :cw_row:
   246       if a result set is set and the context is about a particular cell in the
   236       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
   237       result set, and not the result set as a whole, specify the row number we
   248       are interested in, else None
   238       are interested in, else None
   249     :col:
   239     :cw_col:
   250       if a result set is set and the context is about a particular cell in the
   240       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
   241       result set, and not the result set as a whole, specify the col number we
   252       are interested in, else None
   242       are interested in, else None
   253     """
   243     """
   254     __registry__ = None
   244     __registry__ = None
   255     id = None
   245     __regid__ = None
   256     __select__ = yes()
   246     __select__ = yes()
   257 
   247 
   258     @classmethod
   248     @classmethod
   259     def classid(cls):
   249     def __registered__(cls, registry):
   260         """returns a unique identifier for the appobject"""
       
   261         return '%s.%s' % (cls.__module__, cls.__name__)
       
   262 
       
   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.
   250         """called by the registry when the appobject has been registered.
   289 
   251 
   290         It must return the object that will be actually registered (this may be
   252         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
   253         the right hook to create an instance for example). By default the
   292         appobject is returned without any transformation.
   254         appobject is returned without any transformation.
   293         """
   255         """
   294         cls.build___select__()
   256         try: # XXX < 3.6 bw compat
   295         cls.vreg = registry.vreg
   257             pdefs = cls.property_defs
   296         cls.schema = registry.schema
   258         except AttributeError:
   297         cls.config = registry.config
   259             pdefs = getattr(cls, 'cw_property_defs', {})
       
   260         else:
       
   261             warn('property_defs is deprecated, use cw_property_defs in %s'
       
   262                  % cls, DeprecationWarning)
       
   263         for propid, pdef in pdefs.items():
       
   264             pdef = pdef.copy() # may be shared
       
   265             pdef['default'] = getattr(cls, propid, pdef['default'])
       
   266             pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
       
   267             registry.vreg.register_property(cls._cwpropkey(propid), **pdef)
       
   268         assert callable(cls.__select__), cls
   298         return cls
   269         return cls
   299 
   270 
   300     @classmethod
   271     def __init__(self, req, **extra):
   301     def vreg_initialization_completed(cls):
   272         super(AppObject, self).__init__()
   302         pass
   273         self._cw = req
   303 
   274         try:
   304     # Eproperties definition:
   275             self.cw_rset = extra.pop('rset')
   305     # key: id of the property (the actual CWProperty key is build using
   276             self.cw_row = extra.pop('row', None)
   306     #      <registry name>.<obj id>.<property id>
   277             self.cw_col = extra.pop('col', None)
   307     # value: tuple (property type, vocabfunc, default value, property description)
   278         except KeyError:
   308     #        possible types are those used by `logilab.common.configuration`
   279             pass
       
   280         self.cw_extra_kwargs = extra
       
   281 
       
   282     # persistent class properties ##############################################
       
   283     #
       
   284     # optional `cw_property_defs` dict on a class defines available persistent
       
   285     # properties for this class:
       
   286     #
       
   287     # * key: id of the property (the actual CWProperty key is build using
       
   288     #        <registry name>.<obj id>.<property id>
       
   289     # * value: tuple (property type, vocabfunc, default value, property description)
       
   290     #         possible types are those used by `logilab.common.configuration`
   309     #
   291     #
   310     # notice that when it exists multiple objects with the same id (adaptation,
   292     # notice that when it exists multiple objects with the same id (adaptation,
   311     # overriding) only the first encountered definition is considered, so those
   293     # overriding) only the first encountered definition is considered, so those
   312     # objects can't try to have different default values for instance.
   294     # objects can't try to have different default values for instance.
   313 
   295     #
   314     property_defs = {}
   296     # you can then access to a property value using self.cw_propval, where self
       
   297     # is an instance of class
   315 
   298 
   316     @classmethod
   299     @classmethod
   317     def register_properties(cls):
   300     def _cwpropkey(cls, propid):
   318         for propid, pdef in cls.property_defs.items():
   301         """return cw property key for the property of the given id for this
   319             pdef = pdef.copy() # may be shared
   302         class
   320             pdef['default'] = getattr(cls, propid, pdef['default'])
   303         """
   321             pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
   304         return '%s.%s.%s' % (cls.__registry__, cls.__regid__, propid)
   322             cls.vreg.register_property(cls.propkey(propid), **pdef)
   305 
   323 
   306     def cw_propval(self, propid):
   324     @classmethod
   307         """return cw property value associated to key
   325     def propkey(cls, propid):
   308 
   326         return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
   309         <cls.__registry__>.<cls.id>.<propid>
   327 
   310         """
   328     @classproperty
   311         return self._cw.property_value(self._cwpropkey(propid))
   329     @deprecated('use __select__ and & or | operators')
   312 
   330     def __selectors__(cls):
   313     # deprecated ###############################################################
   331         selector = cls.__select__
   314 
   332         if isinstance(selector, AndSelector):
   315     @property
   333             return tuple(selector.selectors)
   316     @deprecated('[3.6] use self.__regid__')
   334         if not isinstance(selector, tuple):
   317     def id(self):
   335             selector = (selector,)
   318         return self.__regid__
   336         return selector
   319 
   337 
   320     @property
   338     def __init__(self, req=None, rset=None, row=None, col=None, **extra):
   321     @deprecated('[3.6] use self._cw.vreg')
   339         super(AppObject, self).__init__()
   322     def vreg(self):
   340         self.req = req
   323         return self._cw.vreg
   341         self.rset = rset
   324 
   342         self.row = row
   325     @property
   343         self.col = col
   326     @deprecated('[3.6] use self._cw.vreg.schema')
   344         self.extra_kwargs = extra
   327     def schema(self):
   345 
   328         return self._cw.vreg.schema
       
   329 
       
   330     @property
       
   331     @deprecated('[3.6] use self._cw.vreg.config')
       
   332     def config(self):
       
   333         return self._cw.vreg.config
       
   334 
       
   335     @property
       
   336     @deprecated('[3.6] use self._cw')
       
   337     def req(self):
       
   338         return self._cw
       
   339 
       
   340     @deprecated('[3.6] use self.cw_rset')
       
   341     def get_rset(self):
       
   342         return self.cw_rset
       
   343     @deprecated('[3.6] use self.cw_rset')
       
   344     def set_rset(self, rset):
       
   345         self.cw_rset = rset
       
   346     rset = property(get_rset, set_rset)
       
   347 
       
   348     @property
       
   349     @deprecated('[3.6] use self.cw_row')
       
   350     def row(self):
       
   351         return self.cw_row
       
   352 
       
   353     @property
       
   354     @deprecated('[3.6] use self.cw_col')
       
   355     def col(self):
       
   356         return self.cw_col
       
   357 
       
   358     @property
       
   359     @deprecated('[3.6] use self.cw_extra_kwargs')
       
   360     def extra_kwargs(self):
       
   361         return self.cw_extra_kwargs
       
   362 
       
   363     @deprecated('[3.6] use self._cw.view')
       
   364     def view(self, *args, **kwargs):
       
   365         return self._cw.view(*args, **kwargs)
       
   366 
       
   367     @property
       
   368     @deprecated('[3.6] use self._cw.varmaker')
       
   369     def varmaker(self):
       
   370         return self._cw.varmaker
       
   371 
       
   372     @deprecated('[3.6] use self._cw.get_cache')
   346     def get_cache(self, cachename):
   373     def get_cache(self, cachename):
   347         """
   374         return self._cw.get_cache(cachename)
   348         NOTE: cachename should be dotted names as in :
   375 
   349         - cubicweb.mycache
   376     @deprecated('[3.6] use self._cw.build_url')
   350         - cubes.blog.mycache
   377     def build_url(self, *args, **kwargs):
   351         - etc.
   378         return self._cw.build_url(*args, **kwargs)
   352         """
   379 
   353         if cachename in CACHE_REGISTRY:
   380     @deprecated('[3.6] use self.cw_rset.limited_rql')
   354             cache = CACHE_REGISTRY[cachename]
   381     def limited_rql(self):
   355         else:
   382         return self.cw_rset.limited_rql()
   356             cache = CACHE_REGISTRY[cachename] = Cache()
   383 
   357         _now = datetime.now()
   384     @deprecated('[3.6] use self.cw_rset.complete_entity(row,col) instead')
   358         if _now > cache.latest_cache_lookup + ONESECOND:
   385     def complete_entity(self, row, col=0, skip_bytes=True):
   359             ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
   386         return self.cw_rset.complete_entity(row, col, skip_bytes)
   360                                       {'name':cachename}).get_entity(0,0)
   387 
   361             cache.latest_cache_lookup = _now
   388     @deprecated('[3.6] use self.cw_rset.get_entity(row,col) instead')
   362             if not ecache.valid(cache.cache_creation_date):
   389     def entity(self, row, col=0):
   363                 cache.clear()
   390         return self.cw_rset.get_entity(row, col)
   364                 cache.cache_creation_date = _now
   391 
   365         return cache
   392     @deprecated('[3.6] use self._cw.user_rql_callback')
   366 
   393     def user_rql_callback(self, args, msg=None):
       
   394         return self._cw.user_rql_callback(args, msg)
       
   395 
       
   396     @deprecated('[3.6] use self._cw.user_callback')
       
   397     def user_callback(self, cb, args, msg=None, nonify=False):
       
   398         return self._cw.user_callback(cb, args, msg, nonify)
       
   399 
       
   400     @deprecated('[3.6] use self._cw.format_date')
       
   401     def format_date(self, date, date_format=None, time=False):
       
   402         return self._cw.format_date(date, date_format, time)
       
   403 
       
   404     @deprecated('[3.6] use self._cw.format_time')
       
   405     def format_time(self, time):
       
   406         return self._cw.format_time(time)
       
   407 
       
   408     @deprecated('[3.6] use self._cw.format_float')
       
   409     def format_float(self, num):
       
   410         return self._cw.format_float(num)
       
   411 
       
   412     @deprecated('[3.6] use self._cw.parse_datetime')
       
   413     def parse_datetime(self, value, etype='Datetime'):
       
   414         return self._cw.parse_datetime(value, etype)
       
   415 
       
   416     @deprecated('[3.6] use self.cw_propval')
   367     def propval(self, propid):
   417     def propval(self, propid):
   368         assert self.req
   418         return self._cw.property_value(self._cwpropkey(propid))
   369         return self.req.property_value(self.propkey(propid))
       
   370 
       
   371     def limited_rql(self):
       
   372         """return a printable rql for the result set associated to the object,
       
   373         with limit/offset correctly set according to maximum page size and
       
   374         currently displayed page when necessary
       
   375         """
       
   376         # try to get page boundaries from the navigation component
       
   377         # XXX we should probably not have a ref to this component here (eg in
       
   378         #     cubicweb.common)
       
   379         nav = self.vreg['components'].select_object('navigation', self.req,
       
   380                                                     rset=self.rset)
       
   381         if nav:
       
   382             start, stop = nav.page_boundaries()
       
   383             rql = self._limit_offset_rql(stop - start, start)
       
   384         # result set may have be limited manually in which case navigation won't
       
   385         # apply
       
   386         elif self.rset.limited:
       
   387             rql = self._limit_offset_rql(*self.rset.limited)
       
   388         # navigation component doesn't apply and rset has not been limited, no
       
   389         # need to limit query
       
   390         else:
       
   391             rql = self.rset.printable_rql()
       
   392         return rql
       
   393 
       
   394     def _limit_offset_rql(self, limit, offset):
       
   395         rqlst = self.rset.syntax_tree()
       
   396         if len(rqlst.children) == 1:
       
   397             select = rqlst.children[0]
       
   398             olimit, ooffset = select.limit, select.offset
       
   399             select.limit, select.offset = limit, offset
       
   400             rql = rqlst.as_string(kwargs=self.rset.args)
       
   401             # restore original limit/offset
       
   402             select.limit, select.offset = olimit, ooffset
       
   403         else:
       
   404             newselect = Select()
       
   405             newselect.limit = limit
       
   406             newselect.offset = offset
       
   407             aliases = [VariableRef(newselect.get_variable(vref.name, i))
       
   408                        for i, vref in enumerate(rqlst.selection)]
       
   409             newselect.set_with([SubQuery(aliases, rqlst)], check=False)
       
   410             newunion = Union()
       
   411             newunion.append(newselect)
       
   412             rql = rqlst.as_string(kwargs=self.rset.args)
       
   413             rqlst.parent = None
       
   414         return rql
       
   415 
       
   416     def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
       
   417              **kwargs):
       
   418         """shortcut to self.vreg.view method avoiding to pass self.req"""
       
   419         return self.vreg[__registry].render(__vid, self.req, __fallback_oid,
       
   420                                             rset=rset, **kwargs)
       
   421 
       
   422     def initialize_varmaker(self):
       
   423         varmaker = self.req.get_page_data('rql_varmaker')
       
   424         if varmaker is None:
       
   425             varmaker = self.req.varmaker
       
   426             self.req.set_page_data('rql_varmaker', varmaker)
       
   427         self.varmaker = varmaker
       
   428 
       
   429     # url generation methods ##################################################
       
   430 
       
   431     controller = 'view'
       
   432 
       
   433     def build_url(self, *args, **kwargs):
       
   434         """return an absolute URL using params dictionary key/values as URL
       
   435         parameters. Values are automatically URL quoted, and the
       
   436         publishing method to use may be specified or will be guessed.
       
   437         """
       
   438         # use *args since we don't want first argument to be "anonymous" to
       
   439         # avoid potential clash with kwargs
       
   440         if args:
       
   441             assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
       
   442             method = args[0]
       
   443         else:
       
   444             method = None
       
   445         # XXX I (adim) think that if method is passed explicitly, we should
       
   446         #     not try to process it and directly call req.build_url()
       
   447         if method is None:
       
   448             method = self.controller
       
   449             if method == 'view' and self.req.from_controller() == 'view' and \
       
   450                    not '_restpath' in kwargs:
       
   451                 method = self.req.relative_path(includeparams=False) or 'view'
       
   452         return self.req.build_url(method, **kwargs)
       
   453 
       
   454     # various resources accessors #############################################
       
   455 
       
   456     def entity(self, row, col=0):
       
   457         """short cut to get an entity instance for a particular row/column
       
   458         (col default to 0)
       
   459         """
       
   460         return self.rset.get_entity(row, col)
       
   461 
       
   462     def complete_entity(self, row, col=0, skip_bytes=True):
       
   463         """short cut to get an completed entity instance for a particular
       
   464         row (all instance's attributes have been fetched)
       
   465         """
       
   466         entity = self.entity(row, col)
       
   467         entity.complete(skip_bytes=skip_bytes)
       
   468         return entity
       
   469 
       
   470     def user_rql_callback(self, args, msg=None):
       
   471         """register a user callback to execute some rql query and return an url
       
   472         to call it ready to be inserted in html
       
   473         """
       
   474         def rqlexec(req, rql, args=None, key=None):
       
   475             req.execute(rql, args, key)
       
   476         return self.user_callback(rqlexec, args, msg)
       
   477 
       
   478     def user_callback(self, cb, args, msg=None, nonify=False):
       
   479         """register the given user callback and return an url to call it ready to be
       
   480         inserted in html
       
   481         """
       
   482         from simplejson import dumps
       
   483         self.req.add_js('cubicweb.ajax.js')
       
   484         cbname = self.req.register_onetime_callback(cb, *args)
       
   485         msg = dumps(msg or '')
       
   486         return "javascript:userCallbackThenReloadPage('%s', %s)" % (
       
   487             cbname, msg)
       
   488 
       
   489     # formating methods #######################################################
       
   490 
       
   491     def tal_render(self, template, variables):
       
   492         """render a precompiled page template with variables in the given
       
   493         dictionary as context
       
   494         """
       
   495         from cubicweb.ext.tal import CubicWebContext
       
   496         context = CubicWebContext()
       
   497         context.update({'self': self, 'rset': self.rset, '_' : self.req._,
       
   498                         'req': self.req, 'user': self.req.user})
       
   499         context.update(variables)
       
   500         output = UStringIO()
       
   501         template.expand(context, output)
       
   502         return output.getvalue()
       
   503 
       
   504     def format_date(self, date, date_format=None, time=False):
       
   505         """return a string for a date time according to instance's
       
   506         configuration
       
   507         """
       
   508         if date:
       
   509             if date_format is None:
       
   510                 if time:
       
   511                     date_format = self.req.property_value('ui.datetime-format')
       
   512                 else:
       
   513                     date_format = self.req.property_value('ui.date-format')
       
   514             return ustrftime(date, date_format)
       
   515         return u''
       
   516 
       
   517     def format_time(self, time):
       
   518         """return a string for a time according to instance's
       
   519         configuration
       
   520         """
       
   521         if time:
       
   522             return ustrftime(time, self.req.property_value('ui.time-format'))
       
   523         return u''
       
   524 
       
   525     def format_float(self, num):
       
   526         """return a string for floating point number according to instance's
       
   527         configuration """
       
   528         if num is not None:
       
   529             return self.req.property_value('ui.float-format') % num
       
   530         return u''
       
   531 
       
   532     def parse_datetime(self, value, etype='Datetime'):
       
   533         """get a datetime or time from a string (according to etype)
       
   534         Datetime formatted as Date are accepted
       
   535         """
       
   536         assert etype in ('Datetime', 'Date', 'Time'), etype
       
   537         # XXX raise proper validation error
       
   538         if etype == 'Datetime':
       
   539             format = self.req.property_value('ui.datetime-format')
       
   540             try:
       
   541                 return todatetime(strptime(value, format))
       
   542             except ValueError:
       
   543                 pass
       
   544         elif etype == 'Time':
       
   545             format = self.req.property_value('ui.time-format')
       
   546             try:
       
   547                 # (adim) I can't find a way to parse a Time with a custom format
       
   548                 date = strptime(value, format) # this returns a DateTime
       
   549                 return time(date.hour, date.minute, date.second)
       
   550             except ValueError:
       
   551                 raise ValueError('can\'t parse %r (expected %s)' % (value, format))
       
   552         try:
       
   553             format = self.req.property_value('ui.date-format')
       
   554             dt = strptime(value, format)
       
   555             if etype == 'Datetime':
       
   556                 return todatetime(dt)
       
   557             return todate(dt)
       
   558         except ValueError:
       
   559             raise ValueError('can\'t parse %r (expected %s)' % (value, format))
       
   560 
       
   561     # security related methods ################################################
       
   562 
       
   563     def ensure_ro_rql(self, rql):
       
   564         """raise an exception if the given rql is not a select query"""
       
   565         first = rql.split(' ', 1)[0].lower()
       
   566         if first in ('insert', 'set', 'delete'):
       
   567             raise Unauthorized(self.req._('only select queries are authorized'))
       
   568 
   419 
   569 set_log_methods(AppObject, getLogger('cubicweb.appobject'))
   420 set_log_methods(AppObject, getLogger('cubicweb.appobject'))