diff -r 49075f57cf2c -r aa09e20dd8c0 common/appobject.py --- a/common/appobject.py Tue May 05 17:18:49 2009 +0200 +++ b/common/appobject.py Thu May 14 12:48:11 2009 +0200 @@ -1,463 +1,5 @@ -"""Base class for dynamically loaded objects manipulated in the web interface - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" -__docformat__ = "restructuredtext en" - +"""pre 3.2 bw compat""" +# pylint: disable-msg=W0614,W0401 from warnings import warn - -from mx.DateTime import now, oneSecond -from simplejson import dumps - -from logilab.common.deprecation import obsolete - -from rql.nodes import VariableRef, SubQuery -from rql.stmts import Union, Select - -from cubicweb import Unauthorized -from cubicweb.vregistry import VObject -from cubicweb.common.utils import UStringIO -from cubicweb.common.uilib import html_escape, ustrftime -from cubicweb.common.registerers import yes_registerer, priority_registerer -from cubicweb.common.selectors import yes - -_MARKER = object() - - -class Cache(dict): - def __init__(self): - super(Cache, self).__init__() - self.cache_creation_date = None - self.latest_cache_lookup = now() - -CACHE_REGISTRY = {} - -class AppRsetObject(VObject): - """This is the base class for CubicWeb application objects - which are selected according to a request and result set. - - Classes are kept in the vregistry and instantiation is done at selection - time. - - At registration time, the following attributes are set on the class: - :vreg: - the application's registry - :schema: - the application's schema - :config: - the application's configuration - - At instantiation time, the following attributes are set on the instance: - :req: - current request - :rset: - result set on which the object is applied - """ - - @classmethod - def registered(cls, vreg): - cls.vreg = vreg - cls.schema = vreg.schema - cls.config = vreg.config - cls.register_properties() - return cls - - @classmethod - def selected(cls, req, rset, row=None, col=None, **kwargs): - """by default web app objects are usually instantiated on - selection according to a request, a result set, and optional - row and col - """ - instance = cls(req, rset) - instance.row = row - instance.col = col - return instance - - # Eproperties definition: - # key: id of the property (the actual EProperty key is build using - # .. - # value: tuple (property type, vocabfunc, default value, property description) - # possible types are those used by `logilab.common.configuration` - # - # notice that when it exists multiple objects with the same id (adaptation, - # overriding) only the first encountered definition is considered, so those - # objects can't try to have different default values for instance. - - property_defs = {} - - @classmethod - def register_properties(cls): - for propid, pdef in cls.property_defs.items(): - pdef = pdef.copy() # may be shared - pdef['default'] = getattr(cls, propid, pdef['default']) - pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide')) - cls.vreg.register_property(cls.propkey(propid), **pdef) - - @classmethod - def propkey(cls, propid): - return '%s.%s.%s' % (cls.__registry__, cls.id, propid) - - - def __init__(self, req, rset): - super(AppRsetObject, self).__init__() - self.req = req - self.rset = rset - - @property - def cursor(self): # XXX deprecate in favor of req.cursor? - msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)' - warn(msg, DeprecationWarning, stacklevel=2) - return self.req.cursor - - def get_cache(self, cachename): - """ - NOTE: cachename should be dotted names as in : - - cubicweb.mycache - - cubes.blog.mycache - - etc. - """ - if cachename in CACHE_REGISTRY: - cache = CACHE_REGISTRY[cachename] - else: - cache = Cache() - CACHE_REGISTRY[cachename] = cache - _now = now() - if _now > cache.latest_cache_lookup + oneSecond: - ecache = self.req.execute('Any C,T WHERE C is ECache, C name %(name)s, C timestamp T', - {'name':cachename}).get_entity(0,0) - cache.latest_cache_lookup = _now - if not ecache.valid(cache.cache_creation_date): - cache.empty() - cache.cache_creation_date = _now - return cache - - def propval(self, propid): - assert self.req - return self.req.property_value(self.propkey(propid)) - - - def limited_rql(self): - """return a printable rql for the result set associated to the object, - with limit/offset correctly set according to maximum page size and - currently displayed page when necessary - """ - # try to get page boundaries from the navigation component - # XXX we should probably not have a ref to this component here (eg in - # cubicweb.common) - nav = self.vreg.select_component('navigation', self.req, self.rset) - if nav: - start, stop = nav.page_boundaries() - rql = self._limit_offset_rql(stop - start, start) - # result set may have be limited manually in which case navigation won't - # apply - elif self.rset.limited: - rql = self._limit_offset_rql(*self.rset.limited) - # navigation component doesn't apply and rset has not been limited, no - # need to limit query - else: - rql = self.rset.printable_rql() - return rql - - def _limit_offset_rql(self, limit, offset): - rqlst = self.rset.syntax_tree() - if len(rqlst.children) == 1: - select = rqlst.children[0] - olimit, ooffset = select.limit, select.offset - select.limit, select.offset = limit, offset - rql = rqlst.as_string(kwargs=self.rset.args) - # restore original limit/offset - select.limit, select.offset = olimit, ooffset - else: - newselect = Select() - newselect.limit = limit - newselect.offset = offset - aliases = [VariableRef(newselect.get_variable(vref.name, i)) - for i, vref in enumerate(rqlst.selection)] - newselect.set_with([SubQuery(aliases, rqlst)], check=False) - newunion = Union() - newunion.append(newselect) - rql = rqlst.as_string(kwargs=self.rset.args) - rqlst.parent = None - return rql - - # url generation methods ################################################## - - controller = 'view' - - def build_url(self, method=None, **kwargs): - """return an absolute URL using params dictionary key/values as URL - parameters. Values are automatically URL quoted, and the - publishing method to use may be specified or will be guessed. - """ - # XXX I (adim) think that if method is passed explicitly, we should - # not try to process it and directly call req.build_url() - if method is None: - method = self.controller - if method == 'view' and self.req.from_controller() == 'view' and \ - not '_restpath' in kwargs: - method = self.req.relative_path(includeparams=False) or 'view' - return self.req.build_url(method, **kwargs) - - # various resources accessors ############################################# - - def etype_rset(self, etype, size=1): - """return a fake result set for a particular entity type""" - msg = '.etype_rset is deprecated, use req.etype_rset' - warn(msg, DeprecationWarning, stacklevel=2) - return self.req.etype_rset(etype, size=1) - - def eid_rset(self, eid, etype=None): - """return a result set for the given eid""" - msg = '.eid_rset is deprecated, use req.eid_rset' - warn(msg, DeprecationWarning, stacklevel=2) - return self.req.eid_rset(eid, etype) - - def entity(self, row, col=0): - """short cut to get an entity instance for a particular row/column - (col default to 0) - """ - return self.rset.get_entity(row, col) - - def complete_entity(self, row, col=0, skip_bytes=True): - """short cut to get an completed entity instance for a particular - row (all instance's attributes have been fetched) - """ - entity = self.entity(row, col) - entity.complete(skip_bytes=skip_bytes) - return entity - - def user_rql_callback(self, args, msg=None): - """register a user callback to execute some rql query and return an url - to call it ready to be inserted in html - """ - def rqlexec(req, rql, args=None, key=None): - req.execute(rql, args, key) - return self.user_callback(rqlexec, args, msg) - - def user_callback(self, cb, args, msg=None, nonify=False): - """register the given user callback and return an url to call it ready to be - inserted in html - """ - self.req.add_js('cubicweb.ajax.js') - if nonify: - # XXX < 2.48.3 bw compat - warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2) - _cb = cb - def cb(*args): - _cb(*args) - cbname = self.req.register_onetime_callback(cb, *args) - msg = dumps(msg or '') - return "javascript:userCallbackThenReloadPage('%s', %s)" % ( - cbname, msg) - - # formating methods ####################################################### - - def tal_render(self, template, variables): - """render a precompiled page template with variables in the given - dictionary as context - """ - from cubicweb.common.tal import CubicWebContext - context = CubicWebContext() - context.update({'self': self, 'rset': self.rset, '_' : self.req._, - 'req': self.req, 'user': self.req.user}) - context.update(variables) - output = UStringIO() - template.expand(context, output) - return output.getvalue() - - def format_date(self, date, date_format=None, time=False): - """return a string for a mx date time according to application's - configuration - """ - if date: - if date_format is None: - if time: - date_format = self.req.property_value('ui.datetime-format') - else: - date_format = self.req.property_value('ui.date-format') - return ustrftime(date, date_format) - return u'' - - def format_time(self, time): - """return a string for a mx date time according to application's - configuration - """ - if time: - return ustrftime(time, self.req.property_value('ui.time-format')) - return u'' - - def format_float(self, num): - """return a string for floating point number according to application's - configuration - """ - if num: - return self.req.property_value('ui.float-format') % num - return u'' - - # security related methods ################################################ - - def ensure_ro_rql(self, rql): - """raise an exception if the given rql is not a select query""" - first = rql.split(' ', 1)[0].lower() - if first in ('insert', 'set', 'delete'): - raise Unauthorized(self.req._('only select queries are authorized')) - - # .accepts handling utilities ############################################# - - accepts = ('Any',) - - @classmethod - def accept_rset(cls, req, rset, row, col): - """apply the following rules: - * if row is None, return the sum of values returned by the method - for each entity's type in the result set. If any score is 0, - return 0. - * if row is specified, return the value returned by the method with - the entity's type of this row - """ - if row is None: - score = 0 - for etype in rset.column_types(0): - accepted = cls.accept(req.user, etype) - if not accepted: - return 0 - score += accepted - return score - return cls.accept(req.user, rset.description[row][col or 0]) - - @classmethod - def accept(cls, user, etype): - """score etype, returning better score on exact match""" - if 'Any' in cls.accepts: - return 1 - eschema = cls.schema.eschema(etype) - matching_types = [e.type for e in eschema.ancestors()] - matching_types.append(etype) - for index, basetype in enumerate(matching_types): - if basetype in cls.accepts: - return 2 + index - return 0 - - # .rtype handling utilities ############################################## - - @classmethod - def relation_possible(cls, etype): - """tell if a relation with etype entity is possible according to - mixed class'.etype, .rtype and .target attributes - - XXX should probably be moved out to a function - """ - schema = cls.schema - rtype = cls.rtype - eschema = schema.eschema(etype) - if hasattr(cls, 'role'): - role = cls.role - elif cls.target == 'subject': - role = 'object' - else: - role = 'subject' - # check if this relation is possible according to the schema - try: - if role == 'object': - rschema = eschema.object_relation(rtype) - else: - rschema = eschema.subject_relation(rtype) - except KeyError: - return False - if hasattr(cls, 'etype'): - letype = cls.etype - try: - if role == 'object': - return etype in rschema.objects(letype) - else: - return etype in rschema.subjects(letype) - except KeyError, ex: - return False - return True - - - # XXX deprecated (since 2.43) ########################## - - @obsolete('use req.datadir_url') - def datadir_url(self): - """return url of the application's data directory""" - return self.req.datadir_url - - @obsolete('use req.external_resource()') - def external_resource(self, rid, default=_MARKER): - return self.req.external_resource(rid, default) - - -class AppObject(AppRsetObject): - """base class for application objects which are not selected - according to a result set, only by their identifier. - - Those objects may not have req, rset and cursor set. - """ - - @classmethod - def selected(cls, *args, **kwargs): - """by default web app objects are usually instantiated on - selection - """ - return cls(*args, **kwargs) - - def __init__(self, req=None, rset=None, **kwargs): - self.req = req - self.rset = rset - self.__dict__.update(kwargs) - - -class ReloadableMixIn(object): - """simple mixin for reloadable parts of UI""" - - def user_callback(self, cb, args, msg=None, nonify=False): - """register the given user callback and return an url to call it ready to be - inserted in html - """ - self.req.add_js('cubicweb.ajax.js') - if nonify: - _cb = cb - def cb(*args): - _cb(*args) - cbname = self.req.register_onetime_callback(cb, *args) - return self.build_js(cbname, html_escape(msg or '')) - - def build_update_js_call(self, cbname, msg): - rql = html_escape(self.rset.printable_rql()) - return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % ( - cbname, self.id, rql, msg, self.__registry__, self.div_id()) - - def build_reload_js_call(self, cbname, msg): - return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg) - - build_js = build_update_js_call # expect updatable component by default - - def div_id(self): - return '' - - -class ComponentMixIn(ReloadableMixIn): - """simple mixin for component object""" - __registry__ = 'components' - __registerer__ = yes_registerer - __selectors__ = (yes,) - __select__ = classmethod(*__selectors__) - - def div_class(self): - return '%s %s' % (self.propval('htmlclass'), self.id) - - def div_id(self): - return '%sComponent' % self.id - - -class Component(ComponentMixIn, AppObject): - """base class for non displayable components - """ - -class SingletonComponent(Component): - """base class for non displayable unique components - """ - __registerer__ = priority_registerer +warn('moved to cubicweb.appobject', DeprecationWarning, stacklevel=2) +from cubicweb.appobject import *