# HG changeset patch # User Alexandre Fayolle # Date 1303280932 -7200 # Node ID 5a0ee8de7f06cea707be42ea8a1d0a998281fe3c # Parent 69aa3e68c231625f2f58df64d9f53b7058dcd335# Parent 506a0af77cee9c516cfddf1fd2cf89ab1077afae backport stable changes to default diff -r 506a0af77cee -r 5a0ee8de7f06 .hgtags --- a/.hgtags Mon Apr 18 08:03:46 2011 +0200 +++ b/.hgtags Wed Apr 20 08:28:52 2011 +0200 @@ -188,3 +188,9 @@ 77318f1ec4aae3523d455e884daf3708c3c79af7 cubicweb-debian-version-3.11.1-1 56ae3cd5f8553678a2b1d4121b61241598d0ca68 cubicweb-version-3.11.2 954b5b51cd9278eb45d66be1967064d01ab08453 cubicweb-debian-version-3.11.2-1 +fd502219eb76f4bfd239d838a498a1d1e8204baf cubicweb-version-3.12.0 +92b56939b7c77bbf443b893c495a20f19bc30702 cubicweb-debian-version-3.12.0-1 +59701627adba73ee97529f6ea0e250a0f3748e32 cubicweb-version-3.12.1 +07e2c9c7df2617c5ecfa84cb819b3ee8ef91d1f2 cubicweb-debian-version-3.12.1-1 +5a9b6bc5653807500c30a7eb0e95b90fd714fec3 cubicweb-version-3.12.2 +6d418fb3ffed273562aae411efe323d5138b592a cubicweb-debian-version-3.12.2-1 diff -r 506a0af77cee -r 5a0ee8de7f06 __pkginfo__.py --- a/__pkginfo__.py Mon Apr 18 08:03:46 2011 +0200 +++ b/__pkginfo__.py Wed Apr 20 08:28:52 2011 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 11, 2) +numversion = (3, 12, 2) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" @@ -43,7 +43,7 @@ 'logilab-common': '>= 0.55.2', 'logilab-mtconverter': '>= 0.8.0', 'rql': '>= 0.28.0', - 'yams': '>= 0.30.4', + 'yams': '>= 0.32.0', 'docutils': '>= 0.6', #gettext # for xgettext, msgcat, etc... # web dependancies @@ -52,7 +52,7 @@ 'Twisted': '', # XXX graphviz # server dependencies - 'logilab-database': '>= 1.4.0', + 'logilab-database': '>= 1.5.0', 'pysqlite': '>= 2.5.5', # XXX install pysqlite2 } diff -r 506a0af77cee -r 5a0ee8de7f06 cwvreg.py --- a/cwvreg.py Mon Apr 18 08:03:46 2011 +0200 +++ b/cwvreg.py Wed Apr 20 08:28:52 2011 +0200 @@ -312,6 +312,10 @@ kwargs['clear'] = True super(ETypeRegistry, self).register(obj, **kwargs) + def iter_classes(self): + for etype in self.vreg.schema.entities(): + yield self.etype_class(etype) + @cached def parent_classes(self, etype): if etype == 'Any': @@ -835,18 +839,24 @@ return self['views'].select(__vid, req, rset=rset, **kwargs) +import decimal from datetime import datetime, date, time, timedelta -YAMS_TO_PY = { - 'Boolean': bool, +YAMS_TO_PY = { # XXX unify with yams.constraints.BASE_CONVERTERS? 'String' : unicode, + 'Bytes': Binary, 'Password': str, - 'Bytes': Binary, + + 'Boolean': bool, 'Int': int, 'Float': float, - 'Date': date, - 'Datetime': datetime, - 'Time': time, - 'Interval': timedelta, + 'Decimal': decimal.Decimal, + + 'Date': date, + 'Datetime': datetime, + 'TZDatetime': datetime, + 'Time': time, + 'TZTime': time, + 'Interval': timedelta, } diff -r 506a0af77cee -r 5a0ee8de7f06 dataimport.py --- a/dataimport.py Mon Apr 18 08:03:46 2011 +0200 +++ b/dataimport.py Wed Apr 20 08:28:52 2011 +0200 @@ -133,7 +133,7 @@ """ for idx, item in enumerate(iterable): yield item - if idx % number: + if not idx % number: func() func() diff -r 506a0af77cee -r 5a0ee8de7f06 debian/changelog --- a/debian/changelog Mon Apr 18 08:03:46 2011 +0200 +++ b/debian/changelog Wed Apr 20 08:28:52 2011 +0200 @@ -1,6 +1,24 @@ +cubicweb (3.12.2-1) unstable; urgency=low + + * new upstream release + + -- Nicolas Chauvat Mon, 11 Apr 2011 22:15:21 +0200 + +cubicweb (3.12.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 06 Apr 2011 23:25:12 +0200 + +cubicweb (3.12.0-1) unstable; urgency=low + + * new upstream release + + -- Alexandre Fayolle Fri, 01 Apr 2011 15:59:37 +0200 + cubicweb (3.11.2-1) unstable; urgency=low - * new upstream release + * new upstream release -- Nicolas Chauvat Mon, 28 Mar 2011 19:18:54 +0200 diff -r 506a0af77cee -r 5a0ee8de7f06 debian/control --- a/debian/control Mon Apr 18 08:03:46 2011 +0200 +++ b/debian/control Wed Apr 20 08:28:52 2011 +0200 @@ -33,7 +33,7 @@ Conflicts: cubicweb-multisources Replaces: cubicweb-multisources Provides: cubicweb-multisources -Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.4.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2 +Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.5.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2 Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version}) Description: server part of the CubicWeb framework CubicWeb is a semantic web application framework. @@ -97,7 +97,7 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.30.4), python-rql (>= 0.28.0), python-lxml +Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.32.0), python-rql (>= 0.28.0), python-lxml Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 506a0af77cee -r 5a0ee8de7f06 devtools/__init__.py --- a/devtools/__init__.py Mon Apr 18 08:03:46 2011 +0200 +++ b/devtools/__init__.py Wed Apr 20 08:28:52 2011 +0200 @@ -384,7 +384,7 @@ def get_cnx(self): - """return Connection object ont he current repository""" + """return Connection object on the current repository""" from cubicweb.dbapi import in_memory_cnx repo = self.get_repo() sources = self.config.sources() diff -r 506a0af77cee -r 5a0ee8de7f06 devtools/fill.py --- a/devtools/fill.py Mon Apr 18 08:03:46 2011 +0200 +++ b/devtools/fill.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,5 +1,5 @@ # -*- coding: iso-8859-1 -*- -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -152,6 +152,8 @@ base = datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60) return self._constrained_generate(entity, attrname, base, timedelta(hours=1), index) + generate_tzdatetime = generate_datetime # XXX implementation should add a timezone + def generate_date(self, entity, attrname, index): """generates a random date (format is 'yyyy-mm-dd')""" base = date(randint(2000, 2010), 1, 1) + timedelta(randint(1, 365)) @@ -166,6 +168,8 @@ """generates a random time (format is ' HH:MM')""" return time(11, index%60) #'11:%02d' % (index % 60) + generate_tztime = generate_time # XXX implementation should add a timezone + def generate_bytes(self, entity, attrname, index, format=None): fakefile = Binary("%s%s" % (attrname, index)) fakefile.filename = u"file_%s" % attrname @@ -441,7 +445,7 @@ constraints = [c for c in rdef.constraints if isinstance(c, RQLConstraint)] if constraints: - restrictions = ', '.join(c.restriction for c in constraints) + restrictions = ', '.join(c.expression for c in constraints) q += ', %s' % restrictions # restrict object eids if possible # XXX the attempt to restrict below in completely wrong diff -r 506a0af77cee -r 5a0ee8de7f06 devtools/testlib.py --- a/devtools/testlib.py Mon Apr 18 08:03:46 2011 +0200 +++ b/devtools/testlib.py Wed Apr 20 08:28:52 2011 +0200 @@ -37,7 +37,7 @@ from logilab.common.pytest import nocoverage, pause_tracing, resume_tracing from logilab.common.debugger import Debugger from logilab.common.umessage import message_from_string -from logilab.common.decorators import cached, classproperty, clear_cache +from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod from logilab.common.deprecation import deprecated, class_deprecated from logilab.common.shellutils import getlogin @@ -46,7 +46,7 @@ from cubicweb.dbapi import ProgrammingError, DBAPISession, repo_connect from cubicweb.sobjects import notification from cubicweb.web import Redirect, application -from cubicweb.server.session import security_enabled +from cubicweb.server.session import Session, security_enabled from cubicweb.server.hook import SendMailOp from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS from cubicweb.devtools import BASE_URL, fake, htmlparser, DEFAULT_EMPTY_DB_ID @@ -354,13 +354,24 @@ else: return req.user - def create_user(self, login, groups=('users',), password=None, req=None, + @iclassmethod # XXX turn into a class method + def create_user(self, req, login=None, groups=('users',), password=None, commit=True, **kwargs): """create and return a new user entity""" + if isinstance(req, basestring): + warn('[3.12] create_user arguments are now (req, login[, groups, password, commit, **kwargs])', + DeprecationWarning, stacklevel=1) + if not isinstance(groups, (tuple, list)): + password = groups + groups = login + elif isinstance(login, tuple): + groups = login + login = req + if req is None: + assert not isinstance(self, type) + req = self._orig_cnx[0].request() if password is None: password = login.encode('utf8') - if req is None: - req = self._orig_cnx[0].request() user = req.create_entity('CWUser', login=unicode(login), upassword=password, **kwargs) req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)' @@ -368,9 +379,37 @@ {'x': user.eid}) user.cw_clear_relation_cache('in_group', 'subject') if commit: - req.cnx.commit() + try: + req.commit() # req is a session + except AttributeError: + req.cnx.commit() return user + @iclassmethod # XXX turn into a class method + def grant_permission(self, session, entity, group, pname=None, plabel=None): + """insert a permission on an entity. Will have to commit the main + connection to be considered + """ + if not isinstance(session, Session): + warn('[3.12] grant_permission arguments are now (session, entity, group, pname[, plabel])', + DeprecationWarning, stacklevel=1) + plabel = pname + pname = group + group = entity + entity = session + assert not isinstance(self, type) + session = self.session + pname = unicode(pname) + plabel = plabel and unicode(plabel) or unicode(group) + e = entity.eid + with security_enabled(session, False, False): + peid = session.execute( + 'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,' + 'X require_group G, E require_permission X ' + 'WHERE G name %(group)s, E eid %(e)s', + locals())[0][0] + return peid + def login(self, login, **kwargs): """return a connection for the given login/password""" if login == self.admlogin: @@ -439,21 +478,6 @@ # other utilities ######################################################### - def grant_permission(self, entity, group, pname, plabel=None): - """insert a permission on an entity. Will have to commit the main - connection to be considered - """ - pname = unicode(pname) - plabel = plabel and unicode(plabel) or unicode(group) - e = entity.eid - with security_enabled(self.session, False, False): - peid = self.execute( - 'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,' - 'X require_group G, E require_permission X ' - 'WHERE G name %(group)s, E eid %(e)s', - locals())[0][0] - return peid - @contextmanager def temporary_appobjects(self, *appobjects): self.vreg._loadedmods.setdefault(self.__module__, {}) diff -r 506a0af77cee -r 5a0ee8de7f06 doc/tools/pyjsrest.py --- a/doc/tools/pyjsrest.py Mon Apr 18 08:03:46 2011 +0200 +++ b/doc/tools/pyjsrest.py Wed Apr 20 08:28:52 2011 +0200 @@ -153,9 +153,6 @@ 'jquery.flot.js', 'jquery.corner.js', 'jquery.ui.js', - 'ui.core.js', - 'ui.tabs.js', - 'ui.slider.js', 'excanvas.js', 'gmap.utility.labeledmarker.js', diff -r 506a0af77cee -r 5a0ee8de7f06 entity.py --- a/entity.py Mon Apr 18 08:03:46 2011 +0200 +++ b/entity.py Wed Apr 20 08:28:52 2011 +0200 @@ -28,7 +28,7 @@ from rql.utils import rqlvar_maker -from cubicweb import Unauthorized, typed_eid +from cubicweb import Unauthorized, typed_eid, neg_role from cubicweb.rset import ResultSet from cubicweb.selectors import yes from cubicweb.appobject import AppObject @@ -157,6 +157,7 @@ def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', settype=True, ordermethod='fetch_order'): """return a rql to fetch all entities of the class type""" + # XXX update api and implementation to AST manipulation (see unrelated rql) restrictions = restriction or [] if settype: restrictions.append('%s is %s' % (mainvar, cls.__regid__)) @@ -165,6 +166,7 @@ selection = [mainvar] orderby = [] # start from 26 to avoid possible conflicts with X + # XXX not enough to be sure it'll be no conflicts varmaker = rqlvar_maker(index=26) cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection, orderby, restrictions, user, ordermethod) @@ -264,6 +266,7 @@ restrictions = set() pending_relations = [] eschema = cls.e_schema + cache = {} for attr, value in kwargs.items(): if attr.startswith('reverse_'): attr = attr[len('reverse_'):] @@ -272,7 +275,9 @@ role = 'subject' assert eschema.has_relation(attr, role) rschema = eschema.subjrels[attr] if role == 'subject' else eschema.objrels[attr] - if not rschema.final and isinstance(value, (tuple, list, set, frozenset)): + if rschema.final: + cache[attr] = value # XXX what if value is processed in pre add hook (eg timeseries)? + elif isinstance(value, (tuple, list, set, frozenset)): if len(value) == 1: value = iter(value).next() else: @@ -297,6 +302,7 @@ if restrictions: rql = '%s WHERE %s' % (rql, ', '.join(restrictions)) created = execute(rql, kwargs).get_entity(0, 0) + created.cw_attr_cache.update(cache) for attr, values in pending_relations: if attr.startswith('reverse_'): restr = 'Y %s X' % attr[len('reverse_'):] @@ -762,7 +768,7 @@ # generic vocabulary methods ############################################## def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None, - vocabconstraints=True): + vocabconstraints=True): """build a rql to fetch `targettype` entities unrelated to this entity using (rtype, role) relation. @@ -772,58 +778,83 @@ ordermethod = ordermethod or 'fetch_unrelated_order' if isinstance(rtype, basestring): rtype = self._cw.vreg.schema.rschema(rtype) + rdef = rtype.role_rdef(self.e_schema, targettype, role) + rewriter = RQLRewriter(self._cw) + # initialize some variables according to the `role` of `self` in the + # relation: + # * variable for myself (`evar`) and searched entities (`searchvedvar`) + # * entity type of the subject (`subjtype`) and of the object + # (`objtype`) of the relation if role == 'subject': evar, searchedvar = 'S', 'O' subjtype, objtype = self.e_schema, targettype else: searchedvar, evar = 'S', 'O' objtype, subjtype = self.e_schema, targettype + # initialize some variables according to `self` existance + if rdef.role_cardinality(neg_role(role)) in '?1': + # if cardinality in '1?', we want a target entity which isn't + # already linked using this relation + if searchedvar == 'S': + restriction = ['NOT S %s ZZ' % rtype] + else: + restriction = ['NOT ZZ %s O' % rtype] + elif self.has_eid(): + # elif we have an eid, we don't want a target entity which is + # already linked to ourself through this relation + restriction = ['NOT S %s O' % rtype] + else: + restriction = [] if self.has_eid(): - restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar] + restriction += ['%s eid %%(x)s' % evar] args = {'x': self.eid} if role == 'subject': - securitycheck_args = {'fromeid': self.eid} + sec_check_args = {'fromeid': self.eid} else: - securitycheck_args = {'toeid': self.eid} + sec_check_args = {'toeid': self.eid} + existant = None # instead of 'SO', improve perfs else: - restriction = [] args = {} - securitycheck_args = {} - rdef = rtype.role_rdef(self.e_schema, targettype, role) - insertsecurity = (rdef.has_local_role('add') and not - rdef.has_perm(self._cw, 'add', **securitycheck_args)) - # XXX consider constraint.mainvars to check if constraint apply + sec_check_args = {} + existant = searchedvar + # retreive entity class for targettype to compute base rql + etypecls = self._cw.vreg['etypes'].etype_class(targettype) + rql = etypecls.fetch_rql(self._cw.user, restriction, + mainvar=searchedvar, ordermethod=ordermethod) + select = self._cw.vreg.parse(self._cw, rql, args).children[0] + # insert RQL expressions for schema constraints into the rql syntax tree if vocabconstraints: # RQLConstraint is a subclass for RQLVocabularyConstraint, so they # will be included as well - restriction += [cstr.restriction for cstr in rdef.constraints - if isinstance(cstr, RQLVocabularyConstraint)] + cstrcls = RQLVocabularyConstraint else: - restriction += [cstr.restriction for cstr in rdef.constraints - if isinstance(cstr, RQLConstraint)] - etypecls = self._cw.vreg['etypes'].etype_class(targettype) - rql = etypecls.fetch_rql(self._cw.user, restriction, - mainvar=searchedvar, ordermethod=ordermethod) + cstrcls = RQLConstraint + for cstr in rdef.constraints: + # consider constraint.mainvars to check if constraint apply + if isinstance(cstr, cstrcls) and searchedvar in cstr.mainvars: + if not self.has_eid() and evar in cstr.mainvars: + continue + # compute a varmap suitable to RQLRewriter.rewrite argument + varmap = dict((v, v) for v in 'SO' if v in select.defined_vars + and v in cstr.mainvars) + # rewrite constraint by constraint since we want a AND between + # expressions. + rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions, + args, existant) + # insert security RQL expressions granting the permission to 'add' the + # relation into the rql syntax tree, if necessary + rqlexprs = rdef.get_rqlexprs('add') + if rqlexprs and not rdef.has_perm(self._cw, 'add', **sec_check_args): + # compute a varmap suitable to RQLRewriter.rewrite argument + varmap = dict((v, v) for v in 'SO' if v in select.defined_vars) + # rewrite all expressions at once since we want a OR between them. + rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions, + args, existant) # ensure we have an order defined - if not ' ORDERBY ' in rql: - before, after = rql.split(' WHERE ', 1) - rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after) - if insertsecurity: - rqlexprs = rdef.get_rqlexprs('add') - rewriter = RQLRewriter(self._cw) - rqlst = self._cw.vreg.parse(self._cw, rql, args) - if not self.has_eid(): - existant = searchedvar - else: - existant = None # instead of 'SO', improve perfs - for select in rqlst.children: - varmap = {} - for var in 'SO': - if var in select.defined_vars: - varmap[var] = var - rewriter.rewrite(select, [(varmap, rqlexprs)], - select.solutions, args, existant) - rql = rqlst.as_string() + if not select.orderby: + select.add_sort_var(select.defined_vars[searchedvar]) + # we're done, turn the rql syntax tree as a string + rql = select.as_string() return rql, args def unrelated(self, rtype, targettype, role='subject', limit=None, @@ -835,6 +866,7 @@ rql, args = self.cw_unrelated_rql(rtype, targettype, role, ordermethod) except Unauthorized: return self._cw.empty_rset() + # XXX should be set in unrelated rql when manipulating the AST if limit is not None: before, after = rql.split(' WHERE ', 1) rql = '%s LIMIT %s WHERE %s' % (before, limit, after) diff -r 506a0af77cee -r 5a0ee8de7f06 i18n/de.po --- a/i18n/de.po Mon Apr 18 08:03:46 2011 +0200 +++ b/i18n/de.po Wed Apr 20 08:28:52 2011 +0200 @@ -737,6 +737,18 @@ msgid "Submit bug report by mail" msgstr "Diesen Bericht als E-Mail senden" +msgid "TZDatetime" +msgstr "" + +msgid "TZDatetime_plural" +msgstr "" + +msgid "TZTime" +msgstr "" + +msgid "TZTime_plural" +msgstr "" + #, python-format msgid "The view %s can not be applied to this query" msgstr "Die Ansicht %s ist auf diese Anfrage nicht anwendbar." @@ -2743,6 +2755,9 @@ "zeigt an, welcher Zustand standardmäßig benutzt werden soll, wenn eine " "Entität erstellt wird" +msgid "indifferent" +msgstr "gleichgültig" + msgid "info" msgstr "Information" diff -r 506a0af77cee -r 5a0ee8de7f06 i18n/en.po --- a/i18n/en.po Mon Apr 18 08:03:46 2011 +0200 +++ b/i18n/en.po Wed Apr 20 08:28:52 2011 +0200 @@ -711,6 +711,18 @@ msgid "Submit bug report by mail" msgstr "" +msgid "TZDatetime" +msgstr "International date and time" + +msgid "TZDatetime_plural" +msgstr "International dates and times" + +msgid "TZTime" +msgstr "International time" + +msgid "TZTime_plural" +msgstr "International times" + #, python-format msgid "The view %s can not be applied to this query" msgstr "" @@ -2668,6 +2680,9 @@ "is created" msgstr "" +msgid "indifferent" +msgstr "indifferent" + msgid "info" msgstr "" diff -r 506a0af77cee -r 5a0ee8de7f06 i18n/es.po --- a/i18n/es.po Mon Apr 18 08:03:46 2011 +0200 +++ b/i18n/es.po Wed Apr 20 08:28:52 2011 +0200 @@ -732,6 +732,18 @@ msgid "Submit bug report by mail" msgstr "Enviar este reporte por email" +msgid "TZDatetime" +msgstr "" + +msgid "TZDatetime_plural" +msgstr "" + +msgid "TZTime" +msgstr "" + +msgid "TZTime_plural" +msgstr "" + #, python-format msgid "The view %s can not be applied to this query" msgstr "La vista %s no puede ser aplicada a esta búsqueda" @@ -2773,6 +2785,9 @@ msgstr "" "Indica cual estado deberá ser utilizado por defecto al crear una entidad" +msgid "indifferent" +msgstr "indifferente" + msgid "info" msgstr "Información del Sistema" diff -r 506a0af77cee -r 5a0ee8de7f06 i18n/fr.po --- a/i18n/fr.po Mon Apr 18 08:03:46 2011 +0200 +++ b/i18n/fr.po Wed Apr 20 08:28:52 2011 +0200 @@ -449,7 +449,7 @@ msgstr "Date et heure" msgid "Datetime_plural" -msgstr "Date et heure" +msgstr "Dates et heures" msgid "Decimal" msgstr "Nombre décimal" @@ -736,6 +736,18 @@ msgid "Submit bug report by mail" msgstr "Soumettre ce rapport par email" +msgid "TZDatetime" +msgstr "Date et heure internationale" + +msgid "TZDatetime_plural" +msgstr "Dates et heures internationales" + +msgid "TZTime" +msgstr "Heure internationale" + +msgid "TZTime_plural" +msgstr "Heures internationales" + #, python-format msgid "The view %s can not be applied to this query" msgstr "La vue %s ne peut être appliquée à cette requête" @@ -2783,6 +2795,9 @@ msgstr "" "indique quel état devrait être utilisé par défaut lorsqu'une entité est créée" +msgid "indifferent" +msgstr "indifférent" + msgid "info" msgstr "information" diff -r 506a0af77cee -r 5a0ee8de7f06 misc/migration/3.12.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.12.0_Any.py Wed Apr 20 08:28:52 2011 +0200 @@ -0,0 +1,2 @@ +add_entity_type('TZDatetime') +add_entity_type('TZTime') diff -r 506a0af77cee -r 5a0ee8de7f06 rqlrewrite.py --- a/rqlrewrite.py Mon Apr 18 08:03:46 2011 +0200 +++ b/rqlrewrite.py Wed Apr 20 08:28:52 2011 +0200 @@ -257,6 +257,11 @@ insert_scope = None for vi in self.varinfos: scope = vi.get('stinfo', {}).get('scope', self.select) + while True: + negstmt = scope.neged() + if negstmt is None: + break + scope = negstmt.scope if insert_scope is None: insert_scope = scope else: diff -r 506a0af77cee -r 5a0ee8de7f06 schema.py --- a/schema.py Mon Apr 18 08:03:46 2011 +0200 +++ b/schema.py Wed Apr 20 08:28:52 2011 +0200 @@ -28,6 +28,7 @@ from logilab.common.decorators import cached, clear_cache, monkeypatch from logilab.common.logging_ext import set_log_methods from logilab.common.deprecation import deprecated, class_moved +from logilab.common.textutils import splitstrip from logilab.common.graph import get_cycles from logilab.common.compat import any @@ -179,35 +180,6 @@ __builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name) -# rql expression utilities function ############################################ - -def guess_rrqlexpr_mainvars(expression): - defined = set(split_expression(expression)) - mainvars = [] - if 'S' in defined: - mainvars.append('S') - if 'O' in defined: - mainvars.append('O') - if 'U' in defined: - mainvars.append('U') - if not mainvars: - raise Exception('unable to guess selection variables') - return ','.join(sorted(mainvars)) - -def split_expression(rqlstring): - for expr in rqlstring.split(','): - for noparen in expr.split('('): - for word in noparen.split(): - yield word - -def normalize_expression(rqlstring): - """normalize an rql expression to ease schema synchronization (avoid - suppressing and reinserting an expression if only a space has been added/removed - for instance) - """ - return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(',')) - - # Schema objects definition ################################################### def ERSchema_display_name(self, req, form='', context=None): @@ -640,175 +612,57 @@ def schema_by_eid(self, eid): return self._eid_index[eid] - -# Possible constraints ######################################################## - -class BaseRQLConstraint(BaseConstraint): - """base class for rql constraints - """ - distinct_query = None - - def __init__(self, restriction, mainvars=None): - self.restriction = normalize_expression(restriction) - if mainvars is None: - mainvars = guess_rrqlexpr_mainvars(restriction) - else: - normmainvars = [] - for mainvar in mainvars.split(','): - mainvar = mainvar.strip() - if not mainvar.isalpha(): - raise Exception('bad mainvars %s' % mainvars) - normmainvars.append(mainvar) - assert mainvars, 'bad mainvars %s' % mainvars - mainvars = ','.join(sorted(normmainvars)) - self.mainvars = mainvars - - def serialize(self): - # start with a comma for bw compat, see below - return ';' + self.mainvars + ';' + self.restriction - - @classmethod - def deserialize(cls, value): - # XXX < 3.5.10 bw compat - if not value.startswith(';'): - return cls(value) - _, mainvars, restriction = value.split(';', 2) - return cls(restriction, mainvars) - - def check(self, entity, rtype, value): - """return true if the value satisfy the constraint, else false""" - # implemented as a hook in the repository - return 1 - - def repo_check(self, session, eidfrom, rtype, eidto): - """raise ValidationError if the relation doesn't satisfy the constraint - """ - pass # this is a vocabulary constraint, not enforce XXX why? - - def __str__(self): - if self.distinct_query: - selop = 'Any' - else: - selop = 'DISTINCT Any' - return '%s(%s %s WHERE %s)' % (self.__class__.__name__, selop, - self.mainvars, self.restriction) - - def __repr__(self): - return '<%s @%#x>' % (self.__str__(), id(self)) - - -class RQLVocabularyConstraint(BaseRQLConstraint): - """the rql vocabulary constraint : - - limit the proposed values to a set of entities returned by a rql query, - but this is not enforced at the repository level - - restriction is additional rql restriction that will be added to - a predefined query, where the S and O variables respectivly represent - the subject and the object of the relation - - mainvars is a string that should be used as selection variable (eg - `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be - done to guess it according to variable used in the expression. - """ - - -class RepoEnforcedRQLConstraintMixIn(object): +# Bases for manipulating RQL in schema ######################################### - def __init__(self, restriction, mainvars=None, msg=None): - super(RepoEnforcedRQLConstraintMixIn, self).__init__(restriction, mainvars) - self.msg = msg - - def serialize(self): - # start with a semicolon for bw compat, see below - return ';%s;%s\n%s' % (self.mainvars, self.restriction, - self.msg or '') - - def deserialize(cls, value): - # XXX < 3.5.10 bw compat - if not value.startswith(';'): - return cls(value) - value, msg = value.split('\n', 1) - _, mainvars, restriction = value.split(';', 2) - return cls(restriction, mainvars, msg) - deserialize = classmethod(deserialize) +def guess_rrqlexpr_mainvars(expression): + defined = set(split_expression(expression)) + mainvars = set() + if 'S' in defined: + mainvars.add('S') + if 'O' in defined: + mainvars.add('O') + if 'U' in defined: + mainvars.add('U') + if not mainvars: + raise Exception('unable to guess selection variables') + return mainvars - def repo_check(self, session, eidfrom, rtype, eidto=None): - """raise ValidationError if the relation doesn't satisfy the constraint - """ - if not self.match_condition(session, eidfrom, eidto): - # XXX at this point if both or neither of S and O are in mainvar we - # dunno if the validation error `occurred` on eidfrom or eidto (from - # user interface point of view) - # - # possible enhancement: check entity being created, it's probably - # the main eid unless this is a composite relation - if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars: - maineid = eidfrom - qname = role_name(rtype, 'subject') - else: - maineid = eidto - qname = role_name(rtype, 'object') - if self.msg: - msg = session._(self.msg) - else: - msg = '%(constraint)s %(restriction)s failed' % { - 'constraint': session._(self.type()), - 'restriction': self.restriction} - raise ValidationError(maineid, {qname: msg}) +def split_expression(rqlstring): + for expr in rqlstring.split(','): + for noparen in expr.split('('): + for word in noparen.split(): + yield word - def exec_query(self, session, eidfrom, eidto): - if eidto is None: - # checking constraint for an attribute relation - restriction = 'S eid %(s)s, ' + self.restriction - args = {'s': eidfrom} - else: - restriction = 'S eid %(s)s, O eid %(o)s, ' + self.restriction - args = {'s': eidfrom, 'o': eidto} - rql = 'Any %s WHERE %s' % (self.mainvars, restriction) - if self.distinct_query: - rql = 'DISTINCT ' + rql - return session.execute(rql, args, build_descr=False) - - -class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint): - """the rql constraint is similar to the RQLVocabularyConstraint but - are also enforced at the repository level +def normalize_expression(rqlstring): + """normalize an rql expression to ease schema synchronization (avoid + suppressing and reinserting an expression if only a space has been + added/removed for instance) """ - distinct_query = False - - def match_condition(self, session, eidfrom, eidto): - return self.exec_query(session, eidfrom, eidto) - - -class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint): - """the unique rql constraint check that the result of the query isn't - greater than one. - - You *must* specify mainvars when instantiating the constraint since there is - no way to guess it correctly (e.g. if using S,O or U the constraint will - always be satisfied because we've to use a DISTINCT query). - """ - # XXX turns mainvars into a required argument in __init__ - distinct_query = True - - def match_condition(self, session, eidfrom, eidto): - return len(self.exec_query(session, eidfrom, eidto)) <= 1 + return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(',')) class RQLExpression(object): + """Base class for RQL expression used in schema (constraints and + permissions) + """ + # these are overridden by set_log_methods below + # only defining here to prevent pylint from complaining + info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None def __init__(self, expression, mainvars, eid): self.eid = eid # eid of the entity representing this rql expression - if not isinstance(mainvars, unicode): - mainvars = unicode(mainvars) + assert mainvars, 'bad mainvars %s' % mainvars + if isinstance(mainvars, basestring): + mainvars = set(splitstrip(mainvars)) + elif not isinstance(mainvars, set): + mainvars = set(mainvars) self.mainvars = mainvars self.expression = normalize_expression(expression) try: self.rqlst = parse(self.full_rql, print_errors=False).children[0] except RQLSyntaxError: raise RQLSyntaxError(expression) - for mainvar in mainvars.split(','): + for mainvar in mainvars: if len(self.rqlst.defined_vars[mainvar].references()) <= 2: _LOGGER.warn('You did not use the %s variable in your RQL ' 'expression %s', mainvar, self) @@ -832,6 +686,8 @@ def __setstate__(self, state): self.__init__(*state) + # permission rql expression specific stuff ################################# + @cached def transform_has_permission(self): found = None @@ -942,12 +798,10 @@ @property def minimal_rql(self): - return 'Any %s WHERE %s' % (self.mainvars, self.expression) + return 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), + self.expression) - # these are overridden by set_log_methods below - # only defining here to prevent pylint from complaining - info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None - +# rql expressions for use in permission definition ############################# class ERQLExpression(RQLExpression): def __init__(self, expression, mainvars=None, eid=None): @@ -1024,12 +878,153 @@ kwargs['o'] = toeid return self._check(session, **kwargs) + # in yams, default 'update' perm for attributes granted to managers and owners. # Within cw, we want to default to users who may edit the entity holding the # attribute. ybo.DEFAULT_ATTRPERMS['update'] = ( 'managers', ERQLExpression('U has_update_permission X')) +# additional cw specific constraints ########################################### + +class BaseRQLConstraint(RRQLExpression, BaseConstraint): + """base class for rql constraints""" + distinct_query = None + + def serialize(self): + # start with a comma for bw compat,see below + return ';' + ','.join(sorted(self.mainvars)) + ';' + self.expression + + @classmethod + def deserialize(cls, value): + # XXX < 3.5.10 bw compat + if not value.startswith(';'): + return cls(value) + _, mainvars, expression = value.split(';', 2) + return cls(expression, mainvars) + + def check(self, entity, rtype, value): + """return true if the value satisfy the constraint, else false""" + # implemented as a hook in the repository + return 1 + + def __str__(self): + if self.distinct_query: + selop = 'Any' + else: + selop = 'DISTINCT Any' + return '%s(%s %s WHERE %s)' % (self.__class__.__name__, selop, + ','.join(sorted(self.mainvars)), + self.expression) + + def __repr__(self): + return '<%s @%#x>' % (self.__str__(), id(self)) + + +class RQLVocabularyConstraint(BaseRQLConstraint): + """the rql vocabulary constraint: + + limit the proposed values to a set of entities returned by a rql query, + but this is not enforced at the repository level + + `expression` is additional rql restriction that will be added to + a predefined query, where the S and O variables respectivly represent + the subject and the object of the relation + + `mainvars` is a set of variables that should be used as selection variable + (eg `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be + done to guess it according to variable used in the expression. + """ + + def repo_check(self, session, eidfrom, rtype, eidto): + """raise ValidationError if the relation doesn't satisfy the constraint + """ + pass # this is a vocabulary constraint, not enforce + + +class RepoEnforcedRQLConstraintMixIn(object): + + def __init__(self, expression, mainvars=None, msg=None): + super(RepoEnforcedRQLConstraintMixIn, self).__init__(expression, mainvars) + self.msg = msg + + def serialize(self): + # start with a semicolon for bw compat, see below + return ';%s;%s\n%s' % (','.join(sorted(self.mainvars)), self.expression, + self.msg or '') + + def deserialize(cls, value): + # XXX < 3.5.10 bw compat + if not value.startswith(';'): + return cls(value) + value, msg = value.split('\n', 1) + _, mainvars, expression = value.split(';', 2) + return cls(expression, mainvars, msg) + deserialize = classmethod(deserialize) + + def repo_check(self, session, eidfrom, rtype, eidto=None): + """raise ValidationError if the relation doesn't satisfy the constraint + """ + if not self.match_condition(session, eidfrom, eidto): + # XXX at this point if both or neither of S and O are in mainvar we + # dunno if the validation error `occurred` on eidfrom or eidto (from + # user interface point of view) + # + # possible enhancement: check entity being created, it's probably + # the main eid unless this is a composite relation + if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars: + maineid = eidfrom + qname = role_name(rtype, 'subject') + else: + maineid = eidto + qname = role_name(rtype, 'object') + if self.msg: + msg = session._(self.msg) + else: + msg = '%(constraint)s %(expression)s failed' % { + 'constraint': session._(self.type()), + 'expression': self.expression} + raise ValidationError(maineid, {qname: msg}) + + def exec_query(self, session, eidfrom, eidto): + if eidto is None: + # checking constraint for an attribute relation + expression = 'S eid %(s)s, ' + self.expression + args = {'s': eidfrom} + else: + expression = 'S eid %(s)s, O eid %(o)s, ' + self.expression + args = {'s': eidfrom, 'o': eidto} + rql = 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), expression) + if self.distinct_query: + rql = 'DISTINCT ' + rql + return session.execute(rql, args, build_descr=False) + + +class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint): + """the rql constraint is similar to the RQLVocabularyConstraint but + are also enforced at the repository level + """ + distinct_query = False + + def match_condition(self, session, eidfrom, eidto): + return self.exec_query(session, eidfrom, eidto) + + +class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint): + """the unique rql constraint check that the result of the query isn't + greater than one. + + You *must* specify `mainvars` when instantiating the constraint since there + is no way to guess it correctly (e.g. if using S,O or U the constraint will + always be satisfied because we've to use a DISTINCT query). + """ + # XXX turns mainvars into a required argument in __init__ + distinct_query = True + + def match_condition(self, session, eidfrom, eidto): + return len(self.exec_query(session, eidfrom, eidto)) <= 1 + + # workflow extensions ######################################################### from yams.buildobjs import _add_relation as yams_add_relation diff -r 506a0af77cee -r 5a0ee8de7f06 server/migractions.py --- a/server/migractions.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/migractions.py Wed Apr 20 08:28:52 2011 +0200 @@ -438,7 +438,8 @@ 'X expression %%(expr)s, X mainvars %%(vars)s, T %s X ' 'WHERE T eid %%(x)s' % perm, {'expr': expr, 'exprtype': exprtype, - 'vars': expression.mainvars, 'x': teid}, + 'vars': u','.join(sorted(expression.mainvars)), + 'x': teid}, ask_confirm=False) def _synchronize_rschema(self, rtype, syncrdefs=True, @@ -757,9 +758,9 @@ targeted type is known """ instschema = self.repo.schema - assert not etype in instschema, \ + eschema = self.fs_schema.eschema(etype) + assert eschema.final or not etype in instschema, \ '%s already defined in the instance schema' % etype - eschema = self.fs_schema.eschema(etype) confirm = self.verbosity >= 2 groupmap = self.group_mapping() cstrtypemap = self.cstrtype_mapping() diff -r 506a0af77cee -r 5a0ee8de7f06 server/schemaserial.py --- a/server/schemaserial.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/schemaserial.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -566,7 +566,7 @@ yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, ' 'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action, {'e': unicode(rqlexpr.expression), - 'v': unicode(rqlexpr.mainvars), + 'v': unicode(','.join(sorted(rqlexpr.mainvars))), 't': unicode(rqlexpr.__class__.__name__)}) # update functions diff -r 506a0af77cee -r 5a0ee8de7f06 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/sources/rql2sql.py Wed Apr 20 08:28:52 2011 +0200 @@ -50,7 +50,9 @@ __docformat__ = "restructuredtext en" import threading +from datetime import datetime, time +from logilab.common.date import utcdatetime, utctime from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY from rql import BadRQLQuery, CoercionError @@ -1424,6 +1426,14 @@ _id = value if isinstance(_id, unicode): _id = _id.encode() + # convert timestamp to utc. + # expect SET TiME ZONE to UTC at connection opening time. + # This shouldn't change anything for datetime without TZ. + value = self._args[_id] + if isinstance(value, datetime) and value.tzinfo is not None: + self._query_attrs[_id] = utcdatetime(value) + elif isinstance(value, time) and value.tzinfo is not None: + self._query_attrs[_id] = utctime(value) else: _id = str(id(constant)).replace('-', '', 1) self._query_attrs[_id] = value diff -r 506a0af77cee -r 5a0ee8de7f06 server/sqlutils.py --- a/server/sqlutils.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/sqlutils.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -25,7 +25,7 @@ from logilab import database as db, common as lgc from logilab.common.shellutils import ProgressBar -from logilab.common.date import todate, todatetime +from logilab.common.date import todate, todatetime, utcdatetime, utctime from logilab.database.sqlgen import SQLGenerator from cubicweb import Binary, ConfigurationError @@ -274,10 +274,15 @@ value = crypt_password(value) value = self._binary(value) # XXX needed for sqlite but I don't think it is for other backends - elif atype == 'Datetime' and isinstance(value, date): + # Note: use is __class__ since issubclass(datetime, date) + elif atype in ('Datetime', 'TZDatetime') and type(value) is date: value = todatetime(value) elif atype == 'Date' and isinstance(value, datetime): value = todate(value) + elif atype == 'TZDatetime' and getattr(value, 'tzinfo', None): + value = utcdatetime(value) + elif atype == 'TZTime' and getattr(value, 'tzinfo', None): + value = utctime(value) elif isinstance(value, Binary): value = self._binary(value.getvalue()) attrs[SQL_PREFIX+str(attr)] = value @@ -326,3 +331,13 @@ sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', []) sqlite_hooks.append(init_sqlite_connexion) + + +def init_postgres_connexion(cnx): + cnx.cursor().execute('SET TIME ZONE UTC') + # commit is needed, else setting are lost if the connection is first + # rollbacked + cnx.commit() + +postgres_hooks = SQL_CONNECT_HOOKS.setdefault('postgres', []) +postgres_hooks.append(init_postgres_connexion) diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/data/schema.py --- a/server/test/data/schema.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/test/data/schema.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -18,7 +18,7 @@ from yams.buildobjs import (EntityType, RelationType, RelationDefinition, SubjectRelation, RichString, String, Int, Float, - Boolean, Datetime) + Boolean, Datetime, TZDatetime) from yams.constraints import SizeConstraint from cubicweb.schema import (WorkflowableEntityType, RQLConstraint, RQLUniqueConstraint, @@ -114,6 +114,7 @@ tel = Int() fax = Int() datenaiss = Datetime() + tzdatenaiss = TZDatetime() test = Boolean(__permissions__={ 'read': ('managers', 'users', 'guests'), 'update': ('managers',), diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/data/sources_fti --- a/server/test/data/sources_fti Mon Apr 18 08:03:46 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -[system] - -db-driver = postgres -db-host = localhost -db-port = -adapter = native -db-name = cw_fti_test -db-encoding = UTF-8 -db-user = syt -db-password = syt - -[admin] -login = admin -password = gingkow diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/data/sources_postgres --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/data/sources_postgres Wed Apr 20 08:28:52 2011 +0200 @@ -0,0 +1,14 @@ +[system] + +db-driver = postgres +db-host = localhost +db-port = 5433 +adapter = native +db-name = cw_fti_test +db-encoding = UTF-8 +db-user = syt +db-password = syt + +[admin] +login = admin +password = gingkow diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/unittest_fti.py --- a/server/test/unittest_fti.py Mon Apr 18 08:03:46 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -from __future__ import with_statement - -import socket - -from logilab.common.testlib import SkipTest - -from cubicweb.devtools import ApptestConfiguration -from cubicweb.devtools.testlib import CubicWebTC -from cubicweb.selectors import is_instance -from cubicweb.entities.adapters import IFTIndexableAdapter - -AT_LOGILAB = socket.gethostname().endswith('.logilab.fr') - - -class PostgresFTITC(CubicWebTC): - config = ApptestConfiguration('data', sourcefile='sources_fti') - - @classmethod - def setUpClass(cls): - if not AT_LOGILAB: - raise SkipTest('XXX %s: require logilab configuration' % cls.__name__) - - def test_occurence_count(self): - req = self.request() - c1 = req.create_entity('Card', title=u'c1', - content=u'cubicweb cubicweb cubicweb') - c2 = req.create_entity('Card', title=u'c3', - content=u'cubicweb') - c3 = req.create_entity('Card', title=u'c2', - content=u'cubicweb cubicweb') - self.commit() - self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows, - [[c1.eid], [c3.eid], [c2.eid]]) - - - def test_attr_weight(self): - class CardIFTIndexableAdapter(IFTIndexableAdapter): - __select__ = is_instance('Card') - attr_weight = {'title': 'A'} - with self.temporary_appobjects(CardIFTIndexableAdapter): - req = self.request() - c1 = req.create_entity('Card', title=u'c1', - content=u'cubicweb cubicweb cubicweb') - c2 = req.create_entity('Card', title=u'c2', - content=u'cubicweb cubicweb') - c3 = req.create_entity('Card', title=u'cubicweb', - content=u'autre chose') - self.commit() - self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows, - [[c3.eid], [c1.eid], [c2.eid]]) - - def test_entity_weight(self): - class PersonneIFTIndexableAdapter(IFTIndexableAdapter): - __select__ = is_instance('Personne') - entity_weight = 2.0 - with self.temporary_appobjects(PersonneIFTIndexableAdapter): - req = self.request() - c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb') - c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1) - c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1) - self.commit() - self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows, - [[c1.eid], [c3.eid], [c2.eid]]) - - -if __name__ == '__main__': - from logilab.common.testlib import unittest_main - unittest_main() diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/unittest_postgres.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/unittest_postgres.py Wed Apr 20 08:28:52 2011 +0200 @@ -0,0 +1,83 @@ +from __future__ import with_statement + +import socket +from datetime import datetime + +from logilab.common.testlib import SkipTest + +from cubicweb.devtools import ApptestConfiguration +from cubicweb.devtools.testlib import CubicWebTC +from cubicweb.selectors import is_instance +from cubicweb.entities.adapters import IFTIndexableAdapter + +AT_LOGILAB = socket.gethostname().endswith('.logilab.fr') # XXX + +from unittest_querier import FixedOffset + +class PostgresFTITC(CubicWebTC): + @classmethod + def setUpClass(cls): + if not AT_LOGILAB: # XXX here until we can raise SkipTest in setUp to detect we can't connect to the db + raise SkipTest('XXX %s: require logilab configuration' % cls.__name__) + cls.config = ApptestConfiguration('data', sourcefile='sources_postgres', + apphome=cls.datadir) + + def test_occurence_count(self): + req = self.request() + c1 = req.create_entity('Card', title=u'c1', + content=u'cubicweb cubicweb cubicweb') + c2 = req.create_entity('Card', title=u'c3', + content=u'cubicweb') + c3 = req.create_entity('Card', title=u'c2', + content=u'cubicweb cubicweb') + self.commit() + self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows, + [[c1.eid], [c3.eid], [c2.eid]]) + + + def test_attr_weight(self): + class CardIFTIndexableAdapter(IFTIndexableAdapter): + __select__ = is_instance('Card') + attr_weight = {'title': 'A'} + with self.temporary_appobjects(CardIFTIndexableAdapter): + req = self.request() + c1 = req.create_entity('Card', title=u'c1', + content=u'cubicweb cubicweb cubicweb') + c2 = req.create_entity('Card', title=u'c2', + content=u'cubicweb cubicweb') + c3 = req.create_entity('Card', title=u'cubicweb', + content=u'autre chose') + self.commit() + self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows, + [[c3.eid], [c1.eid], [c2.eid]]) + + def test_entity_weight(self): + class PersonneIFTIndexableAdapter(IFTIndexableAdapter): + __select__ = is_instance('Personne') + entity_weight = 2.0 + with self.temporary_appobjects(PersonneIFTIndexableAdapter): + req = self.request() + c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb') + c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1) + c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1) + self.commit() + self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows, + [[c1.eid], [c3.eid], [c2.eid]]) + + + def test_tz_datetime(self): + self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s", + {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))}) + datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0] + self.assertEqual(datenaiss.tzinfo, None) + self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0)) + self.commit() + self.execute("INSERT Personne X: X nom 'boby', X tzdatenaiss %(date)s", + {'date': datetime(1977, 6, 7, 2, 0)}) + datenaiss = self.execute("Any XD WHERE X nom 'boby', X tzdatenaiss XD")[0][0] + self.assertEqual(datenaiss.tzinfo, None) + self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 2, 0)) + +if __name__ == '__main__': + from logilab.common.testlib import unittest_main + unittest_main() diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/test/unittest_querier.py Wed Apr 20 08:28:52 2011 +0200 @@ -18,7 +18,7 @@ # with CubicWeb. If not, see . """unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner """ -from datetime import date, datetime +from datetime import date, datetime, timedelta, tzinfo from logilab.common.testlib import TestCase, unittest_main from rql import BadRQLQuery, RQLSyntaxError @@ -32,6 +32,14 @@ from cubicweb.devtools.repotest import tuplify, BaseQuerierTC from unittest_session import Variable +class FixedOffset(tzinfo): + def __init__(self, hours=0): + self.hours = hours + def utcoffset(self, dt): + return timedelta(hours=self.hours) + def dst(self, dt): + return timedelta(0) + # register priority/severity sorting registered procedure from rql.utils import register_function, FunctionDescr @@ -761,18 +769,20 @@ rset = self.execute('Any N WHERE X is CWEType, X name N, X final %(val)s', {'val': True}) self.assertEqual(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes', - 'Date', 'Datetime', - 'Decimal', 'Float', - 'Int', 'Interval', - 'Password', 'String', - 'Time']) + 'Date', 'Datetime', + 'Decimal', 'Float', + 'Int', 'Interval', + 'Password', 'String', + 'TZDatetime', 'TZTime', + 'Time']) rset = self.execute('Any N WHERE X is CWEType, X name N, X final TRUE') self.assertEqual(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes', - 'Date', 'Datetime', - 'Decimal', 'Float', - 'Int', 'Interval', - 'Password', 'String', - 'Time']) + 'Date', 'Datetime', + 'Decimal', 'Float', + 'Int', 'Interval', + 'Password', 'String', + 'TZDatetime', 'TZTime', + 'Time']) def test_select_constant(self): rset = self.execute('Any X, "toto" ORDERBY X WHERE X is CWGroup') @@ -1213,11 +1223,11 @@ self.assertEqual(rset.description, [('CWUser',)]) def test_update_upassword(self): - cursor = self.pool['system'] rset = self.execute("INSERT CWUser X: X login 'bob', X upassword %(pwd)s", {'pwd': 'toto'}) self.assertEqual(rset.description[0][0], 'CWUser') rset = self.execute("SET X upassword %(pwd)s WHERE X is CWUser, X login 'bob'", {'pwd': 'tutu'}) + cursor = self.pool['system'] cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'" % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX)) passwd = str(cursor.fetchone()[0]) @@ -1227,7 +1237,16 @@ self.assertEqual(len(rset.rows), 1) self.assertEqual(rset.description, [('CWUser',)]) - # non regression tests #################################################### + # ZT datetime tests ######################################################## + + def test_tz_datetime(self): + self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s", + {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))}) + datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0] + self.assertEqual(datenaiss.tzinfo, None) + self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0)) + + # non regression tests ##################################################### def test_nonregr_1(self): teid = self.execute("INSERT Tag X: X name 'tag'")[0][0] diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/test/unittest_repository.py Wed Apr 20 08:28:52 2011 +0200 @@ -69,11 +69,12 @@ cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s' % (namecol, table, finalcol, namecol), {'final': 'TRUE'}) self.assertEqual(cu.fetchall(), [(u'Boolean',), (u'Bytes',), - (u'Date',), (u'Datetime',), - (u'Decimal',),(u'Float',), - (u'Int',), - (u'Interval',), (u'Password',), - (u'String',), (u'Time',)]) + (u'Date',), (u'Datetime',), + (u'Decimal',),(u'Float',), + (u'Int',), + (u'Interval',), (u'Password',), + (u'String',), + (u'TZDatetime',), (u'TZTime',), (u'Time',)]) sql = ("SELECT etype.cw_eid, etype.cw_name, cstr.cw_eid, rel.eid_to " "FROM cw_CWUniqueTogetherConstraint as cstr, " " relations_relation as rel, " @@ -327,7 +328,7 @@ self.assertEqual(len(constraints), 1) cstr = constraints[0] self.assert_(isinstance(cstr, RQLConstraint)) - self.assertEqual(cstr.restriction, 'O final TRUE') + self.assertEqual(cstr.expression, 'O final TRUE') ownedby = schema.rschema('owned_by') self.assertEqual(ownedby.objects('CWEType'), ('CWUser',)) diff -r 506a0af77cee -r 5a0ee8de7f06 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Mon Apr 18 08:03:46 2011 +0200 +++ b/server/test/unittest_rql2sql.py Wed Apr 20 08:28:52 2011 +0200 @@ -1225,9 +1225,13 @@ yield self._check, rql, sql def _checkall(self, rql, sql): + if isinstance(rql, tuple): + rql, args = rql + else: + args = None try: rqlst = self._prepare(rql) - r, args, cbs = self.o.generate(rqlst) + r, args, cbs = self.o.generate(rqlst, args) self.assertEqual((r.strip(), args), sql) except Exception, ex: print rql @@ -1239,7 +1243,7 @@ return def test1(self): - self._checkall('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s', + self._checkall(('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s', {'x': None}), ("""SELECT COUNT(T1.C0) FROM (SELECT _RDEF.cw_eid AS C0 FROM cw_CWAttribute AS _RDEF WHERE _RDEF.cw_relation_type=%(x)s @@ -1250,7 +1254,7 @@ ) def test2(self): - self._checkall('Any X WHERE C comments X, C eid %(x)s', + self._checkall(('Any X WHERE C comments X, C eid %(x)s', {'x': None}), ('''SELECT rel_comments0.eid_to FROM comments_relation AS rel_comments0 WHERE rel_comments0.eid_from=%(x)s''', {}) diff -r 506a0af77cee -r 5a0ee8de7f06 test/data/schema.py --- a/test/data/schema.py Mon Apr 18 08:03:46 2011 +0200 +++ b/test/data/schema.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,13 +15,11 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""" - -""" from yams.buildobjs import (EntityType, String, SubjectRelation, RelationDefinition) -from cubicweb.schema import WorkflowableEntityType +from cubicweb.schema import (WorkflowableEntityType, + RQLConstraint, RQLVocabularyConstraint) class Personne(EntityType): nom = String(required=True) @@ -29,7 +27,14 @@ type = String() travaille = SubjectRelation('Societe') evaluee = SubjectRelation(('Note', 'Personne')) - connait = SubjectRelation('Personne', symmetric=True) + connait = SubjectRelation( + 'Personne', symmetric=True, + constraints=[ + RQLConstraint('NOT S identity O'), + # conflicting constraints, see cw_unrelated_rql tests in + # unittest_entity.py + RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'), + RQLVocabularyConstraint('S travaille P, P nom "tutu"')]) class Societe(EntityType): nom = String() diff -r 506a0af77cee -r 5a0ee8de7f06 test/unittest_entity.py --- a/test/unittest_entity.py Mon Apr 18 08:03:46 2011 +0200 +++ b/test/unittest_entity.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -19,7 +19,7 @@ """unit tests for cubicweb.web.views.entities module""" from datetime import datetime - +from logilab.common import tempattr from cubicweb import Binary, Unauthorized from cubicweb.devtools.testlib import CubicWebTC from cubicweb.mttransforms import HAS_TAL @@ -29,6 +29,17 @@ class EntityTC(CubicWebTC): + def setUp(self): + super(EntityTC, self).setUp() + self.backup_dict = {} + for cls in self.vreg['etypes'].iter_classes(): + self.backup_dict[cls] = (cls.fetch_attrs, cls.fetch_order) + + def tearDown(self): + super(EntityTC, self).tearDown() + for cls in self.vreg['etypes'].iter_classes(): + cls.fetch_attrs, cls.fetch_order = self.backup_dict[cls] + def test_boolean_value(self): e = self.vreg['etypes'].etype_class('CWUser')(self.request()) self.failUnless(e) @@ -136,17 +147,19 @@ Note = self.vreg['etypes'].etype_class('Note') peschema = Personne.e_schema seschema = Societe.e_schema - peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '1*' - peschema.subjrels['connait'].rdef(peschema, peschema).cardinality = '11' - peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema).cardinality = '1*' - seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema).cardinality = '1*' - # testing basic fetch_attrs attribute - self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC ORDERBY AA ASC ' - 'WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC') - pfetch_attrs = Personne.fetch_attrs - sfetch_attrs = Societe.fetch_attrs + torestore = [] + for rdef, card in [(peschema.subjrels['travaille'].rdef(peschema, seschema), '1*'), + (peschema.subjrels['connait'].rdef(peschema, peschema), '11'), + (peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema), '1*'), + (seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema), '1*')]: + cm = tempattr(rdef, 'cardinality', card) + cm.__enter__() + torestore.append(cm) try: + # testing basic fetch_attrs attribute + self.assertEqual(Personne.fetch_rql(user), + 'Any X,AA,AB,AC ORDERBY AA ASC ' + 'WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC') # testing unknown attributes Personne.fetch_attrs = ('bloug', 'beep') self.assertEqual(Personne.fetch_rql(user), 'Any X WHERE X is Personne') @@ -185,8 +198,9 @@ 'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB') # XXX test unauthorized attribute finally: - Personne.fetch_attrs = pfetch_attrs - Societe.fetch_attrs = sfetch_attrs + # fetch_attrs restored by generic tearDown + for cm in torestore: + cm.__exit__(None, None, None) def test_related_rql_base(self): Personne = self.vreg['etypes'].etype_class('Personne') @@ -227,7 +241,7 @@ user = self.request().user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' - 'WHERE NOT S use_email O, S eid %(x)s, ' + 'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, ' 'O is EmailAddress, O address AA, O alias AB, O modification_date AC') def test_unrelated_rql_security_1_user(self): @@ -236,43 +250,80 @@ user = self.request().user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' - 'WHERE NOT S use_email O, S eid %(x)s, ' + 'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, ' 'O is EmailAddress, O address AA, O alias AB, O modification_date AC') user = self.execute('Any X WHERE X login "admin"').get_entity(0, 0) rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] - self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' - 'NOT EXISTS(S use_email O), S eid %(x)s, ' - 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, ' - 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' + 'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, A eid %(B)s, ' + 'EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') def test_unrelated_rql_security_1_anon(self): self.login('anon') user = self.request().user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] - self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' - 'NOT EXISTS(S use_email O), S eid %(x)s, ' - 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, ' - 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' + 'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, A eid %(B)s, ' + 'EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') def test_unrelated_rql_security_2(self): email = self.execute('INSERT EmailAddress X: X address "hop"').get_entity(0, 0) rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0] - self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ASC ' - 'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD') + self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ' + 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, ' + 'S login AA, S firstname AB, S surname AC, S modification_date AD') self.login('anon') email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0) rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0] self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ' - 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, ' - 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, ' + 'S login AA, S firstname AB, S surname AC, S modification_date AD, ' + 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') def test_unrelated_rql_security_nonexistant(self): self.login('anon') email = self.vreg['etypes'].etype_class('EmailAddress')(self.request()) rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0] self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ' - 'WHERE S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, ' - 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + 'WHERE S is CWUser, ' + 'S login AA, S firstname AB, S surname AC, S modification_date AD, ' + 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + + def test_unrelated_rql_constraints_creation_subject(self): + person = self.vreg['etypes'].etype_class('Personne')(self.request()) + rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0] + self.assertEqual( + rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' + 'O is Personne, O nom AA, O prenom AB, O modification_date AC') + + def test_unrelated_rql_constraints_creation_object(self): + person = self.vreg['etypes'].etype_class('Personne')(self.request()) + rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0] + self.assertEqual( + rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE ' + 'S is Personne, S nom AA, S prenom AB, S modification_date AC, ' + 'NOT (S connait A, A nom "toto"), A is Personne, EXISTS(S travaille B, B nom "tutu")') + + def test_unrelated_rql_constraints_edition_subject(self): + person = self.request().create_entity('Personne', nom=u'sylvain') + rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0] + self.assertEqual( + rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' + 'NOT EXISTS(S connait O), S eid %(x)s, O is Personne, ' + 'O nom AA, O prenom AB, O modification_date AC, ' + 'NOT S identity O') + + def test_unrelated_rql_constraints_edition_object(self): + person = self.request().create_entity('Personne', nom=u'sylvain') + rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0] + self.assertEqual( + rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE ' + 'NOT EXISTS(S connait O), O eid %(x)s, S is Personne, ' + 'S nom AA, S prenom AB, S modification_date AC, ' + 'NOT S identity O, NOT (S connait A, A nom "toto"), ' + 'EXISTS(S travaille B, B nom "tutu")') def test_unrelated_base(self): req = self.request() diff -r 506a0af77cee -r 5a0ee8de7f06 test/unittest_schema.py --- a/test/unittest_schema.py Mon Apr 18 08:03:46 2011 +0200 +++ b/test/unittest_schema.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -169,7 +169,7 @@ 'Password', 'Personne', 'RQLExpression', 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint', - 'Tag', 'Time', 'Transition', 'TrInfo', + 'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo', 'Workflow', 'WorkflowTransition'] self.assertListEqual(sorted(expected_entities), entities) relations = sorted([str(r) for r in schema.relations()]) @@ -239,7 +239,7 @@ self.failUnlessEqual(len(constraints), 1, constraints) constraint = constraints[0] self.failUnless(isinstance(constraint, RQLConstraint)) - self.failUnlessEqual(constraint.restriction, 'O final TRUE') + self.failUnlessEqual(constraint.expression, 'O final TRUE') def test_fulltext_container(self): schema = loader.load(config) @@ -315,7 +315,7 @@ class GuessRrqlExprMainVarsTC(TestCase): def test_exists(self): mainvars = guess_rrqlexpr_mainvars(normalize_expression('NOT EXISTS(O team_competition C, C level < 3)')) - self.assertEqual(mainvars, 'O') + self.assertEqual(mainvars, set(['O'])) if __name__ == '__main__': diff -r 506a0af77cee -r 5a0ee8de7f06 uilib.py --- a/uilib.py Mon Apr 18 08:03:46 2011 +0200 +++ b/uilib.py Wed Apr 20 08:28:52 2011 +0200 @@ -62,9 +62,9 @@ return value if attrtype == 'Date': return ustrftime(value, req.property_value('ui.date-format')) - if attrtype == 'Time': + if attrtype in ('Time', 'TZTime'): return ustrftime(value, req.property_value('ui.time-format')) - if attrtype == 'Datetime': + if attrtype in ('Datetime', 'TZDatetime'): if displaytime: return ustrftime(value, req.property_value('ui.datetime-format')) return ustrftime(value, req.property_value('ui.date-format')) @@ -72,8 +72,9 @@ if value: return req._('yes') return req._('no') - if attrtype == 'Float': + if attrtype in ('Float', 'Decimal'): value = req.property_value('ui.float-format') % value + # XXX Interval return unicode(value) diff -r 506a0af77cee -r 5a0ee8de7f06 utils.py --- a/utils.py Mon Apr 18 08:03:46 2011 +0200 +++ b/utils.py Wed Apr 20 08:28:52 2011 +0200 @@ -361,17 +361,43 @@ self.doctype = u'' # xmldecl and html opening tag self.xmldecl = u'\n' % req.encoding - self.htmltag = u'' % (req.lang, req.lang) + self._namespaces = [('xmlns', 'http://www.w3.org/1999/xhtml'), + ('xmlns:cubicweb','http://www.logilab.org/2008/cubicweb')] + self._htmlattrs = [('xml:lang', req.lang), + ('lang', req.lang)] # keep main_stream's reference on req for easier text/html demoting req.main_stream = self + def add_namespace(self, prefix, uri): + self._namespaces.append( (prefix, uri) ) + + def set_namespaces(self, namespaces): + self._namespaces = namespaces + + def add_htmlattr(self, attrname, attrvalue): + self._htmlattrs.append( (attrname, attrvalue) ) + + def set_htmlattrs(self, attrs): + self._htmlattrs = attrs + + def set_doctype(self, doctype, reset_xmldecl=True): + self.doctype = doctype + if reset_xmldecl: + self.xmldecl = u'' + def write(self, data): """StringIO interface: this method will be assigned to self.w """ self.body.write(data) + @property + def htmltag(self): + attrs = ' '.join('%s="%s"' % (attr, xml_escape(value)) + for attr, value in (self._namespaces + self._htmlattrs)) + if attrs: + return '' % attrs + return '' + def getvalue(self): """writes HTML headers, closes tag and writes HTML body""" return u'%s\n%s\n%s\n%s\n%s\n' % (self.xmldecl, self.doctype, @@ -379,7 +405,6 @@ self.head.getvalue(), self.body.getvalue()) - try: # may not be there if cubicweb-web not installed if sys.version_info < (2, 6): diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/cubicweb.css --- a/web/data/cubicweb.css Mon Apr 18 08:03:46 2011 +0200 +++ b/web/data/cubicweb.css Wed Apr 20 08:28:52 2011 +0200 @@ -993,23 +993,49 @@ background-image: none; } -/* ui.tabs.css */ -ul.ui-tabs-nav, -div.ui-tabs-panel { - font-family: %(defaultFontFamily)s; - font-size: %(defaultSize)s; +/* jquery-ui tabs */ + +div.ui-tabs.ui-widget-content { + background:none; + border:none; + color:inherit; +} + +div.ui-tabs ul.ui-tabs-nav { + padding-left: 0.5em; +} + +div.ui-tabs ul.ui-tabs-nav a { + color:#27537A; + padding: 0.3em 0.6em; +} + +div.ui-tabs ul.ui-tabs-nav li.ui-tabs-selected a { + color:black; } -div.ui-tabs-panel { - border-top:1px solid #b6b6b6; +div.ui-tabs ul.ui-tabs-nav li.ui-state-hover { + background:none; +} + +div.ui-tabs .ui-widget-header { + background:none; + border:none; } -ul.ui-tabs-nav a { - color: #3d3d3d; +div.ui-tabs .ui-widget-header li { + border-color:#333333; } -ul.ui-tabs-nav a:hover { - color: #000; +div.ui-tabs .ui-tabs-panel { + border-top:1px solid #97A5B0; + padding-left:0.5em; + color:inherit; +} + +div.ui-tabs .ui-tabs-nav, div.ui-tabs .ui-tabs-panel { + font-family: %(defaultFontFamily)s; + font-size: %(defaultSize)s; } img.ui-datepicker-trigger { diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/cubicweb.old.css --- a/web/data/cubicweb.old.css Mon Apr 18 08:03:46 2011 +0200 +++ b/web/data/cubicweb.old.css Wed Apr 20 08:28:52 2011 +0200 @@ -976,3 +976,42 @@ /* remove background image (orange bullet) for autocomplete suggestions */ background-image: none; } + +div.ui-tabs.ui-widget-content { + background:none; + border:none; + color:inherit; +} + +div.ui-tabs ul.ui-tabs-nav { + padding-left: 0.5em; +} + +div.ui-tabs ul.ui-tabs-nav a { + color:#27537A; + padding: 0.3em 0.6em; + outline:0; +} + +div.ui-tabs ul.ui-tabs-nav li.ui-tabs-selected a { + color:black; +} + +div.ui-tabs ul.ui-tabs-nav li.ui-state-hover, div.ui-tabs ul.ui-tabs-nav li.ui-state-focus { + background:white; +} + +div.ui-tabs .ui-widget-header { + background:none; + border:none; +} + +div.ui-tabs .ui-widget-header li { + border-color:#333333; +} + +div.ui-tabs .ui-tabs-panel { + border-top:1px solid #97A5B0; + padding-left:0.5em; + color:inherit; +} diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/cubicweb.reledit.js --- a/web/data/cubicweb.reledit.js Mon Apr 18 08:03:46 2011 +0200 +++ b/web/data/cubicweb.reledit.js Wed Apr 20 08:28:52 2011 +0200 @@ -18,6 +18,7 @@ cleanupAfterCancel: function (divid) { jQuery('#appMsg').hide(); jQuery('div.errorMessage').remove(); + // plus re-set inline style ? jQuery('#' + divid).show(); jQuery('#' + divid + '-value').show(); jQuery('#' + divid + '-form').hide(); @@ -63,9 +64,9 @@ * @param reload: boolean to reload page if true (when changing URL dependant data) * @param default_value : value if the field is empty */ - loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid) { + loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid, action) { var args = {fname: 'reledit_form', rtype: rtype, role: role, - pageid: pageid, + pageid: pageid, action: action, eid: eid, divid: divid, formid: formid, reload: reload, vid: vid}; var d = jQuery('#'+divid+'-reledit').loadxhtml(JSON_BASE_URL, args, 'post'); diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/jquery.corner.js --- a/web/data/jquery.corner.js Mon Apr 18 08:03:46 2011 +0200 +++ b/web/data/jquery.corner.js Wed Apr 20 08:28:52 2011 +0200 @@ -1,178 +1,247 @@ -/* - * jQuery corner plugin - * - * version 1.92 (12/18/2007) - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - */ - -/** - * The corner() method provides a simple way of styling DOM elements. - * - * corner() takes a single string argument: $().corner("effect corners width") - * - * effect: The name of the effect to apply, such as round or bevel. - * If you don't specify an effect, rounding is used. - * - * corners: The corners can be one or more of top, bottom, tr, tl, br, or bl. - * By default, all four corners are adorned. - * - * width: The width specifies the width of the effect; in the case of rounded corners this - * will be the radius of the width. - * Specify this value using the px suffix such as 10px, and yes it must be pixels. - * - * For more details see: http://methvin.com/jquery/jq-corner.html - * For a full demo see: http://malsup.com/jquery/corner/ - * - * - * @example $('.adorn').corner(); - * @desc Create round, 10px corners - * - * @example $('.adorn').corner("25px"); - * @desc Create round, 25px corners - * - * @example $('.adorn').corner("notch bottom"); - * @desc Create notched, 10px corners on bottom only - * - * @example $('.adorn').corner("tr dog 25px"); - * @desc Create dogeared, 25px corner on the top-right corner only - * - * @example $('.adorn').corner("round 8px").parent().css('padding', '4px').corner("round 10px"); - * @desc Create a rounded border effect by styling both the element and its parent - * - * @name corner - * @type jQuery - * @param String options Options which control the corner style - * @cat Plugins/Corner - * @return jQuery - * @author Dave Methvin (dave.methvin@gmail.com) - * @author Mike Alsup (malsup@gmail.com) - */ -(function($) { - -$.fn.corner = function(o) { - var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent); - function sz(el, p) { return parseInt($.css(el,p))||0; }; - function hex2(s) { - var s = parseInt(s).toString(16); - return ( s.length < 2 ) ? '0'+s : s; - }; - function gpc(node) { - for ( ; node && node.nodeName.toLowerCase() != 'html'; node = node.parentNode ) { - var v = $.css(node,'backgroundColor'); - if ( v.indexOf('rgb') >= 0 ) { - if ($.browser.safari && v == 'rgba(0, 0, 0, 0)') - continue; - var rgb = v.match(/\d+/g); - return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]); - } - if ( v && v != 'transparent' ) - return v; - } - return '#ffffff'; - }; - function getW(i) { - switch(fx) { - case 'round': return Math.round(width*(1-Math.cos(Math.asin(i/width)))); - case 'cool': return Math.round(width*(1+Math.cos(Math.asin(i/width)))); - case 'sharp': return Math.round(width*(1-Math.cos(Math.acos(i/width)))); - case 'bite': return Math.round(width*(Math.cos(Math.asin((width-i-1)/width)))); - case 'slide': return Math.round(width*(Math.atan2(i,width/i))); - case 'jut': return Math.round(width*(Math.atan2(width,(width-i-1)))); - case 'curl': return Math.round(width*(Math.atan(i))); - case 'tear': return Math.round(width*(Math.cos(i))); - case 'wicked': return Math.round(width*(Math.tan(i))); - case 'long': return Math.round(width*(Math.sqrt(i))); - case 'sculpt': return Math.round(width*(Math.log((width-i-1),width))); - case 'dog': return (i&1) ? (i+1) : width; - case 'dog2': return (i&2) ? (i+1) : width; - case 'dog3': return (i&3) ? (i+1) : width; - case 'fray': return (i%2)*width; - case 'notch': return width; - case 'bevel': return i+1; - } - }; - o = (o||"").toLowerCase(); - var keep = /keep/.test(o); // keep borders? - var cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]); // corner color - var sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]); // strip color - var width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10; // corner width - var re = /round|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dog/; - var fx = ((o.match(re)||['round'])[0]); - var edges = { T:0, B:1 }; - var opts = { - TL: /top|tl/.test(o), TR: /top|tr/.test(o), - BL: /bottom|bl/.test(o), BR: /bottom|br/.test(o) - }; - if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR ) - opts = { TL:1, TR:1, BL:1, BR:1 }; - var strip = document.createElement('div'); - strip.style.overflow = 'hidden'; - strip.style.height = '1px'; - strip.style.backgroundColor = sc || 'transparent'; - strip.style.borderStyle = 'solid'; - return this.each(function(index){ - var pad = { - T: parseInt($.css(this,'paddingTop'))||0, R: parseInt($.css(this,'paddingRight'))||0, - B: parseInt($.css(this,'paddingBottom'))||0, L: parseInt($.css(this,'paddingLeft'))||0 - }; - - if ($.browser.msie) this.style.zoom = 1; // force 'hasLayout' in IE - if (!keep) this.style.border = 'none'; - strip.style.borderColor = cc || gpc(this.parentNode); - var cssHeight = $.curCSS(this, 'height'); - - for (var j in edges) { - var bot = edges[j]; - // only add stips if needed - if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) { - strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none'); - var d = document.createElement('div'); - $(d).addClass('jquery-corner'); - var ds = d.style; - - bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild); - - if (bot && cssHeight != 'auto') { - if ($.css(this,'position') == 'static') - this.style.position = 'relative'; - ds.position = 'absolute'; - ds.bottom = ds.left = ds.padding = ds.margin = '0'; - if (($.browser.msie) && ($.browser.version < 8.0)) - ds.setExpression('width', 'this.parentNode.offsetWidth'); - else - ds.width = '100%'; - } - else if (!bot && $.browser.msie) { - if ($.css(this,'position') == 'static') - this.style.position = 'relative'; - ds.position = 'absolute'; - ds.top = ds.left = ds.right = ds.padding = ds.margin = '0'; - - // fix ie6 problem when blocked element has a border width - var bw = 0; - if (ie6 || !$.boxModel) - bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth'); - ie6 ? ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"') : ds.width = '100%'; - } - else { - ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' : - (pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px'; - } - - for (var i=0; i < width; i++) { - var w = Math.max(0,getW(i)); - var e = strip.cloneNode(false); - e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px'; - bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild); - } - } - } - }); -}; - -$.fn.uncorner = function(o) { return $('.jquery-corner', this).remove(); }; - -})(jQuery); +/*! + * jQuery corner plugin: simple corner rounding + * Examples and documentation at: http://jquery.malsup.com/corner/ + * version 2.11 (15-JUN-2010) + * Requires jQuery v1.3.2 or later + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * Authors: Dave Methvin and Mike Alsup + */ + +/** + * corner() takes a single string argument: $('#myDiv').corner("effect corners width") + * + * effect: name of the effect to apply, such as round, bevel, notch, bite, etc (default is round). + * corners: one or more of: top, bottom, tr, tl, br, or bl. (default is all corners) + * width: width of the effect; in the case of rounded corners this is the radius. + * specify this value using the px suffix such as 10px (yes, it must be pixels). + */ +;(function($) { + +var style = document.createElement('div').style, + moz = style['MozBorderRadius'] !== undefined, + webkit = style['WebkitBorderRadius'] !== undefined, + radius = style['borderRadius'] !== undefined || style['BorderRadius'] !== undefined, + mode = document.documentMode || 0, + noBottomFold = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8), + + expr = $.browser.msie && (function() { + var div = document.createElement('div'); + try { div.style.setExpression('width','0+0'); div.style.removeExpression('width'); } + catch(e) { return false; } + return true; + })(); + +$.support = $.support || {}; +$.support.borderRadius = moz || webkit || radius; // so you can do: if (!$.support.borderRadius) $('#myDiv').corner(); + +function sz(el, p) { + return parseInt($.css(el,p))||0; +}; +function hex2(s) { + var s = parseInt(s).toString(16); + return ( s.length < 2 ) ? '0'+s : s; +}; +function gpc(node) { + while(node) { + var v = $.css(node,'backgroundColor'), rgb; + if (v && v != 'transparent' && v != 'rgba(0, 0, 0, 0)') { + if (v.indexOf('rgb') >= 0) { + rgb = v.match(/\d+/g); + return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]); + } + return v; + } + if (node.nodeName.toLowerCase() == 'html') + break; + node = node.parentNode; // keep walking if transparent + } + return '#ffffff'; +}; + +function getWidth(fx, i, width) { + switch(fx) { + case 'round': return Math.round(width*(1-Math.cos(Math.asin(i/width)))); + case 'cool': return Math.round(width*(1+Math.cos(Math.asin(i/width)))); + case 'sharp': return Math.round(width*(1-Math.cos(Math.acos(i/width)))); + case 'bite': return Math.round(width*(Math.cos(Math.asin((width-i-1)/width)))); + case 'slide': return Math.round(width*(Math.atan2(i,width/i))); + case 'jut': return Math.round(width*(Math.atan2(width,(width-i-1)))); + case 'curl': return Math.round(width*(Math.atan(i))); + case 'tear': return Math.round(width*(Math.cos(i))); + case 'wicked': return Math.round(width*(Math.tan(i))); + case 'long': return Math.round(width*(Math.sqrt(i))); + case 'sculpt': return Math.round(width*(Math.log((width-i-1),width))); + case 'dogfold': + case 'dog': return (i&1) ? (i+1) : width; + case 'dog2': return (i&2) ? (i+1) : width; + case 'dog3': return (i&3) ? (i+1) : width; + case 'fray': return (i%2)*width; + case 'notch': return width; + case 'bevelfold': + case 'bevel': return i+1; + } +}; + +$.fn.corner = function(options) { + // in 1.3+ we can fix mistakes with the ready state + if (this.length == 0) { + if (!$.isReady && this.selector) { + var s = this.selector, c = this.context; + $(function() { + $(s,c).corner(options); + }); + } + return this; + } + + return this.each(function(index){ + var $this = $(this), + // meta values override options + o = [$this.attr($.fn.corner.defaults.metaAttr) || '', options || ''].join(' ').toLowerCase(), + keep = /keep/.test(o), // keep borders? + cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]), // corner color + sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]), // strip color + width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10, // corner width + re = /round|bevelfold|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dogfold|dog/, + fx = ((o.match(re)||['round'])[0]), + fold = /dogfold|bevelfold/.test(o), + edges = { T:0, B:1 }, + opts = { + TL: /top|tl|left/.test(o), TR: /top|tr|right/.test(o), + BL: /bottom|bl|left/.test(o), BR: /bottom|br|right/.test(o) + }, + // vars used in func later + strip, pad, cssHeight, j, bot, d, ds, bw, i, w, e, c, common, $horz; + + if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR ) + opts = { TL:1, TR:1, BL:1, BR:1 }; + + // support native rounding + if ($.fn.corner.defaults.useNative && fx == 'round' && (radius || moz || webkit) && !cc && !sc) { + if (opts.TL) + $this.css(radius ? 'border-top-left-radius' : moz ? '-moz-border-radius-topleft' : '-webkit-border-top-left-radius', width + 'px'); + if (opts.TR) + $this.css(radius ? 'border-top-right-radius' : moz ? '-moz-border-radius-topright' : '-webkit-border-top-right-radius', width + 'px'); + if (opts.BL) + $this.css(radius ? 'border-bottom-left-radius' : moz ? '-moz-border-radius-bottomleft' : '-webkit-border-bottom-left-radius', width + 'px'); + if (opts.BR) + $this.css(radius ? 'border-bottom-right-radius' : moz ? '-moz-border-radius-bottomright' : '-webkit-border-bottom-right-radius', width + 'px'); + return; + } + + strip = document.createElement('div'); + $(strip).css({ + overflow: 'hidden', + height: '1px', + minHeight: '1px', + fontSize: '1px', + backgroundColor: sc || 'transparent', + borderStyle: 'solid' + }); + + pad = { + T: parseInt($.css(this,'paddingTop'))||0, R: parseInt($.css(this,'paddingRight'))||0, + B: parseInt($.css(this,'paddingBottom'))||0, L: parseInt($.css(this,'paddingLeft'))||0 + }; + + if (typeof this.style.zoom != undefined) this.style.zoom = 1; // force 'hasLayout' in IE + if (!keep) this.style.border = 'none'; + strip.style.borderColor = cc || gpc(this.parentNode); + cssHeight = $(this).outerHeight(); + + for (j in edges) { + bot = edges[j]; + // only add stips if needed + if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) { + strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none'); + d = document.createElement('div'); + $(d).addClass('jquery-corner'); + ds = d.style; + + bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild); + + if (bot && cssHeight != 'auto') { + if ($.css(this,'position') == 'static') + this.style.position = 'relative'; + ds.position = 'absolute'; + ds.bottom = ds.left = ds.padding = ds.margin = '0'; + if (expr) + ds.setExpression('width', 'this.parentNode.offsetWidth'); + else + ds.width = '100%'; + } + else if (!bot && $.browser.msie) { + if ($.css(this,'position') == 'static') + this.style.position = 'relative'; + ds.position = 'absolute'; + ds.top = ds.left = ds.right = ds.padding = ds.margin = '0'; + + // fix ie6 problem when blocked element has a border width + if (expr) { + bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth'); + ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"'); + } + else + ds.width = '100%'; + } + else { + ds.position = 'relative'; + ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' : + (pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px'; + } + + for (i=0; i < width; i++) { + w = Math.max(0,getWidth(fx,i, width)); + e = strip.cloneNode(false); + e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px'; + bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild); + } + + if (fold && $.support.boxModel) { + if (bot && noBottomFold) continue; + for (c in opts) { + if (!opts[c]) continue; + if (bot && (c == 'TL' || c == 'TR')) continue; + if (!bot && (c == 'BL' || c == 'BR')) continue; + + common = { position: 'absolute', border: 'none', margin: 0, padding: 0, overflow: 'hidden', backgroundColor: strip.style.borderColor }; + $horz = $('
').css(common).css({ width: width + 'px', height: '1px' }); + switch(c) { + case 'TL': $horz.css({ bottom: 0, left: 0 }); break; + case 'TR': $horz.css({ bottom: 0, right: 0 }); break; + case 'BL': $horz.css({ top: 0, left: 0 }); break; + case 'BR': $horz.css({ top: 0, right: 0 }); break; + } + d.appendChild($horz[0]); + + var $vert = $('
').css(common).css({ top: 0, bottom: 0, width: '1px', height: width + 'px' }); + switch(c) { + case 'TL': $vert.css({ left: width }); break; + case 'TR': $vert.css({ right: width }); break; + case 'BL': $vert.css({ left: width }); break; + case 'BR': $vert.css({ right: width }); break; + } + d.appendChild($vert[0]); + } + } + } + } + }); +}; + +$.fn.uncorner = function() { + if (radius || moz || webkit) + this.css(radius ? 'border-radius' : moz ? '-moz-border-radius' : '-webkit-border-radius', 0); + $('div.jquery-corner', this).remove(); + return this; +}; + +// expose options +$.fn.corner.defaults = { + useNative: true, // true if plugin should attempt to use native browser support for border radius rounding + metaAttr: 'data-corner' // name of meta attribute to use for options +}; + +})(jQuery); diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/tab.png Binary file web/data/tab.png has changed diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/ui.core.js --- a/web/data/ui.core.js Mon Apr 18 08:03:46 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,431 +0,0 @@ -/* - * jQuery UI @VERSION - * - * Copyright (c) 2010 Paul Bakaus (ui.jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * http://docs.jquery.com/UI - */ -;(function($) { - -/** jQuery core modifications and additions **/ - -var _remove = $.fn.remove; -$.fn.remove = function() { - $("*", this).add(this).triggerHandler("remove"); - return _remove.apply(this, arguments ); -}; - -function isVisible(element) { - function checkStyles(element) { - var style = element.style; - return (style.display != 'none' && style.visibility != 'hidden'); - } - - var visible = checkStyles(element); - - (visible && $.each($.dir(element, 'parentNode'), function() { - return (visible = checkStyles(this)); - })); - - return visible; -} - -$.extend($.expr[':'], { - data: function(a, i, m) { - return $.data(a, m[3]); - }, - - // TODO: add support for object, area - tabbable: function(a, i, m) { - var nodeName = a.nodeName.toLowerCase(); - - return ( - // in tab order - a.tabIndex >= 0 && - - ( // filter node types that participate in the tab order - - // anchor tag - ('a' == nodeName && a.href) || - - // enabled form element - (/input|select|textarea|button/.test(nodeName) && - 'hidden' != a.type && !a.disabled) - ) && - - // visible on page - isVisible(a) - ); - } -}); - -$.keyCode = { - BACKSPACE: 8, - CAPS_LOCK: 20, - COMMA: 188, - CONTROL: 17, - DELETE: 46, - DOWN: 40, - END: 35, - ENTER: 13, - ESCAPE: 27, - HOME: 36, - INSERT: 45, - LEFT: 37, - NUMPAD_ADD: 107, - NUMPAD_DECIMAL: 110, - NUMPAD_DIVIDE: 111, - NUMPAD_ENTER: 108, - NUMPAD_MULTIPLY: 106, - NUMPAD_SUBTRACT: 109, - PAGE_DOWN: 34, - PAGE_UP: 33, - PERIOD: 190, - RIGHT: 39, - SHIFT: 16, - SPACE: 32, - TAB: 9, - UP: 38 -}; - -// $.widget is a factory to create jQuery plugins -// taking some boilerplate code out of the plugin code -// created by Scott González and Jörn Zaefferer -function getter(namespace, plugin, method, args) { - function getMethods(type) { - var methods = $[namespace][plugin][type] || []; - return (typeof methods == 'string' ? methods.split(/,?\s+/) : methods); - } - - var methods = getMethods('getter'); - if (args.length == 1 && typeof args[0] == 'string') { - methods = methods.concat(getMethods('getterSetter')); - } - return ($.inArray(method, methods) != -1); -} - -$.widget = function(name, prototype) { - var namespace = name.split(".")[0]; - name = name.split(".")[1]; - - // create plugin method - $.fn[name] = function(options) { - var isMethodCall = (typeof options == 'string'), - args = Array.prototype.slice.call(arguments, 1); - - // prevent calls to internal methods - if (isMethodCall && options.substring(0, 1) == '_') { - return this; - } - - // handle getter methods - if (isMethodCall && getter(namespace, name, options, args)) { - var instance = $.data(this[0], name); - return (instance ? instance[options].apply(instance, args) - : undefined); - } - - // handle initialization and non-getter methods - return this.each(function() { - var instance = $.data(this, name); - - // constructor - (!instance && !isMethodCall && - $.data(this, name, new $[namespace][name](this, options))); - - // method call - (instance && isMethodCall && $.isFunction(instance[options]) && - instance[options].apply(instance, args)); - }); - }; - - // create widget constructor - $[namespace][name] = function(element, options) { - var self = this; - - this.widgetName = name; - this.widgetEventPrefix = $[namespace][name].eventPrefix || name; - this.widgetBaseClass = namespace + '-' + name; - - this.options = $.extend({}, - $.widget.defaults, - $[namespace][name].defaults, - $.metadata && $.metadata.get(element)[name], - options); - - this.element = $(element) - .bind('setData.' + name, function(e, key, value) { - return self._setData(key, value); - }) - .bind('getData.' + name, function(e, key) { - return self._getData(key); - }) - .bind('remove', function() { - return self.destroy(); - }); - - this._init(); - }; - - // add widget prototype - $[namespace][name].prototype = $.extend({}, $.widget.prototype, prototype); - - // TODO: merge getter and getterSetter properties from widget prototype - // and plugin prototype - $[namespace][name].getterSetter = 'option'; -}; - -$.widget.prototype = { - _init: function() {}, - destroy: function() { - this.element.removeData(this.widgetName); - }, - - option: function(key, value) { - var options = key, - self = this; - - if (typeof key == "string") { - if (value === undefined) { - return this._getData(key); - } - options = {}; - options[key] = value; - } - - $.each(options, function(key, value) { - self._setData(key, value); - }); - }, - _getData: function(key) { - return this.options[key]; - }, - _setData: function(key, value) { - this.options[key] = value; - - if (key == 'disabled') { - this.element[value ? 'addClass' : 'removeClass']( - this.widgetBaseClass + '-disabled'); - } - }, - - enable: function() { - this._setData('disabled', false); - }, - disable: function() { - this._setData('disabled', true); - }, - - _trigger: function(type, e, data) { - var eventName = (type == this.widgetEventPrefix - ? type : this.widgetEventPrefix + type); - e = e || $.event.fix({ type: eventName, target: this.element[0] }); - return this.element.triggerHandler(eventName, [e, data], this.options[type]); - } -}; - -$.widget.defaults = { - disabled: false -}; - - -/** jQuery UI core **/ - -$.ui = { - plugin: { - add: function(module, option, set) { - var proto = $.ui[module].prototype; - for(var i in set) { - proto.plugins[i] = proto.plugins[i] || []; - proto.plugins[i].push([option, set[i]]); - } - }, - call: function(instance, name, args) { - var set = instance.plugins[name]; - if(!set) { return; } - - for (var i = 0; i < set.length; i++) { - if (instance.options[set[i][0]]) { - set[i][1].apply(instance.element, args); - } - } - } - }, - cssCache: {}, - css: function(name) { - if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; } - var tmp = $('
').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body'); - - //if (!$.browser.safari) - //tmp.appendTo('body'); - - //Opera and Safari set width and height to 0px instead of auto - //Safari returns rgba(0,0,0,0) when bgcolor is not set - $.ui.cssCache[name] = !!( - (!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) || - !(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor'))) - ); - try { $('body').get(0).removeChild(tmp.get(0)); } catch(e){} - return $.ui.cssCache[name]; - }, - disableSelection: function(el) { - $(el) - .attr('unselectable', 'on') - .css('MozUserSelect', 'none') - .bind('selectstart.ui', function() { return false; }); - }, - enableSelection: function(el) { - $(el) - .attr('unselectable', 'off') - .css('MozUserSelect', '') - .unbind('selectstart.ui'); - }, - hasScroll: function(e, a) { - var scroll = (a && a == 'left') ? 'scrollLeft' : 'scrollTop', - has = false; - - if (e[scroll] > 0) { return true; } - - // TODO: determine which cases actually cause this to happen - // if the element doesn't have the scroll set, see if it's possible to - // set the scroll - e[scroll] = 1; - has = (e[scroll] > 0); - e[scroll] = 0; - return has; - } -}; - - -/** Mouse Interaction Plugin **/ - -$.ui.mouse = { - _mouseInit: function() { - var self = this; - - this.element.bind('mousedown.'+this.widgetName, function(e) { - return self._mouseDown(e); - }); - - // Prevent text selection in IE - if ($.browser.msie) { - this._mouseUnselectable = this.element.attr('unselectable'); - this.element.attr('unselectable', 'on'); - } - - this.started = false; - }, - - // TODO: make sure destroying one instance of mouse doesn't mess with - // other instances of mouse - _mouseDestroy: function() { - this.element.unbind('.'+this.widgetName); - - // Restore text selection in IE - ($.browser.msie - && this.element.attr('unselectable', this._mouseUnselectable)); - }, - - _mouseDown: function(e) { - // we may have missed mouseup (out of window) - (this._mouseStarted && this._mouseUp(e)); - - this._mouseDownEvent = e; - - var self = this, - btnIsLeft = (e.which == 1), - elIsCancel = (typeof this.options.cancel == "string" ? $(e.target).parents().add(e.target).filter(this.options.cancel).length : false); - if (!btnIsLeft || elIsCancel || !this._mouseCapture(e)) { - return true; - } - - this.mouseDelayMet = !this.options.delay; - if (!this.mouseDelayMet) { - this._mouseDelayTimer = setTimeout(function() { - self.mouseDelayMet = true; - }, this.options.delay); - } - - if (this._mouseDistanceMet(e) && this._mouseDelayMet(e)) { - this._mouseStarted = (this._mouseStart(e) !== false); - if (!this._mouseStarted) { - e.preventDefault(); - return true; - } - } - - // these delegates are required to keep context - this._mouseMoveDelegate = function(e) { - return self._mouseMove(e); - }; - this._mouseUpDelegate = function(e) { - return self._mouseUp(e); - }; - $(document) - .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate) - .bind('mouseup.'+this.widgetName, this._mouseUpDelegate); - - return false; - }, - - _mouseMove: function(e) { - // IE mouseup check - mouseup happened when mouse was out of window - if ($.browser.msie && !e.button) { - return this._mouseUp(e); - } - - if (this._mouseStarted) { - this._mouseDrag(e); - return false; - } - - if (this._mouseDistanceMet(e) && this._mouseDelayMet(e)) { - this._mouseStarted = - (this._mouseStart(this._mouseDownEvent, e) !== false); - (this._mouseStarted ? this._mouseDrag(e) : this._mouseUp(e)); - } - - return !this._mouseStarted; - }, - - _mouseUp: function(e) { - $(document) - .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) - .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); - - if (this._mouseStarted) { - this._mouseStarted = false; - this._mouseStop(e); - } - - return false; - }, - - _mouseDistanceMet: function(e) { - return (Math.max( - Math.abs(this._mouseDownEvent.pageX - e.pageX), - Math.abs(this._mouseDownEvent.pageY - e.pageY) - ) >= this.options.distance - ); - }, - - _mouseDelayMet: function(e) { - return this.mouseDelayMet; - }, - - // These are placeholder methods, to be overriden by extending plugin - _mouseStart: function(e) {}, - _mouseDrag: function(e) {}, - _mouseStop: function(e) {}, - _mouseCapture: function(e) { return true; } -}; - -$.ui.mouse.defaults = { - cancel: null, - distance: 1, - delay: 0 -}; - -})(jQuery); diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/ui.slider.js --- a/web/data/ui.slider.js Mon Apr 18 08:03:46 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -/* - * jQuery UI 1.7.1 - * - * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * http://docs.jquery.com/UI - */ jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.1",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/* - * jQuery UI Draggable 1.7.1 - * - * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * http://docs.jquery.com/UI/Draggables - * - * Depends: - * ui.core.js - */ (function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.leftthis.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.topthis.containment[3])?g:(!(g-this.offset.click.topthis.containment[2])?f:(!(f-this.offset.click.left
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y=p&&n<=k)||(m>=p&&m<=k)||(nk))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(ec));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d
");if(!c.values){c.values=[this._valueMin(),this._valueMin()]}if(c.values.length&&c.values.length!=2){c.values=[c.values[0],c.values[0]]}}else{this.range=a("
")}this.range.appendTo(this.element).addClass("ui-slider-range");if(c.range=="min"||c.range=="max"){this.range.addClass("ui-slider-range-"+c.range)}this.range.addClass("ui-widget-header")}if(a(".ui-slider-handle",this.element).length==0){a('').appendTo(this.element).addClass("ui-slider-handle")}if(c.values&&c.values.length){while(a(".ui-slider-handle",this.element).length').appendTo(this.element).addClass("ui-slider-handle")}}this.handles=a(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(d){d.preventDefault()}).hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(d){a(this).data("index.ui-slider-handle",d)});this.handles.keydown(function(i){var f=true;var e=a(this).data("index.ui-slider-handle");if(b.options.disabled){return}switch(i.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!b._keySliding){b._keySliding=true;a(this).addClass("ui-state-active");b._start(i,e)}break}var g,d,h=b._step();if(b.options.values&&b.options.values.length){g=d=b.values(e)}else{g=d=b.value()}switch(i.keyCode){case a.ui.keyCode.HOME:d=b._valueMin();break;case a.ui.keyCode.END:d=b._valueMax();break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g==b._valueMax()){return}d=g+h;break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g==b._valueMin()){return}d=g-h;break}b._slide(i,e,d);return f}).keyup(function(e){var d=a(this).data("index.ui-slider-handle");if(b._keySliding){b._stop(e,d);b._change(e,d);b._keySliding=false;a(this).removeClass("ui-state-active")}});this._refreshValue()},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy()},_mouseCapture:function(d){var e=this.options;if(e.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();var h={x:d.pageX,y:d.pageY};var j=this._normValueFromMouse(h);var c=this._valueMax()-this._valueMin()+1,f;var k=this,i;this.handles.each(function(l){var m=Math.abs(j-k.values(l));if(c>m){c=m;f=a(this);i=l}});if(e.range==true&&this.values(1)==e.min){f=a(this.handles[++i])}this._start(d,i);k._handleIndex=i;f.addClass("ui-state-active").focus();var g=f.offset();var b=!a(d.target).parents().andSelf().is(".ui-slider-handle");this._clickOffset=b?{left:0,top:0}:{left:d.pageX-g.left-(f.width()/2),top:d.pageY-g.top-(f.height()/2)-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};j=this._normValueFromMouse(h);this._slide(d,i,j);return true},_mouseStart:function(b){return true},_mouseDrag:function(d){var b={x:d.pageX,y:d.pageY};var c=this._normValueFromMouse(b);this._slide(d,this._handleIndex,c);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._handleIndex=null;this._clickOffset=null;return false},_detectOrientation:function(){this.orientation=this.options.orientation=="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c,h;if("horizontal"==this.orientation){c=this.elementSize.width;h=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;h=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}var f=(h/c);if(f>1){f=1}if(f<0){f=0}if("vertical"==this.orientation){f=1-f}var e=this._valueMax()-this._valueMin(),i=f*e,b=i%this.options.step,g=this._valueMin()+i-b;if(b>(this.options.step/2)){g+=this.options.step}return parseFloat(g.toFixed(5))},_start:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("start",d,b)},_slide:function(f,e,d){var g=this.handles[e];if(this.options.values&&this.options.values.length){var b=this.values(e?0:1);if((e==0&&d>=b)||(e==1&&d<=b)){d=b}if(d!=this.values(e)){var c=this.values();c[e]=d;var h=this._trigger("slide",f,{handle:this.handles[e],value:d,values:c});var b=this.values(e?0:1);if(h!==false){this.values(e,d,(f.type=="mousedown"&&this.options.animate),true)}}}else{if(d!=this.value()){var h=this._trigger("slide",f,{handle:this.handles[e],value:d});if(h!==false){this._setData("value",d,(f.type=="mousedown"&&this.options.animate))}}}},_stop:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("stop",d,b)},_change:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("change",d,b)},value:function(b){if(arguments.length){this._setData("value",b);this._change(null,0)}return this._value()},values:function(b,e,c,d){if(arguments.length>1){this.options.values[b]=e;this._refreshValue(c);if(!d){this._change(null,b)}}if(arguments.length){if(this.options.values&&this.options.values.length){return this._values(b)}else{return this.value()}}else{return this._values()}},_setData:function(b,d,c){a.widget.prototype._setData.apply(this,arguments);switch(b){case"orientation":this._detectOrientation();this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue(c);break;case"value":this._refreshValue(c);break}},_step:function(){var b=this.options.step;return b},_value:function(){var b=this.options.value;if(bthis._valueMax()){b=this._valueMax()}return b},_values:function(b){if(arguments.length){var c=this.options.values[b];if(cthis._valueMax()){c=this._valueMax()}return c}else{return this.options.values}},_valueMin:function(){var b=this.options.min;return b},_valueMax:function(){var b=this.options.max;return b},_refreshValue:function(c){var f=this.options.range,d=this.options,l=this;if(this.options.values&&this.options.values.length){var i,h;this.handles.each(function(p,n){var o=(l.values(p)-l._valueMin())/(l._valueMax()-l._valueMin())*100;var m={};m[l.orientation=="horizontal"?"left":"bottom"]=o+"%";a(this).stop(1,1)[c?"animate":"css"](m,d.animate);if(l.options.range===true){if(l.orientation=="horizontal"){(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({left:o+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({width:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}else{(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({bottom:(o)+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({height:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}}lastValPercent=o})}else{var j=this.value(),g=this._valueMin(),k=this._valueMax(),e=k!=g?(j-g)/(k-g)*100:0;var b={};b[l.orientation=="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[c?"animate":"css"](b,d.animate);(f=="min")&&(this.orientation=="horizontal")&&this.range.stop(1,1)[c?"animate":"css"]({width:e+"%"},d.animate);(f=="max")&&(this.orientation=="horizontal")&&this.range[c?"animate":"css"]({width:(100-e)+"%"},{queue:false,duration:d.animate});(f=="min")&&(this.orientation=="vertical")&&this.range.stop(1,1)[c?"animate":"css"]({height:e+"%"},d.animate);(f=="max")&&(this.orientation=="vertical")&&this.range[c?"animate":"css"]({height:(100-e)+"%"},{queue:false,duration:d.animate})}}}));a.extend(a.ui.slider,{getter:"value values",version:"1.7.1",eventPrefix:"slide",defaults:{animate:false,delay:0,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null}})})(jQuery);; \ No newline at end of file diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/ui.tabs.css --- a/web/data/ui.tabs.css Mon Apr 18 08:03:46 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/* Caution! Ensure accessibility in print and other media types... */ -@media projection, screen { /* Use class for showing/hiding tab content, so that visibility can be better controlled in different media types... */ - .ui-tabs-hide { - display: none; - } -} - -/* Hide useless elements in print layouts... */ -@media print { - .ui-tabs-nav { - display: none; - } -} - -/* Skin */ -.ui-tabs-nav, .ui-tabs-panel { - font-family: "Trebuchet MS", Trebuchet, Verdana, Helvetica, Arial, sans-serif; - font-size: 12px; - -} -.ui-tabs-nav { - list-style: none; - margin: 0px; - padding: 0px 0px 0px 4px; - -} -.ui-tabs-nav:after { /* clearing without presentational markup, IE gets extra treatment */ - display: block; - clear: both; - content: " "; -} -.ui-tabs-nav li { - float: left; - margin: 0 0 0 1px; - min-width: 84px; /* be nice to Opera */ - list-style: none; - background: none; - padding: 0px 0px 1px 1px; -} -.ui-tabs-nav a, .ui-tabs-nav a span { - display: block; - padding: 0 10px; - background: url(tab.png) no-repeat; -} -.ui-tabs-nav a { - margin: 1px 0 0; /* position: relative makes opacity fail for disabled tab in IE */ - padding-left: 0; - color: #27537a; - font-weight: bold; - line-height: 1.2; - text-align: center; - text-decoration: none; - white-space: nowrap; /* required in IE 6 */ - outline: 0; /* prevent dotted border in Firefox */ -} -.ui-tabs-nav .ui-tabs-selected a { - position: relative; - top: 1px; - z-index: 2; - margin-top: 0; - color: #000; -} -.ui-tabs-nav a span { - width: 64px; /* IE 6 treats width as min-width */ - min-width: 64px; - height: 18px; /* IE 6 treats height as min-height */ - min-height: 18px; - padding-top: 6px; - padding-right: 0; -} -*>.ui-tabs-nav a span { /* hide from IE 6 */ - width: auto; - height: auto; -} -.ui-tabs-nav .ui-tabs-selected a span { - padding-bottom: 1px; -} -.ui-tabs-nav .ui-tabs-selected a, .ui-tabs-nav a:hover, .ui-tabs-nav a:focus, .ui-tabs-nav a:active { - background-position: 100% -150px; -} -.ui-tabs-nav a, .ui-tabs-nav .ui-tabs-disabled a:hover, .ui-tabs-nav .ui-tabs-disabled a:focus, .ui-tabs-nav .ui-tabs-disabled a:active { - background-position: 100% -100px; -} -.ui-tabs-nav .ui-tabs-selected a span, .ui-tabs-nav a:hover span, .ui-tabs-nav a:focus span, .ui-tabs-nav a:active span { - background-position: 0 -50px; -} -.ui-tabs-nav a span, .ui-tabs-nav .ui-tabs-disabled a:hover span, .ui-tabs-nav .ui-tabs-disabled a:focus span, .ui-tabs-nav .ui-tabs-disabled a:active span { - background-position: 0 0; -} -.ui-tabs-nav .ui-tabs-selected a:link, .ui-tabs-nav .ui-tabs-selected a:visited, .ui-tabs-nav .ui-tabs-disabled a:link, .ui-tabs-nav .ui-tabs-disabled a:visited { /* @ Opera, use pseudo classes otherwise it confuses cursor... */ - cursor: text; -} -.ui-tabs-nav a:hover, .ui-tabs-nav a:focus, .ui-tabs-nav a:active, -.ui-tabs-nav .ui-tabs-unselect a:hover, .ui-tabs-nav .ui-tabs-unselect a:focus, .ui-tabs-nav .ui-tabs-unselect a:active { /* @ Opera, we need to be explicit again here now... */ - cursor: pointer; -} -.ui-tabs-disabled { - opacity: .4; - filter: alpha(opacity=40); -} -.ui-tabs-panel { - border-top: 1px solid #97a5b0; - padding: 1em 8px; - margin-top:-1px; /* Logilab style */ - background: #fff; /* declare background color for container to avoid distorted fonts in IE while fading */ -} -.ui-tabs-loading em { - padding: 0 0 0 20px; - background: url(loading.gif) no-repeat 0 50%; -} - -/* Additional IE specific bug fixes... */ -* html .ui-tabs-nav { /* auto clear, @ IE 6 & IE 7 Quirks Mode */ - display: inline-block; -} -*:first-child+html .ui-tabs-nav { /* @ IE 7 Standards Mode - do not group selectors, otherwise IE 6 will ignore complete rule (because of the unknown + combinator)... */ - display: inline-block; -} - -/* ========= Lobilab styles =========== */ - -/* added by katia */ -* html .ui-tabs-panel{ - width:100%; -} \ No newline at end of file diff -r 506a0af77cee -r 5a0ee8de7f06 web/data/ui.tabs.js --- a/web/data/ui.tabs.js Mon Apr 18 08:03:46 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,587 +0,0 @@ -/* - * jQuery UI Tabs @VERSION - * - * Copyright (c) 2007, 2010 Klaus Hartl (stilbuero.de) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * http://docs.jquery.com/UI/Tabs - * - * Depends: - * ui.core.js - */ -(function($) { - -$.widget("ui.tabs", { - _init: function() { - this.options.event += '.tabs'; // namespace event - - // create tabs - this._tabify(true); - }, - _setData: function(key, value) { - if ((/^selected/).test(key)) - this.select(value); - else { - this.options[key] = value; - this._tabify(); - } - }, - length: function() { - return this.$tabs.length; - }, - _tabId: function(a) { - return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '') - || this.options.idPrefix + $.data(a); - }, - ui: function(tab, panel) { - return { - options: this.options, - tab: tab, - panel: panel, - index: this.$tabs.index(tab) - }; - }, - _tabify: function(init) { - - this.$lis = $('li:has(a[href])', this.element); - this.$tabs = this.$lis.map(function() { return $('a', this)[0]; }); - this.$panels = $([]); - - var self = this, o = this.options; - - this.$tabs.each(function(i, a) { - // inline tab - if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash - self.$panels = self.$panels.add(a.hash); - // remote tab - else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#" - $.data(a, 'href.tabs', a.href); // required for restore on destroy - $.data(a, 'load.tabs', a.href); // mutable - var id = self._tabId(a); - a.href = '#' + id; - var $panel = $('#' + id); - if (!$panel.length) { - $panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass) - .insertAfter( self.$panels[i - 1] || self.element ); - $panel.data('destroy.tabs', true); - } - self.$panels = self.$panels.add( $panel ); - } - // invalid tab href - else - o.disabled.push(i + 1); - }); - - // initialization from scratch - if (init) { - - // attach necessary classes for styling if not present - this.element.addClass(o.navClass); - this.$panels.each(function() { - var $this = $(this); - $this.addClass(o.panelClass); - }); - - // Selected tab - // use "selected" option or try to retrieve: - // 1. from fragment identifier in url - // 2. from cookie - // 3. from selected class attribute on
  • - if (o.selected === undefined) { - if (location.hash) { - this.$tabs.each(function(i, a) { - if (a.hash == location.hash) { - o.selected = i; - // prevent page scroll to fragment - if ($.browser.msie || $.browser.opera) { // && !o.remote - var $toShow = $(location.hash), toShowId = $toShow.attr('id'); - $toShow.attr('id', ''); - setTimeout(function() { - $toShow.attr('id', toShowId); // restore id - }, 500); - } - scrollTo(0, 0); - return false; // break - } - }); - } - else if (o.cookie) { - var index = parseInt($.cookie('ui-tabs-' + $.data(self.element[0])), 10); - if (index && self.$tabs[index]) - o.selected = index; - } - else if (self.$lis.filter('.' + o.selectedClass).length) - o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] ); - } - o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default - - // Take disabling tabs via class attribute from HTML - // into account and update option properly. - // A selected tab cannot become disabled. - o.disabled = $.unique(o.disabled.concat( - $.map(this.$lis.filter('.' + o.disabledClass), - function(n, i) { return self.$lis.index(n); } ) - )).sort(); - if ($.inArray(o.selected, o.disabled) != -1) - o.disabled.splice($.inArray(o.selected, o.disabled), 1); - - // highlight selected tab - this.$panels.addClass(o.hideClass); - this.$lis.removeClass(o.selectedClass); - if (o.selected !== null) { - this.$panels.eq(o.selected).show().removeClass(o.hideClass); // use show and remove class to show in any case no matter how it has been hidden before - this.$lis.eq(o.selected).addClass(o.selectedClass); - - // seems to be expected behavior that the show callback is fired - var onShow = function() { - self._trigger('show', null, - self.ui(self.$tabs[o.selected], self.$panels[o.selected])); - }; - - // load if remote tab - if ($.data(this.$tabs[o.selected], 'load.tabs')) - this.load(o.selected, onShow); - // just trigger show event - else - onShow(); - } - - // clean up to avoid memory leaks in certain versions of IE 6 - $(window).bind('unload', function() { - self.$tabs.unbind('.tabs'); - self.$lis = self.$tabs = self.$panels = null; - }); - - } - // update selected after add/remove - else - o.selected = this.$lis.index( this.$lis.filter('.' + o.selectedClass)[0] ); - - // set or update cookie after init and add/remove respectively - if (o.cookie) - $.cookie('ui-tabs-' + $.data(self.element[0]), o.selected, o.cookie); - - // disable tabs - for (var i = 0, li; li = this.$lis[i]; i++) - $(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass); - - // reset cache if switching from cached to not cached - if (o.cache === false) - this.$tabs.removeData('cache.tabs'); - - // set up animations - var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal'; - if (o.fx && o.fx.constructor == Array) - hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx; - else - hideFx = showFx = o.fx || baseFx; - - // reset some styles to maintain print style sheets etc. - var resetCSS = { display: '', overflow: '', height: '' }; - if (!$.browser.msie) // not in IE to prevent ClearType font issue - resetCSS.opacity = ''; - - // Hide a tab, animation prevents browser scrolling to fragment, - // $show is optional. - function hideTab(clicked, $hide, $show) { - $hide.animate(hideFx, hideFx.duration || baseDuration, function() { // - $hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc. - if ($.browser.msie && hideFx.opacity) - $hide[0].style.filter = ''; - if ($show) - showTab(clicked, $show, $hide); - }); - } - - // Show a tab, animation prevents browser scrolling to fragment, - // $hide is optional. - function showTab(clicked, $show, $hide) { - if (showFx === baseFx) - $show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels - $show.animate(showFx, showFx.duration || baseDuration, function() { - $show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc. - if ($.browser.msie && showFx.opacity) - $show[0].style.filter = ''; - - // callback - self._trigger('show', null, self.ui(clicked, $show[0])); - }); - } - - // switch a tab - function switchTab(clicked, $li, $hide, $show) { - /*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click - $.ajaxHistory.update(clicked.hash); - }*/ - $li.addClass(o.selectedClass) - .siblings().removeClass(o.selectedClass); - hideTab(clicked, $hide, $show); - } - - // attach tab event handler, unbind to avoid duplicates from former tabifying... - this.$tabs.unbind('.tabs').bind(o.event, function() { - - //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click - var $li = $(this).parents('li:eq(0)'), - $hide = self.$panels.filter(':visible'), - $show = $(this.hash); - - // If tab is already selected and not unselectable or tab disabled or - // or is already loading or click callback returns false stop here. - // Check if click handler returns false last so that it is not executed - // for a disabled or loading tab! - if (($li.hasClass(o.selectedClass) && !o.unselect) - || $li.hasClass(o.disabledClass) - || $(this).hasClass(o.loadingClass) - || self._trigger('select', null, self.ui(this, $show[0])) === false - ) { - this.blur(); - return false; - } - - self.options.selected = self.$tabs.index(this); - - // if tab may be closed - if (o.unselect) { - if ($li.hasClass(o.selectedClass)) { - self.options.selected = null; - $li.removeClass(o.selectedClass); - self.$panels.stop(); - hideTab(this, $hide); - this.blur(); - return false; - } else if (!$hide.length) { - self.$panels.stop(); - var a = this; - self.load(self.$tabs.index(this), function() { - $li.addClass(o.selectedClass).addClass(o.unselectClass); - showTab(a, $show); - }); - this.blur(); - return false; - } - } - - if (o.cookie) - $.cookie('ui-tabs-' + $.data(self.element[0]), self.options.selected, o.cookie); - - // stop possibly running animations - self.$panels.stop(); - - // show new tab - if ($show.length) { - - // prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled - /*if ($.browser.msie && o.bookmarkable) { - var showId = this.hash.replace('#', ''); - $show.attr('id', ''); - setTimeout(function() { - $show.attr('id', showId); // restore id - }, 0); - }*/ - - var a = this; - self.load(self.$tabs.index(this), $hide.length ? - function() { - switchTab(a, $li, $hide, $show); - } : - function() { - $li.addClass(o.selectedClass); - showTab(a, $show); - } - ); - - // Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash - /*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0; - var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0; - setTimeout(function() { - scrollTo(scrollX, scrollY); - }, 0);*/ - - } else - throw 'jQuery UI Tabs: Mismatching fragment identifier.'; - - // Prevent IE from keeping other link focussed when using the back button - // and remove dotted border from clicked link. This is controlled in modern - // browsers via CSS, also blur removes focus from address bar in Firefox - // which can become a usability and annoying problem with tabsRotate. - if ($.browser.msie) - this.blur(); - - //return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE - return false; - - }); - - // disable click if event is configured to something else - if (!(/^click/).test(o.event)) - this.$tabs.bind('click.tabs', function() { return false; }); - - }, - add: function(url, label, index) { - if (index == undefined) - index = this.$tabs.length; // append by default - - var o = this.options; - var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)); - $li.data('destroy.tabs', true); - - var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( $('a:first-child', $li)[0] ); - - // try to find an existing element before creating a new one - var $panel = $('#' + id); - if (!$panel.length) { - $panel = $(o.panelTemplate).attr('id', id) - .addClass(o.hideClass) - .data('destroy.tabs', true); - } - $panel.addClass(o.panelClass); - if (index >= this.$lis.length) { - $li.appendTo(this.element); - $panel.appendTo(this.element[0].parentNode); - } else { - $li.insertBefore(this.$lis[index]); - $panel.insertBefore(this.$panels[index]); - } - - o.disabled = $.map(o.disabled, - function(n, i) { return n >= index ? ++n : n }); - - this._tabify(); - - if (this.$tabs.length == 1) { - $li.addClass(o.selectedClass); - $panel.removeClass(o.hideClass); - var href = $.data(this.$tabs[0], 'load.tabs'); - if (href) - this.load(index, href); - } - - // callback - this._trigger('add', null, this.ui(this.$tabs[index], this.$panels[index])); - }, - remove: function(index) { - var o = this.options, $li = this.$lis.eq(index).remove(), - $panel = this.$panels.eq(index).remove(); - - // If selected tab was removed focus tab to the right or - // in case the last tab was removed the tab to the left. - if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1) - this.select(index + (index + 1 < this.$tabs.length ? 1 : -1)); - - o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }), - function(n, i) { return n >= index ? --n : n }); - - this._tabify(); - - // callback - this._trigger('remove', null, this.ui($li.find('a')[0], $panel[0])); - }, - enable: function(index) { - var o = this.options; - if ($.inArray(index, o.disabled) == -1) - return; - - var $li = this.$lis.eq(index).removeClass(o.disabledClass); - if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2... - $li.css('display', 'inline-block'); - setTimeout(function() { - $li.css('display', 'block'); - }, 0); - } - - o.disabled = $.grep(o.disabled, function(n, i) { return n != index; }); - - // callback - this._trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index])); - }, - disable: function(index) { - var self = this, o = this.options; - if (index != o.selected) { // cannot disable already selected tab - this.$lis.eq(index).addClass(o.disabledClass); - - o.disabled.push(index); - o.disabled.sort(); - - // callback - this._trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index])); - } - }, - select: function(index) { - if (typeof index == 'string') - index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] ); - this.$tabs.eq(index).trigger(this.options.event); - }, - load: function(index, callback) { // callback is for internal usage only - - var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0], - bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs'); - - callback = callback || function() {}; - - // no remote or from cache - just finish with callback - if (!url || !bypassCache && $.data(a, 'cache.tabs')) { - callback(); - return; - } - - // load remote from here on - - var inner = function(parent) { - var $parent = $(parent), $inner = $parent.find('*:last'); - return $inner.length && $inner.is(':not(img)') && $inner || $parent; - }; - var cleanup = function() { - self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass) - .each(function() { - if (o.spinner) - inner(this).parent().html(inner(this).data('label.tabs')); - }); - self.xhr = null; - }; - - if (o.spinner) { - var label = inner(a).html(); - inner(a).wrapInner('') - .find('em').data('label.tabs', label).html(o.spinner); - } - - var ajaxOptions = $.extend({}, o.ajaxOptions, { - url: url, - success: function(r, s) { - $(a.hash).html(r); - cleanup(); - - if (o.cache) - $.data(a, 'cache.tabs', true); // if loaded once do not load them again - - // callbacks - self._trigger('load', null, self.ui(self.$tabs[index], self.$panels[index])); - o.ajaxOptions.success && o.ajaxOptions.success(r, s); - - // This callback is required because the switch has to take - // place after loading has completed. Call last in order to - // fire load before show callback... - callback(); - } - }); - if (this.xhr) { - // terminate pending requests from other tabs and restore tab label - this.xhr.abort(); - cleanup(); - } - $a.addClass(o.loadingClass); - setTimeout(function() { // timeout is again required in IE, "wait" for id being restored - self.xhr = $.ajax(ajaxOptions); - }, 0); - - }, - url: function(index, url) { - this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url); - }, - destroy: function() { - var o = this.options; - this.element.unbind('.tabs') - .removeClass(o.navClass).removeData('tabs'); - this.$tabs.each(function() { - var href = $.data(this, 'href.tabs'); - if (href) - this.href = href; - var $this = $(this).unbind('.tabs'); - $.each(['href', 'load', 'cache'], function(i, prefix) { - $this.removeData(prefix + '.tabs'); - }); - }); - this.$lis.add(this.$panels).each(function() { - if ($.data(this, 'destroy.tabs')) - $(this).remove(); - else - $(this).removeClass([o.selectedClass, o.unselectClass, - o.disabledClass, o.panelClass, o.hideClass].join(' ')); - }); - } -}); - -$.ui.tabs.defaults = { - // basic setup - unselect: false, - event: 'click', - disabled: [], - cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } - // TODO history: false, - - // Ajax - spinner: 'Loading…', - cache: false, - idPrefix: 'ui-tabs-', - ajaxOptions: {}, - - // animations - fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } - - // templates - tabTemplate: '
  • #{label}
  • ', - panelTemplate: '
    ', - - // CSS classes - navClass: 'ui-tabs-nav', - selectedClass: 'ui-tabs-selected', - unselectClass: 'ui-tabs-unselect', - disabledClass: 'ui-tabs-disabled', - panelClass: 'ui-tabs-panel', - hideClass: 'ui-tabs-hide', - loadingClass: 'ui-tabs-loading' -}; - -$.ui.tabs.getter = "length"; - -/* - * Tabs Extensions - */ - -/* - * Rotate - */ -$.extend($.ui.tabs.prototype, { - rotation: null, - rotate: function(ms, continuing) { - - continuing = continuing || false; - - var self = this, t = this.options.selected; - - function start() { - self.rotation = setInterval(function() { - t = ++t < self.$tabs.length ? t : 0; - self.select(t); - }, ms); - } - - function stop(e) { - if (!e || e.clientX) { // only in case of a true click - clearInterval(self.rotation); - } - } - - // start interval - if (ms) { - start(); - if (!continuing) - this.$tabs.bind(this.options.event, stop); - else - this.$tabs.bind(this.options.event, function() { - stop(); - t = self.options.selected; - start(); - }); - } - // stop interval - else { - stop(); - this.$tabs.unbind(this.options.event, stop); - } - } -}); - -})(jQuery); diff -r 506a0af77cee -r 5a0ee8de7f06 web/facet.py --- a/web/facet.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/facet.py Wed Apr 20 08:28:52 2011 +0200 @@ -1125,8 +1125,8 @@ def _render(self): facet = self.facet - facet._cw.add_js('ui.slider.js') - facet._cw.add_css('ui.all.css') + facet._cw.add_js('jquery.ui.js') + facet._cw.add_css('jquery.ui.css') sliderid = make_uid('theslider') facetid = xml_escape(self.facet.__regid__) facet._cw.html_headers.add_onload(self.onload % { diff -r 506a0af77cee -r 5a0ee8de7f06 web/formfields.py --- a/web/formfields.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/formfields.py Wed Apr 20 08:28:52 2011 +0200 @@ -875,7 +875,9 @@ if self.choices: return super(BooleanField, self).vocabulary(form) if self.allow_none: - return [('', ''), (form._cw._('yes'), '1'), (form._cw._('no'), '0')] + return [(form._cw._('indifferent'), ''), + (form._cw._('yes'), '1'), + (form._cw._('no'), '0')] # XXX empty string for 'no' in that case for bw compat return [(form._cw._('yes'), '1'), (form._cw._('no'), '')] @@ -1200,14 +1202,19 @@ FIELDS = { - 'Boolean': BooleanField, + 'String' : StringField, 'Bytes': FileField, - 'Date': DateField, - 'Datetime': DateTimeField, + 'Password': PasswordField, + + 'Boolean': BooleanField, 'Int': IntField, 'Float': FloatField, 'Decimal': StringField, - 'Password': PasswordField, - 'String' : StringField, - 'Time': TimeField, + + 'Date': DateField, + 'Datetime': DateTimeField, + 'TZDatetime': DateTimeField, + 'Time': TimeField, + 'TZTime': TimeField, + # XXX implement 'Interval': TimeIntervalField, } diff -r 506a0af77cee -r 5a0ee8de7f06 web/formwidgets.py --- a/web/formwidgets.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/formwidgets.py Wed Apr 20 08:28:52 2011 +0200 @@ -478,11 +478,12 @@ default
    is used. """ type = 'checkbox' + default_separator = u'
    \n' vocabulary_widget = True - def __init__(self, attrs=None, separator=u'
    \n', **kwargs): + def __init__(self, attrs=None, separator=None, **kwargs): super(CheckBox, self).__init__(attrs, **kwargs) - self.separator = separator + self.separator = separator or self.default_separator def _render(self, form, field, renderer): curvalues, attrs = self.values_and_attributes(form, field) diff -r 506a0af77cee -r 5a0ee8de7f06 web/request.py --- a/web/request.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/request.py Wed Apr 20 08:28:52 2011 +0200 @@ -734,26 +734,14 @@ return None, None def parse_accept_header(self, header): - """returns an ordered list of preferred languages""" + """returns an ordered list of accepted values""" + try: + value_parser, value_sort_key = ACCEPT_HEADER_PARSER[header.lower()] + except KeyError: + value_parser = value_sort_key = None accepteds = self.get_header(header, '') - values = [] - for info in accepteds.split(','): - try: - value, scores = info.split(';', 1) - except ValueError: - value = info - score = 1.0 - else: - for score in scores.split(';'): - try: - scorekey, scoreval = score.split('=') - if scorekey == 'q': # XXX 'level' - score = float(scoreval) - except ValueError: - continue - values.append((score, value)) - values.sort(reverse=True) - return (value for (score, value) in values) + values = _parse_accept_header(accepteds, value_parser, value_sort_key) + return (raw_value for (raw_value, parsed_value, score) in values) def header_if_modified_since(self): """If the HTTP header If-modified-since is set, return the equivalent @@ -768,8 +756,16 @@ will display '<[' at the beginning of the page """ self.set_content_type('text/html') - self.main_stream.doctype = TRANSITIONAL_DOCTYPE_NOEXT - self.main_stream.xmldecl = u'' + self.main_stream.set_doctype(TRANSITIONAL_DOCTYPE_NOEXT) + + def set_doctype(self, doctype, reset_xmldecl=True): + """helper method to dynamically change page doctype + + :param doctype: the new doctype, e.g. '' + :param reset_xmldecl: if True, remove the '' + declaration from the page + """ + self.main_stream.set_doctype(doctype, reset_xmldecl) # page data management #################################################### @@ -858,5 +854,91 @@ self.parse_accept_header('Accept-Language')] + +## HTTP-accept parsers / utilies ############################################## +def _mimetype_sort_key(accept_info): + """accepted mimetypes must be sorted by : + + 1/ highest score first + 2/ most specific mimetype first, e.g. : + - 'text/html level=1' is more specific 'text/html' + - 'text/html' is more specific than 'text/*' + - 'text/*' itself more specific than '*/*' + + """ + raw_value, (media_type, media_subtype, media_type_params), score = accept_info + # FIXME: handle '+' in media_subtype ? (should xhtml+xml have a + # higher precedence than xml ?) + if media_subtype == '*': + score -= 0.0001 + if media_type == '*': + score -= 0.0001 + return 1./score, media_type, media_subtype, 1./(1+len(media_type_params)) + +def _charset_sort_key(accept_info): + """accepted mimetypes must be sorted by : + + 1/ highest score first + 2/ most specific charset first, e.g. : + - 'utf-8' is more specific than '*' + """ + raw_value, value, score = accept_info + if value == '*': + score -= 0.0001 + return 1./score, value + +def _parse_accept_header(raw_header, value_parser=None, value_sort_key=None): + """returns an ordered list accepted types + + returned value is a list of 2-tuple (value, score), ordered + by score. Exact type of `value` will depend on what `value_parser` + will reutrn. if `value_parser` is None, then the raw value, as found + in the http header, is used. + """ + if value_sort_key is None: + value_sort_key = lambda infos: 1./infos[-1] + values = [] + for info in raw_header.split(','): + score = 1.0 + other_params = {} + try: + value, infodef = info.split(';', 1) + except ValueError: + value = info + else: + for info in infodef.split(';'): + try: + infokey, infoval = info.split('=') + if infokey == 'q': # XXX 'level' + score = float(infoval) + continue + except ValueError: + continue + other_params[infokey] = infoval + parsed_value = value_parser(value, other_params) if value_parser else value + values.append( (value.strip(), parsed_value, score) ) + values.sort(key=value_sort_key) + return values + + +def _mimetype_parser(value, other_params): + """return a 3-tuple + (type, subtype, type_params) corresponding to the mimetype definition + e.g. : for 'text/*', `mimetypeinfo` will be ('text', '*', {}), for + 'text/html;level=1', `mimetypeinfo` will be ('text', '*', {'level': '1'}) + """ + try: + media_type, media_subtype = value.strip().split('/') + except ValueError: # safety belt : '/' should always be present + media_type = value.strip() + media_subtype = '*' + return (media_type, media_subtype, other_params) + + +ACCEPT_HEADER_PARSER = { + 'accept': (_mimetype_parser, _mimetype_sort_key), + 'accept-charset': (None, _charset_sort_key), + } + from cubicweb import set_log_methods set_log_methods(CubicWebRequestBase, LOGGER) diff -r 506a0af77cee -r 5a0ee8de7f06 web/schemaviewer.py --- a/web/schemaviewer.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/schemaviewer.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,9 +15,8 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""an helper class to display CubicWeb schema using ureports +"""an helper class to display CubicWeb schema using ureports""" -""" __docformat__ = "restructuredtext en" _ = unicode @@ -217,7 +216,7 @@ if val is None: val = '' elif prop == 'constraints': - val = ', '.join([c.restriction for c in val]) + val = ', '.join([c.expression for c in val]) elif isinstance(val, dict): for key, value in val.iteritems(): if isinstance(value, (list, tuple)): diff -r 506a0af77cee -r 5a0ee8de7f06 web/test/unittest_application.py --- a/web/test/unittest_application.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/test/unittest_application.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. diff -r 506a0af77cee -r 5a0ee8de7f06 web/test/unittest_form.py --- a/web/test/unittest_form.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/test/unittest_form.py Wed Apr 20 08:28:52 2011 +0200 @@ -104,9 +104,9 @@ def test_reledit_composite_field(self): rset = self.execute('INSERT BlogEntry X: X title "cubicweb.org", X content "hop"') - form = self.vreg['views'].select('doreledit', self.request(), + form = self.vreg['views'].select('reledit', self.request(), rset=rset, row=0, rtype='content') - data = form.render(row=0, rtype='content', formid='base') + data = form.render(row=0, rtype='content', formid='base', action='edit_rtype') self.failUnless('content_format' in data) # form view tests ######################################################### diff -r 506a0af77cee -r 5a0ee8de7f06 web/test/unittest_reledit.py --- a/web/test/unittest_reledit.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/test/unittest_reledit.py Wed Apr 20 08:28:52 2011 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """ -mainly regression-preventing tests for reledit/doreledit views +mainly regression-preventing tests for reledit views """ from cubicweb.devtools.testlib import CubicWebTC @@ -33,9 +33,9 @@ class ClickAndEditFormTC(ReleditMixinTC, CubicWebTC): def test_default_config(self): - reledit = {'title': """
    cubicweb-world-domination
    """, - 'long_desc': """
    <not specified>
    """, - 'manager': """
    <not specified>
    """, + reledit = {'title': '''
    cubicweb-world-domination
    ''', + 'long_desc': '''
    <not specified>
    ''', + 'manager': '''
    <not specified>
    ''', 'composite_card11_2ttypes': """<not specified>""", 'concerns': """<not specified>"""} @@ -44,9 +44,11 @@ continue rtype = rschema.type self.assertMultiLineEqual(reledit[rtype] % {'eid': self.proj.eid}, - self.proj.view('reledit', rtype=rtype, role=role), rtype) + self.proj.view('reledit', rtype=rtype, role=role), + rtype) def test_default_forms(self): + self.skip('Need to check if this test should still run post reledit/doreledit merge') doreledit = {'title': """
    cubicweb-world-domination
    @@ -190,11 +192,11 @@ reledit_ctrl.tag_object_of(('Ticket', 'concerns', 'Project'), {'edit_target': 'rtype'}) reledit = { - 'title': """
    cubicweb-world-domination
    """, - 'long_desc': """
    <long_desc is required>
    """, - 'manager': """""", + 'title': """
    cubicweb-world-domination
    """, + 'long_desc': """
    <long_desc is required>
    """, + 'manager': """""", 'composite_card11_2ttypes': """<not specified>""", - 'concerns': """""" + 'concerns': """""" } for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True): if rschema not in reledit: diff -r 506a0af77cee -r 5a0ee8de7f06 web/test/unittest_request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/test/unittest_request.py Wed Apr 20 08:28:52 2011 +0200 @@ -0,0 +1,69 @@ +"""misc. unittests for utility functions +""" + +from logilab.common.testlib import TestCase, unittest_main + +from functools import partial + +from cubicweb.web.request import (_parse_accept_header, + _mimetype_sort_key, _mimetype_parser, _charset_sort_key) + + + +class AcceptParserTC(TestCase): + + def test_parse_accept(self): + parse_accept_header = partial(_parse_accept_header, + value_parser=_mimetype_parser, + value_sort_key=_mimetype_sort_key) + # compare scores + self.assertEqual(parse_accept_header("audio/*;q=0.2, audio/basic"), + [( ('audio/basic', ('audio', 'basic', {}), 1.0 ) ), + ( ('audio/*', ('audio', '*', {}), 0.2 ) )]) + self.assertEqual(parse_accept_header("text/plain;q=0.5, text/html, text/x-dvi;q=0.8, text/x-c"), + [( ('text/html', ('text', 'html', {}), 1.0 ) ), + ( ('text/x-c', ('text', 'x-c', {}), 1.0 ) ), + ( ('text/x-dvi', ('text', 'x-dvi', {}), 0.8 ) ), + ( ('text/plain', ('text', 'plain', {}), 0.5 ) )]) + # compare mimetype precedence for a same given score + self.assertEqual(parse_accept_header("audio/*, audio/basic"), + [( ('audio/basic', ('audio', 'basic', {}), 1.0 ) ), + ( ('audio/*', ('audio', '*', {}), 1.0 ) )]) + self.assertEqual(parse_accept_header("text/*, text/html, text/html;level=1, */*"), + [( ('text/html', ('text', 'html', {'level': '1'}), 1.0 ) ), + ( ('text/html', ('text', 'html', {}), 1.0 ) ), + ( ('text/*', ('text', '*', {}), 1.0 ) ), + ( ('*/*', ('*', '*', {}), 1.0 ) )]) + # free party + self.assertEqual(parse_accept_header("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"), + [( ('text/html', ('text', 'html', {'level': '1'}), 1.0 ) ), + ( ('text/html', ('text', 'html', {}), 0.7 ) ), + ( ('*/*', ('*', '*', {}), 0.5 ) ), + ( ('text/html', ('text', 'html', {'level': '2'}), 0.4 ) ), + ( ('text/*', ('text', '*', {}), 0.3 ) ) + ]) + # chrome sample header + self.assertEqual(parse_accept_header("application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"), + [( ('application/xhtml+xml', ('application', 'xhtml+xml', {}), 1.0 ) ), + ( ('application/xml', ('application', 'xml', {}), 1.0 ) ), + ( ('image/png', ('image', 'png', {}), 1.0 ) ), + ( ('text/html', ('text', 'html', {}), 0.9 ) ), + ( ('text/plain', ('text', 'plain', {}), 0.8 ) ), + ( ('*/*', ('*', '*', {}), 0.5 ) ), + ]) + + def test_parse_accept_language(self): + self.assertEqual(_parse_accept_header('fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'), + [('fr', 'fr', 1.0), ('fr-fr', 'fr-fr', 0.8), + ('en-us', 'en-us', 0.5), ('en', 'en', 0.3)]) + + def test_parse_accept_charset(self): + parse_accept_header = partial(_parse_accept_header, + value_sort_key=_charset_sort_key) + self.assertEqual(parse_accept_header('ISO-8859-1,utf-8;q=0.7,*;q=0.7'), + [('ISO-8859-1', 'ISO-8859-1', 1.0), + ('utf-8', 'utf-8', 0.7), + ('*', '*', 0.7)]) + +if __name__ == '__main__': + unittest_main() diff -r 506a0af77cee -r 5a0ee8de7f06 web/test/unittest_views_baseviews.py --- a/web/test/unittest_views_baseviews.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/test/unittest_views_baseviews.py Wed Apr 20 08:28:52 2011 +0200 @@ -16,11 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . +from __future__ import with_statement + from logilab.common.testlib import unittest_main from logilab.mtconverter import html_unescape from cubicweb.devtools.testlib import CubicWebTC from cubicweb.utils import json +from cubicweb.view import StartupView, TRANSITIONAL_DOCTYPE_NOEXT from cubicweb.web.htmlwidgets import TableWidget from cubicweb.web.views import vid_from_rset @@ -125,5 +128,47 @@ self.assertListEqual(got, expected) +class HTMLStreamTests(CubicWebTC): + + def test_set_doctype_reset_xmldecl(self): + """ + tests `cubicweb.web.request.CubicWebRequestBase.set_doctype` + with xmldecl reset + """ + class MyView(StartupView): + __regid__ = 'my-view' + def call(self): + self._cw.set_doctype('') + + with self.temporary_appobjects(MyView): + html_source = self.view('my-view').source + source_lines = [line.strip() for line in html_source.splitlines(False) + if line.strip()] + self.assertListEqual(source_lines[:2], + ['', + '']) + + def test_set_doctype_no_reset_xmldecl(self): + """ + tests `cubicweb.web.request.CubicWebRequestBase.set_doctype` + with no xmldecl reset + """ + html_doctype = TRANSITIONAL_DOCTYPE_NOEXT.strip() + class MyView(StartupView): + __regid__ = 'my-view' + def call(self): + self._cw.set_doctype(html_doctype, reset_xmldecl=False) + self._cw.main_stream.set_namespaces([('xmlns', 'http://www.w3.org/1999/xhtml')]) + self._cw.main_stream.set_htmlattrs([('lang', 'cz')]) + + with self.temporary_appobjects(MyView): + html_source = self.view('my-view').source + source_lines = [line.strip() for line in html_source.splitlines(False) + if line.strip()] + self.assertListEqual(source_lines[:3], + ['', + html_doctype, + '']) + if __name__ == '__main__': unittest_main() diff -r 506a0af77cee -r 5a0ee8de7f06 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/views/basecontrollers.py Wed Apr 20 08:28:52 2011 +0200 @@ -455,13 +455,13 @@ def js_reledit_form(self): req = self._cw args = dict((x, req.form[x]) - for x in ('formid', 'rtype', 'role', 'reload')) + for x in ('formid', 'rtype', 'role', 'reload', 'action')) rset = req.eid_rset(typed_eid(self._cw.form['eid'])) try: args['reload'] = json.loads(args['reload']) except ValueError: # not true/false, an absolute url assert args['reload'].startswith('http') - view = req.vreg['views'].select('doreledit', req, rset=rset, rtype=args['rtype']) + view = req.vreg['views'].select('reledit', req, rset=rset, rtype=args['rtype']) return self._call_view(view, **args) @jsonize diff -r 506a0af77cee -r 5a0ee8de7f06 web/views/owl.py --- a/web/views/owl.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/views/owl.py Wed Apr 20 08:28:52 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -35,15 +35,19 @@ } OWL_TYPE_MAP = {'String': 'xsd:string', - 'Datetime': 'xsd:dateTime', 'Bytes': 'xsd:byte', - 'Float': 'xsd:float', + 'Password': 'xsd:byte', + 'Boolean': 'xsd:boolean', 'Int': 'xsd:int', + 'Float': 'xsd:float', + 'Decimal' : 'xsd:decimal', + 'Date':'xsd:date', + 'Datetime': 'xsd:dateTime', + 'TZDatetime': 'xsd:dateTime', 'Time': 'xsd:time', - 'Password': 'xsd:byte', - 'Decimal' : 'xsd:decimal', + 'TZTime': 'xsd:time', 'Interval': 'xsd:duration' } diff -r 506a0af77cee -r 5a0ee8de7f06 web/views/plots.py --- a/web/views/plots.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/views/plots.py Wed Apr 20 08:28:52 2011 +0200 @@ -47,7 +47,7 @@ @objectify_selector def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs): etypes = rset.description[0] - if etypes[0] not in ('Date', 'Datetime'): + if etypes[0] not in ('Date', 'Datetime', 'TZDatetime'): return 0 for etype in etypes[1:]: if etype not in ('Int', 'Float'): diff -r 506a0af77cee -r 5a0ee8de7f06 web/views/reledit.py --- a/web/views/reledit.py Mon Apr 18 08:03:46 2011 +0200 +++ b/web/views/reledit.py Wed Apr 20 08:28:52 2011 +0200 @@ -15,7 +15,9 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""the 'reledit' feature (eg edit attribute/relation from primary view)""" +"""edit entity attributes/relations from any view, without going to the entity +form +""" __docformat__ = "restructuredtext en" _ = unicode @@ -24,7 +26,8 @@ from warnings import warn from logilab.mtconverter import xml_escape -from logilab.common.deprecation import deprecated +from logilab.common.deprecation import deprecated, class_renamed +from logilab.common.decorators import cached from cubicweb import neg_role from cubicweb.schema import display_name @@ -43,16 +46,18 @@ return u'' def append_field(self, *args): pass + def add_hidden(self, *args): + pass rctrl = uicfg.reledit_ctrl -class ClickAndEditFormView(EntityView): - __regid__ = 'doreledit' +class AutoClickAndEditFormView(EntityView): + __regid__ = 'reledit' __select__ = non_final_entity() & match_kwargs('rtype') # ui side continuations _onclick = (u"cw.reledit.loadInlineEditionForm('%(formid)s', %(eid)s, '%(rtype)s', '%(role)s', " - "'%(divid)s', %(reload)s, '%(vid)s');") + "'%(divid)s', %(reload)s, '%(vid)s', '%(action)s');") _cancelclick = "cw.reledit.cleanupAfterCancel('%s')" # ui side actions/buttons @@ -75,93 +80,84 @@ # function taking the subject entity & returning a boolean or an eid rvid=None, # vid to be applied to other side of rtype (non final relations only) default_value=None, - formid='base' + formid='base', + action=None ): """display field to edit entity's `rtype` relation on click""" assert rtype - assert role in ('subject', 'object'), '%s is not an acceptable role value' % role self._cw.add_css('cubicweb.form.css') self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js')) - entity = self.cw_rset.get_entity(row, col) + self.entity = self.cw_rset.get_entity(row, col) rschema = self._cw.vreg.schema[rtype] - self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*') + self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*') if rvid is not None or default_value is not None: warn('[3.9] specifying rvid/default_value on select is deprecated, ' 'reledit_ctrl rtag to control this' % self, DeprecationWarning) - reload = self._compute_reload(entity, rschema, role, reload) - divid = self._build_divid(rtype, role, entity.eid) + reload = self._compute_reload(rschema, role, reload) + divid = self._build_divid(rtype, role, self.entity.eid) if rschema.final: - self._handle_attribute(entity, rschema, role, divid, reload) + self._handle_attribute(rschema, role, divid, reload, action) else: if self._is_composite(): - self._handle_composite(entity, rschema, role, divid, reload, formid) + self._handle_composite(rschema, role, divid, reload, formid, action) else: - self._handle_relation(entity, rschema, role, divid, reload, formid) + self._handle_relation(rschema, role, divid, reload, formid, action) - def _handle_attribute(self, entity, rschema, role, divid, reload): - rtype = rschema.type - value = entity.printable_value(rtype) - if not self._should_edit_attribute(entity, rschema): + def _handle_attribute(self, rschema, role, divid, reload, action): + value = self.entity.printable_value(rschema.type) + if not self._should_edit_attribute(rschema): self.w(value) return - display_label, related_entity = self._prepare_form(entity, rtype, role) - form, renderer = self._build_form(entity, rtype, role, divid, 'base', - reload, display_label, related_entity) + form, renderer = self._build_form(self.entity, rschema, role, divid, 'base', reload, action) value = value or self._compute_default_value(rschema, role) self.view_form(divid, value, form, renderer) - def _compute_formid_value(self, entity, rschema, role, rvid, formid): - related_rset = entity.related(rschema.type, role) + def _compute_formid_value(self, rschema, role, rvid, formid): + related_rset = self.entity.related(rschema.type, role) if related_rset: value = self._cw.view(rvid, related_rset) else: value = self._compute_default_value(rschema, role) - if not self._should_edit_relation(entity, rschema, role): + if not self._should_edit_relation(rschema, role): return None, value return formid, value - def _handle_relation(self, entity, rschema, role, divid, reload, formid): + def _handle_relation(self, rschema, role, divid, reload, formid, action): rvid = self._rules.get('rvid', 'autolimited') - formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid) + formid, value = self._compute_formid_value(rschema, role, rvid, formid) if formid is None: return self.w(value) - rtype = rschema.type - display_label, related_entity = self._prepare_form(entity, rtype, role) - form, renderer = self._build_form(entity, rtype, role, divid, formid, reload, - display_label, related_entity, dict(vid=rvid)) + form, renderer = self._build_form(self.entity, rschema, role, divid, formid, + reload, action, dict(vid=rvid)) self.view_form(divid, value, form, renderer) - def _handle_composite(self, entity, rschema, role, divid, reload, formid): + def _handle_composite(self, rschema, role, divid, reload, formid, action): # this is for attribute-like composites (1 target type, 1 related entity at most, for now) - ttypes = self._compute_ttypes(rschema, role) + entity = self.entity related_rset = entity.related(rschema.type, role) - add_related = self._may_add_related(related_rset, entity, rschema, role, ttypes) - edit_related = self._may_edit_related_entity(related_rset, entity, rschema, role, ttypes) - delete_related = edit_related and self._may_delete_related(related_rset, entity, rschema, role) + add_related = self._may_add_related(related_rset, rschema, role) + edit_related = self._may_edit_related_entity(related_rset, rschema, role) + delete_related = edit_related and self._may_delete_related(related_rset, rschema, role) rvid = self._rules.get('rvid', 'autolimited') - formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid) + formid, value = self._compute_formid_value(rschema, role, rvid, formid) if formid is None or not (edit_related or add_related): # till we learn to handle cases where not (edit_related or add_related) self.w(value) return - rtype = rschema.type - ttype = ttypes[0] - _fdata = self._prepare_composite_form(entity, rtype, role, edit_related, - add_related and ttype) - display_label, related_entity = _fdata - form, renderer = self._build_form(entity, rtype, role, divid, formid, reload, - display_label, related_entity, dict(vid=rvid)) + form, renderer = self._build_form(entity, rschema, role, divid, formid, + reload, action, dict(vid=rvid)) self.view_form(divid, value, form, renderer, edit_related, add_related, delete_related) + @cached def _compute_ttypes(self, rschema, role): dual_role = neg_role(role) return getattr(rschema, '%ss' % dual_role)() - def _compute_reload(self, entity, rschema, role, reload): + def _compute_reload(self, rschema, role, reload): ctrl_reload = self._rules.get('reload', reload) if callable(ctrl_reload): - ctrl_reload = ctrl_reload(entity) + ctrl_reload = ctrl_reload(self.entity) if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False ctrl_reload = self._cw.build_url(ctrl_reload) return ctrl_reload @@ -179,33 +175,36 @@ def _is_composite(self): return self._rules.get('edit_target') == 'related' - def _may_add_related(self, related_rset, entity, rschema, role, ttypes): + def _may_add_related(self, related_rset, rschema, role): """ ok for attribute-like composite entities """ + ttypes = self._compute_ttypes(rschema, role) if len(ttypes) > 1: # many etypes: learn how to do it return False - rdef = rschema.role_rdef(entity.e_schema, ttypes[0], role) + rdef = rschema.role_rdef(self.entity.e_schema, ttypes[0], role) card = rdef.role_cardinality(role) if related_rset or card not in '?1': return False if role == 'subject': - kwargs = {'fromeid': entity.eid} + kwargs = {'fromeid': self.entity.eid} else: - kwargs = {'toeid': entity.eid} + kwargs = {'toeid': self.entity.eid} return rdef.has_perm(self._cw, 'add', **kwargs) - def _may_edit_related_entity(self, related_rset, entity, rschema, role, ttypes): + def _may_edit_related_entity(self, related_rset, rschema, role): """ controls the edition of the related entity """ + ttypes = self._compute_ttypes(rschema, role) if len(ttypes) > 1 or len(related_rset.rows) != 1: return False - if entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1': + if self.entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1': return False return related_rset.get_entity(0, 0).cw_has_perm('update') - def _may_delete_related(self, related_rset, entity, rschema, role): + def _may_delete_related(self, related_rset, rschema, role): # we assume may_edit_related, only 1 related entity if not related_rset: return False rentity = related_rset.get_entity(0, 0) + entity = self.entity if role == 'subject': kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid} else: @@ -230,33 +229,33 @@ """ builds an id for the root div of a reledit widget """ return '%s-%s-%s' % (rtype, role, entity_eid) - def _build_args(self, entity, rtype, role, formid, reload, + def _build_args(self, entity, rtype, role, formid, reload, action, extradata=None): divid = self._build_divid(rtype, role, entity.eid) event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid, - 'reload' : json_dumps(reload), + 'reload' : json_dumps(reload), 'action': action, 'role' : role, 'vid' : u''} if extradata: event_args.update(extradata) return event_args - def _prepare_form(self, entity, _rtype, role): - display_label = False - related_entity = entity - return display_label, related_entity - - def _prepare_composite_form(self, entity, rtype, role, edit_related, add_related): - display_label = True - if edit_related and not add_related: - related_entity = entity.related(rtype, role).get_entity(0, 0) - elif add_related: - _new_entity = self._cw.vreg['etypes'].etype_class(add_related)(self._cw) + def _prepare_form(self, entity, rschema, role, action): + assert action in ('edit_rtype', 'edit_related', 'add', 'delete'), action + if action == 'edit_rtype': + return False, entity + label = True + if action in ('edit_related', 'delete'): + edit_entity = entity.related(rschema, role).get_entity(0, 0) + elif action == 'add': + add_etype = self._compute_ttypes(rschema, role)[0] + _new_entity = self._cw.vreg['etypes'].etype_class(add_etype)(self._cw) _new_entity.eid = self._cw.varmaker.next() - related_entity = _new_entity + edit_entity = _new_entity # XXX see forms.py ~ 276 and entities.linked_to method # is there another way ? - self._cw.form['__linkto'] = '%s:%s:%s' % (rtype, entity.eid, neg_role(role)) - return display_label, related_entity + self._cw.form['__linkto'] = '%s:%s:%s' % (rschema, entity.eid, neg_role(role)) + assert edit_entity + return label, edit_entity def _build_renderer(self, related_entity, display_label): return self._cw.vreg['formrenderers'].select( @@ -266,13 +265,18 @@ display_help=False, button_bar_class='buttonbar', display_progress_div=False) - def _build_form(self, entity, rtype, role, divid, formid, reload, - display_label, related_entity, extradata=None, **formargs): - event_args = self._build_args(entity, rtype, role, formid, - reload, extradata) + def _build_form(self, entity, rschema, role, divid, formid, reload, action, + extradata=None, **formargs): + rtype = rschema.type + event_args = self._build_args(entity, rtype, role, formid, reload, action, extradata) + if not action: + form = _DummyForm() + form.event_args = event_args + return form, None + label, edit_entity = self._prepare_form(entity, rschema, role, action) cancelclick = self._cancelclick % divid form = self._cw.vreg['forms'].select( - formid, self._cw, rset=related_entity.as_rset(), entity=related_entity, + formid, self._cw, rset=edit_entity.as_rset(), entity=edit_entity, domid='%s-form' % divid, formtype='inlined', action=self._cw.build_url('validateform', __onsuccess='window.parent.cw.reledit.onSuccess'), cwtarget='eformframe', cssclass='releditForm', @@ -298,9 +302,10 @@ if formid == 'base': field = form.field_by_name(rtype, role, entity.e_schema) form.append_field(field) - return form, self._build_renderer(related_entity, display_label) + return form, self._build_renderer(edit_entity, label) - def _should_edit_attribute(self, entity, rschema): + def _should_edit_attribute(self, rschema): + entity = self.entity rdef = entity.e_schema.rdef(rschema) # check permissions if not entity.cw_has_perm('update'): @@ -312,8 +317,8 @@ ' use _should_edit_attribute instead', _should_edit_attribute) - def _should_edit_relation(self, entity, rschema, role): - eeid = entity.eid + def _should_edit_relation(self, rschema, role): + eeid = self.entity.eid perm_args = {'fromeid': eeid} if role == 'subject' else {'toeid': eeid} return rschema.has_perm(self._cw, 'add', **perm_args) @@ -335,9 +340,11 @@ w(u'