# HG changeset patch # User Sylvain Thénault # Date 1250148603 -7200 # Node ID 135580d15d4298a18c6c2578f7d8fdb3dd007635 # Parent 7ef2b08fbe2888c665c07393a84d16cc9f297aa9 rename and move cw.RequestSessionMixIn to cw.req.RequestSessionBase; move some appobjects methods where they actually belong to diff -r 7ef2b08fbe28 -r 135580d15d42 __init__.py --- a/__init__.py Thu Aug 13 09:23:22 2009 +0200 +++ b/__init__.py Thu Aug 13 09:30:03 2009 +0200 @@ -7,7 +7,6 @@ :license: Library General Public License version 2 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" -from cubicweb.__pkginfo__ import version as __version__ import __builtin__ # '_' is available in builtins to mark internationalized string but should @@ -21,7 +20,6 @@ from StringIO import StringIO from urllib import quote as urlquote, unquote as urlunquote -from logilab.common.decorators import cached from logilab.common.logging_ext import set_log_methods if os.environ.get('APYCOT_ROOT'): @@ -29,6 +27,8 @@ else: logging.basicConfig() +from cubicweb.__pkginfo__ import version as __version__ + set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb')) @@ -57,173 +57,6 @@ StringIO.write(self, data) -class RequestSessionMixIn(object): - """mixin class containing stuff shared by server session and web request - """ - def __init__(self, vreg): - self.vreg = vreg - try: - encoding = vreg.property_value('ui.encoding') - except: # no vreg or property not registered - encoding = 'utf-8' - self.encoding = encoding - # cache result of execution for (rql expr / eids), - # should be emptied on commit/rollback of the server session / web - # connection - self.local_perm_cache = {} - - def property_value(self, key): - if self.user: - return self.user.property_value(key) - return self.vreg.property_value(key) - - def etype_rset(self, etype, size=1): - """return a fake result set for a particular entity type""" - from cubicweb.rset import ResultSet - rset = ResultSet([('A',)]*size, '%s X' % etype, - description=[(etype,)]*size) - def get_entity(row, col=0, etype=etype, req=self, rset=rset): - return req.vreg.etype_class(etype)(req, rset, row, col) - rset.get_entity = get_entity - return self.decorate_rset(rset) - - def eid_rset(self, eid, etype=None): - """return a result set for the given eid without doing actual query - (we have the eid, we can suppose it exists and user has access to the - entity) - """ - from cubicweb.rset import ResultSet - eid = typed_eid(eid) - if etype is None: - etype = self.describe(eid)[0] - rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid}, - [(etype,)]) - return self.decorate_rset(rset) - - def empty_rset(self): - """return a result set for the given eid without doing actual query - (we have the eid, we can suppose it exists and user has access to the - entity) - """ - from cubicweb.rset import ResultSet - return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1')) - - def entity_from_eid(self, eid, etype=None): - try: - return self.entity_cache(eid) - except KeyError: - rset = self.eid_rset(eid, etype) - entity = rset.get_entity(0, 0) - self.set_entity_cache(entity) - return entity - - def entity_cache(self, eid): - raise KeyError - def set_entity_cache(self, entity): - pass - # url generation methods ################################################## - - def build_url(self, *args, **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. - """ - # use *args since we don't want first argument to be "anonymous" to - # avoid potential clash with kwargs - assert len(args) == 1, 'only 0 or 1 non-named-argument expected' - method = args[0] - base_url = kwargs.pop('base_url', None) - if base_url is None: - base_url = self.base_url() - if '_restpath' in kwargs: - assert method == 'view', method - path = kwargs.pop('_restpath') - else: - path = method - if not kwargs: - return u'%s%s' % (base_url, path) - return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs)) - - - def build_url_params(self, **kwargs): - """return encoded params to incorporate them in an URL""" - args = [] - for param, values in kwargs.items(): - if not isinstance(values, (list, tuple)): - values = (values,) - for value in values: - args.append(u'%s=%s' % (param, self.url_quote(value))) - return '&'.join(args) - - def url_quote(self, value, safe=''): - """urllib.quote is not unicode safe, use this method to do the - necessary encoding / decoding. Also it's designed to quote each - part of a url path and so the '/' character will be encoded as well. - """ - if isinstance(value, unicode): - quoted = urlquote(value.encode(self.encoding), safe=safe) - return unicode(quoted, self.encoding) - return urlquote(str(value), safe=safe) - - def url_unquote(self, quoted): - """returns a unicode unquoted string - - decoding is based on `self.encoding` which is the encoding - used in `url_quote` - """ - if isinstance(quoted, unicode): - quoted = quoted.encode(self.encoding) - try: - return unicode(urlunquote(quoted), self.encoding) - except UnicodeDecodeError: # might occurs on manually typed URLs - return unicode(urlunquote(quoted), 'iso-8859-1') - - - # session's user related methods ##################################### - - @cached - def user_data(self): - """returns a dictionnary with this user's information""" - userinfo = {} - if self.is_internal_session: - userinfo['login'] = "cubicweb" - userinfo['name'] = "cubicweb" - userinfo['email'] = "" - return userinfo - user = self.actual_session().user - rql = "Any F,S,A where U eid %(x)s, U firstname F, U surname S, U primary_email E, E address A" - try: - firstname, lastname, email = self.execute(rql, {'x': user.eid}, 'x')[0] - if firstname is None and lastname is None: - userinfo['name'] = '' - else: - userinfo['name'] = ("%s %s" % (firstname, lastname)) - userinfo['email'] = email - except IndexError: - userinfo['name'] = None - userinfo['email'] = None - userinfo['login'] = user.login - return userinfo - - def is_internal_session(self): - """overrided on the server-side""" - return False - - # abstract methods to override according to the web front-end ############# - - def base_url(self): - """return the root url of the instance""" - raise NotImplementedError - - def decorate_rset(self, rset): - """add vreg/req (at least) attributes to the given result set """ - raise NotImplementedError - - def describe(self, eid): - """return a tuple (type, sourceuri, extid) for the entity with id """ - raise NotImplementedError - - # XXX 2.45 is allowing nicer entity type names, use this map for bw compat ETYPE_NAME_MAP = {# 3.2 migration 'ECache': 'CWCache', diff -r 7ef2b08fbe28 -r 135580d15d42 appobject.py --- a/appobject.py Thu Aug 13 09:23:22 2009 +0200 +++ b/appobject.py Thu Aug 13 09:30:03 2009 +0200 @@ -11,17 +11,14 @@ import types from logging import getLogger -from datetime import datetime, timedelta, time +from datetime import datetime, timedelta from logilab.common.decorators import classproperty from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods -from rql.nodes import VariableRef, SubQuery -from rql.stmts import Union, Select - from cubicweb import Unauthorized, NoSelectableObject -from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime +from cubicweb.utils import UStringIO ONESECOND = timedelta(0, 1, 0) CACHE_REGISTRY = {} @@ -293,8 +290,6 @@ """ cls.build___select__() cls.vreg = registry.vreg - cls.schema = registry.schema - cls.config = registry.config cls.register_properties() return cls @@ -302,7 +297,7 @@ def vreg_initialization_completed(cls): pass - # Eproperties definition: + # properties definition: # key: id of the property (the actual CWProperty key is build using # .. # value: tuple (property type, vocabfunc, default value, property description) @@ -326,15 +321,6 @@ def propkey(cls, propid): return '%s.%s.%s' % (cls.__registry__, cls.id, propid) - @classproperty - @deprecated('[3.4] use __select__ and & or | operators') - def __selectors__(cls): - selector = cls.__select__ - if isinstance(selector, AndSelector): - return tuple(selector.selectors) - if not isinstance(selector, tuple): - selector = (selector,) - return selector def __init__(self, req=None, rset=None, row=None, col=None, **extra): super(AppObject, self).__init__() @@ -369,64 +355,12 @@ 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['components'].select_or_none('navigation', self.req, - rset=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 - def view(self, __vid, rset=None, __fallback_oid=None, __registry='views', **kwargs): """shortcut to self.vreg.view method avoiding to pass self.req""" return self.vreg[__registry].render(__vid, self.req, __fallback_oid, rset=rset, **kwargs) - def initialize_varmaker(self): - varmaker = self.req.get_page_data('rql_varmaker') - if varmaker is None: - varmaker = self.req.varmaker - self.req.set_page_data('rql_varmaker', varmaker) - self.varmaker = varmaker - # url generation methods ################################################## controller = 'view' @@ -452,42 +386,6 @@ method = self.req.relative_path(includeparams=False) or 'view' return self.req.build_url(method, **kwargs) - # various resources accessors ############################################# - - @deprecated('[3.5] use self.rset.get_entity(row,col) instead') - 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.rset.get_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 - """ - from simplejson import dumps - self.req.add_js('cubicweb.ajax.js') - 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): @@ -503,70 +401,66 @@ template.expand(context, output) return output.getvalue() + # deprecated ############################################################### + + @classproperty + @deprecated('[3.4] use __select__ and & or | operators') + def __selectors__(cls): + selector = cls.__select__ + if isinstance(selector, AndSelector): + return tuple(selector.selectors) + if not isinstance(selector, tuple): + selector = (selector,) + return selector + + @classmethod + @deprecated('[3.5] use vreg.schema') + def schema(cls): + return cls.vreg.schema + + @classmethod + @deprecated('[3.5] use vreg.config') + def schema(cls): + return cls.vreg.config + + @deprecated('[3.5] use req.varmaker') + def initialize_varmaker(self): + self.varmaker = self.req.varmaker + + @deprecated('[3.5] use rset.limited_rql') + def limited_rql(self): + return self.rset.limited_rql() + + @deprecated('[3.5] use self.rset.complete_entity(row,col) instead') + def complete_entity(self, row, col=0, skip_bytes=True): + return self.rset.complete_entity(row, col, skip_bytes) + + @deprecated('[3.5] use self.rset.get_entity(row,col) instead') + def entity(self, row, col=0): + return self.rset.get_entity(row, col) + + @deprecated('[3.5] use req.user_rql_callback') + def user_rql_callback(self, args, msg=None): + return self.req.user_rql_callback(args, msg) + + @deprecated('[3.5] use req.user_callback') + def user_callback(self, cb, args, msg=None, nonify=False): + return self.req.user_callback(cb, args, msg, nonify) + + @deprecated('[3.5] use req.format_date') def format_date(self, date, date_format=None, time=False): - """return a string for a date time according to instance'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'' + return self.req.format_date(date, date_format, time) + @deprecated('[3.5] use req.format_timoe') def format_time(self, time): - """return a string for a time according to instance's - configuration - """ - if time: - return ustrftime(time, self.req.property_value('ui.time-format')) - return u'' + return self.req.format_time(time) + @deprecated('[3.5] use req.format_float') def format_float(self, num): - """return a string for floating point number according to instance's - configuration - """ - if num: - return self.req.property_value('ui.float-format') % num - return u'' + return self.req.format_float(num) + @deprecated('[3.5] use req.parse_datetime') def parse_datetime(self, value, etype='Datetime'): - """get a datetime or time from a string (according to etype) - Datetime formatted as Date are accepted - """ - assert etype in ('Datetime', 'Date', 'Time'), etype - # XXX raise proper validation error - if etype == 'Datetime': - format = self.req.property_value('ui.datetime-format') - try: - return todatetime(strptime(value, format)) - except ValueError: - pass - elif etype == 'Time': - format = self.req.property_value('ui.time-format') - try: - # (adim) I can't find a way to parse a Time with a custom format - date = strptime(value, format) # this returns a DateTime - return time(date.hour, date.minute, date.second) - except ValueError: - raise ValueError('can\'t parse %r (expected %s)' % (value, format)) - try: - format = self.req.property_value('ui.date-format') - dt = strptime(value, format) - if etype == 'Datetime': - return todatetime(dt) - return todate(dt) - except ValueError: - raise ValueError('can\'t parse %r (expected %s)' % (value, format)) - - # 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')) + return self.req.parse_datetime(value, etype) set_log_methods(AppObject, getLogger('cubicweb.appobject')) diff -r 7ef2b08fbe28 -r 135580d15d42 dbapi.py --- a/dbapi.py Thu Aug 13 09:23:22 2009 +0200 +++ b/dbapi.py Thu Aug 13 09:30:03 2009 +0200 @@ -19,8 +19,9 @@ from logilab.common.decorators import monkeypatch from logilab.common.deprecation import deprecated -from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn -from cubicweb import cwvreg, cwconfig +from cubicweb import ETYPE_NAME_MAP, ConnectionError, cwvreg, cwconfig +from cubicweb.req import RequestSessionBase + _MARKER = object() @@ -174,7 +175,7 @@ return repo, cnx -class DBAPIRequest(RequestSessionMixIn): +class DBAPIRequest(RequestSessionBase): def __init__(self, vreg, cnx=None): super(DBAPIRequest, self).__init__(vreg) diff -r 7ef2b08fbe28 -r 135580d15d42 devtools/fake.py --- a/devtools/fake.py Thu Aug 13 09:23:22 2009 +0200 +++ b/devtools/fake.py Thu Aug 13 09:30:03 2009 +0200 @@ -11,7 +11,7 @@ from indexer import get_indexer -from cubicweb import RequestSessionMixIn +from cubicweb.req import RequestSessionBase from cubicweb.web.request import CubicWebRequestBase from cubicweb.devtools import BASE_URL, BaseApptestConfiguration @@ -173,7 +173,7 @@ return True -class FakeSession(RequestSessionMixIn): +class FakeSession(RequestSessionBase): def __init__(self, repo=None, user=None): self.repo = repo self.vreg = getattr(self.repo, 'vreg', FakeVReg()) diff -r 7ef2b08fbe28 -r 135580d15d42 goa/db.py --- a/goa/db.py Thu Aug 13 09:23:22 2009 +0200 +++ b/goa/db.py Thu Aug 13 09:30:03 2009 +0200 @@ -35,7 +35,8 @@ from logilab.common.decorators import cached, iclassmethod -from cubicweb import RequestSessionMixIn, Binary, entities +from cubicweb import Binary, entities +from cubicweb.req import RequestSessionBase from cubicweb.rset import ResultSet from cubicweb.entity import metaentity from cubicweb.server.utils import crypt_password @@ -92,7 +93,7 @@ def needrequest(wrapped): def wrapper(cls, *args, **kwargs): req = kwargs.pop('req', None) - if req is None and args and isinstance(args[0], RequestSessionMixIn): + if req is None and args and isinstance(args[0], RequestSessionBase): args = list(args) req = args.pop(0) if req is None: @@ -155,7 +156,7 @@ # # Entity prototype: # __init__(self, req, rset, row=None, col=0) - if args and isinstance(args[0], RequestSessionMixIn) or 'req' in kwargs: + if args and isinstance(args[0], RequestSessionBase) or 'req' in kwargs: super(Model, self).__init__(*args, **kwargs) self._gaeinitargs = None else: diff -r 7ef2b08fbe28 -r 135580d15d42 req.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/req.py Thu Aug 13 09:30:03 2009 +0200 @@ -0,0 +1,251 @@ +"""Base class for request/session + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: Library General Public License version 2 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +from datetime import time + +from logilab.common.decorators import cached + +from cubicweb import Unauthorized, typed_eid +from cubicweb.rset import ResultSet +from cubicweb.utils import ustrftime, strptime, todate, todatetime + +class RequestSessionBase(object): + """base class containing stuff shared by server session and web request + """ + def __init__(self, vreg): + self.vreg = vreg + try: + encoding = vreg.property_value('ui.encoding') + except: # no vreg or property not registered + encoding = 'utf-8' + self.encoding = encoding + # cache result of execution for (rql expr / eids), + # should be emptied on commit/rollback of the server session / web + # connection + self.local_perm_cache = {} + self._ = unicode + + def property_value(self, key): + """return value of the property with the given key, giving priority to + user specific value if any, else using site value + """ + if self.user: + return self.user.property_value(key) + return self.vreg.property_value(key) + + def etype_rset(self, etype, size=1): + """return a fake result set for a particular entity type""" + rset = ResultSet([('A',)]*size, '%s X' % etype, + description=[(etype,)]*size) + def get_entity(row, col=0, etype=etype, req=self, rset=rset): + return req.vreg.etype_class(etype)(req, rset, row, col) + rset.get_entity = get_entity + return self.decorate_rset(rset) + + def eid_rset(self, eid, etype=None): + """return a result set for the given eid without doing actual query + (we have the eid, we can suppose it exists and user has access to the + entity) + """ + eid = typed_eid(eid) + if etype is None: + etype = self.describe(eid)[0] + rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid}, + [(etype,)]) + return self.decorate_rset(rset) + + def empty_rset(self): + """return a result set for the given eid without doing actual query + (we have the eid, we can suppose it exists and user has access to the + entity) + """ + return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1')) + + def entity_from_eid(self, eid, etype=None): + """return an entity instance for the given eid. No query is done""" + try: + return self.entity_cache(eid) + except KeyError: + rset = self.eid_rset(eid, etype) + entity = rset.get_entity(0, 0) + self.set_entity_cache(entity) + return entity + + def entity_cache(self, eid): + raise KeyError + + def set_entity_cache(self, entity): + pass + + 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._('only select queries are authorized')) + + # url generation methods ################################################## + + def build_url(self, *args, **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. + """ + # use *args since we don't want first argument to be "anonymous" to + # avoid potential clash with kwargs + assert len(args) == 1, 'only 0 or 1 non-named-argument expected' + method = args[0] + base_url = kwargs.pop('base_url', None) + if base_url is None: + base_url = self.base_url() + if '_restpath' in kwargs: + assert method == 'view', method + path = kwargs.pop('_restpath') + else: + path = method + if not kwargs: + return u'%s%s' % (base_url, path) + return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs)) + + + def build_url_params(self, **kwargs): + """return encoded params to incorporate them in an URL""" + args = [] + for param, values in kwargs.items(): + if not isinstance(values, (list, tuple)): + values = (values,) + for value in values: + args.append(u'%s=%s' % (param, self.url_quote(value))) + return '&'.join(args) + + def url_quote(self, value, safe=''): + """urllib.quote is not unicode safe, use this method to do the + necessary encoding / decoding. Also it's designed to quote each + part of a url path and so the '/' character will be encoded as well. + """ + if isinstance(value, unicode): + quoted = urlquote(value.encode(self.encoding), safe=safe) + return unicode(quoted, self.encoding) + return urlquote(str(value), safe=safe) + + def url_unquote(self, quoted): + """returns a unicode unquoted string + + decoding is based on `self.encoding` which is the encoding + used in `url_quote` + """ + if isinstance(quoted, unicode): + quoted = quoted.encode(self.encoding) + try: + return unicode(urlunquote(quoted), self.encoding) + except UnicodeDecodeError: # might occurs on manually typed URLs + return unicode(urlunquote(quoted), 'iso-8859-1') + + # bound user related methods ############################################### + + @cached + def user_data(self): + """returns a dictionnary with this user's information""" + userinfo = {} + if self.is_internal_session: + userinfo['login'] = "cubicweb" + userinfo['name'] = "cubicweb" + userinfo['email'] = "" + return userinfo + user = self.actual_session().user + rql = "Any F,S,A where U eid %(x)s, U firstname F, U surname S, U primary_email E, E address A" + try: + firstname, lastname, email = self.execute(rql, {'x': user.eid}, 'x')[0] + if firstname is None and lastname is None: + userinfo['name'] = '' + else: + userinfo['name'] = ("%s %s" % (firstname, lastname)) + userinfo['email'] = email + except IndexError: + userinfo['name'] = None + userinfo['email'] = None + userinfo['login'] = user.login + return userinfo + + def is_internal_session(self): + """overrided on the server-side""" + return False + + # formating methods ####################################################### + + def format_date(self, date, date_format=None, time=False): + """return a string for a date time according to instance's + configuration + """ + if date: + if date_format is None: + if time: + date_format = self.property_value('ui.datetime-format') + else: + date_format = self.property_value('ui.date-format') + return ustrftime(date, date_format) + return u'' + + def format_time(self, time): + """return a string for a time according to instance's + configuration + """ + if time: + return ustrftime(time, self.property_value('ui.time-format')) + return u'' + + def format_float(self, num): + """return a string for floating point number according to instance's + configuration + """ + if num: + return self.property_value('ui.float-format') % num + return u'' + + def parse_datetime(self, value, etype='Datetime'): + """get a datetime or time from a string (according to etype) + Datetime formatted as Date are accepted + """ + assert etype in ('Datetime', 'Date', 'Time'), etype + # XXX raise proper validation error + if etype == 'Datetime': + format = self.property_value('ui.datetime-format') + try: + return todatetime(strptime(value, format)) + except ValueError: + pass + elif etype == 'Time': + format = self.property_value('ui.time-format') + try: + # (adim) I can't find a way to parse a Time with a custom format + date = strptime(value, format) # this returns a DateTime + return time(date.hour, date.minute, date.second) + except ValueError: + raise ValueError('can\'t parse %r (expected %s)' % (value, format)) + try: + format = self.property_value('ui.date-format') + dt = strptime(value, format) + if etype == 'Datetime': + return todatetime(dt) + return todate(dt) + except ValueError: + raise ValueError('can\'t parse %r (expected %s)' % (value, format)) + + # abstract methods to override according to the web front-end ############# + + def base_url(self): + """return the root url of the instance""" + raise NotImplementedError + + def decorate_rset(self, rset): + """add vreg/req (at least) attributes to the given result set """ + raise NotImplementedError + + def describe(self, eid): + """return a tuple (type, sourceuri, extid) for the entity with id """ + raise NotImplementedError diff -r 7ef2b08fbe28 -r 135580d15d42 rset.py --- a/rset.py Thu Aug 13 09:23:22 2009 +0200 +++ b/rset.py Thu Aug 13 09:30:03 2009 +0200 @@ -9,7 +9,7 @@ from logilab.common.decorators import cached, clear_cache, copy_cache -from rql import nodes +from rql import nodes, stmts from cubicweb import NotAnEntity @@ -240,8 +240,6 @@ rset = mapping[key] rset.rows.append(self.rows[idx]) rset.description.append(self.description[idx]) - - for rset in result: rset.rowcount = len(rset.rows) if return_dict: @@ -249,6 +247,51 @@ else: return result + 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['components'].select_or_none('navigation', self.req, + rset=self) + 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.limited: + rql = self._limit_offset_rql(*self.limited) + # navigation component doesn't apply and rset has not been limited, no + # need to limit query + else: + rql = self.printable_rql() + return rql + + def _limit_offset_rql(self, limit, offset): + rqlst = self.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.args) + # restore original limit/offset + select.limit, select.offset = olimit, ooffset + else: + newselect = stmts.Select() + newselect.limit = limit + newselect.offset = offset + aliases = [nodes.VariableRef(newselect.get_variable(vref.name, i)) + for i, vref in enumerate(rqlst.selection)] + newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False) + newunion = stmts.Union() + newunion.append(newselect) + rql = rqlst.as_string(kwargs=self.args) + rqlst.parent = None + return rql + def limit(self, limit, offset=0, inplace=False): """limit the result set to the given number of rows optionaly starting from an index different than 0 @@ -318,6 +361,14 @@ if self.rows[i][col] is not None: yield self.get_entity(i, 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.get_entity(row, col) + entity.complete(skip_bytes=skip_bytes) + return entity + @cached def get_entity(self, row, col=None): """special method for query retreiving a single entity, returns a diff -r 7ef2b08fbe28 -r 135580d15d42 server/session.py --- a/server/session.py Thu Aug 13 09:23:22 2009 +0200 +++ b/server/session.py Thu Aug 13 09:30:03 2009 +0200 @@ -15,7 +15,8 @@ from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj from yams import BASE_TYPES -from cubicweb import RequestSessionMixIn, Binary, UnknownEid +from cubicweb import Binary, UnknownEid +from cubicweb.req import RequestSessionBase from cubicweb.dbapi import ConnectionProperties from cubicweb.utils import make_uid from cubicweb.server.rqlrewrite import RQLRewriter @@ -41,7 +42,7 @@ return description -class Session(RequestSessionMixIn): +class Session(RequestSessionBase): """tie session id, user, connections pool and other session data all together """ diff -r 7ef2b08fbe28 -r 135580d15d42 web/controller.py --- a/web/controller.py Thu Aug 13 09:23:22 2009 +0200 +++ b/web/controller.py Thu Aug 13 09:30:03 2009 +0200 @@ -90,7 +90,7 @@ # XXX assigning to self really necessary? self.rset = None if rql: - self.ensure_ro_rql(rql) + self.req.ensure_ro_rql(rql) if not isinstance(rql, unicode): rql = unicode(rql, self.req.encoding) pp = self.vreg['components'].select_or_none('magicsearch', self.req) diff -r 7ef2b08fbe28 -r 135580d15d42 web/request.py --- a/web/request.py Thu Aug 13 09:23:22 2009 +0200 +++ b/web/request.py Thu Aug 13 09:30:03 2009 +0200 @@ -15,11 +15,12 @@ from urlparse import urlsplit from itertools import count +from simplejson import dumps + from rql.utils import rqlvar_maker from logilab.common.decorators import cached from logilab.common.deprecation import deprecated - from logilab.mtconverter import xml_escape from cubicweb.dbapi import DBAPIRequest @@ -85,9 +86,16 @@ self.next_tabindex = self.tabindexgen.next # page id, set by htmlheader template self.pageid = None - self.varmaker = rqlvar_maker() self.datadir_url = self._datadir_url() + @property + def varmaker(self) + varmaker = self.get_page_data('rql_varmaker') + if varmaker is None: + varmaker = rqlvar_maker() + self.set_page_data('rql_varmaker', varmaker) + return varmaker + def set_connection(self, cnx, user=None): """method called by the session handler when the user is authenticated or an anonymous connection is open @@ -257,6 +265,24 @@ return breadcrumbs.pop() return self.base_url() + 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.add_js('cubicweb.ajax.js') + cbname = self.register_onetime_callback(cb, *args) + msg = dumps(msg or '') + return "javascript:userCallbackThenReloadPage('%s', %s)" % ( + cbname, msg) + def register_onetime_callback(self, func, *args): cbname = 'cb_%s' % ( sha.sha('%s%s%s%s' % (time.time(), func.__name__, diff -r 7ef2b08fbe28 -r 135580d15d42 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Thu Aug 13 09:23:22 2009 +0200 +++ b/web/views/basecontrollers.py Thu Aug 13 09:30:03 2009 +0200 @@ -290,7 +290,7 @@ def _exec(self, rql, args=None, eidkey=None, rocheck=True): """json mode: execute RQL and return resultset as json""" if rocheck: - self.ensure_ro_rql(rql) + self.req.ensure_ro_rql(rql) try: return self.req.execute(rql, args, eidkey) except Exception, ex: diff -r 7ef2b08fbe28 -r 135580d15d42 web/views/tableview.py --- a/web/views/tableview.py Thu Aug 13 09:23:22 2009 +0200 +++ b/web/views/tableview.py Thu Aug 13 09:30:03 2009 +0200 @@ -293,7 +293,7 @@ displaycols=None, displayactions=None, mainindex=None): """Dumps a table displaying a composite query""" actrql = self.req.form['actualrql'] - self.ensure_ro_rql(actrql) + self.req.ensure_ro_rql(actrql) displaycols = self.displaycols(displaycols) if displayactions is None and 'displayactions' in self.req.form: displayactions = True