# HG changeset patch # User Sylvain Thénault # Date 1305555933 -7200 # Node ID c2b29631c1a354edffea8974e74059f0f91a4b63 # Parent 69aa88765db5cf88dab2bda896a88e5d1d9412fc# Parent dc319ece0bd62f841e712df8133b38981b07d4de backport oldstable diff -r dc319ece0bd6 -r c2b29631c1a3 .hgtags --- a/.hgtags Mon May 16 16:24:00 2011 +0200 +++ b/.hgtags Mon May 16 16:25:33 2011 +0200 @@ -188,5 +188,15 @@ 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 b7a124f9aed2c7c9c86c6349ddd9f0a07023f0ca cubicweb-version-3.11.3 b3c6702761a18a41fdbb7bc1083f92aefce07765 cubicweb-debian-version-3.11.3-1 +e712bc6f1f71684f032bfcb9bb151a066c707dec cubicweb-version-3.12.3 +ba8fe4f2e408c3fdf6c297cd42c2577dcac50e71 cubicweb-debian-version-3.12.3-1 +5cd0dbc26882f60e3f11ec55e7f058d94505e7ed cubicweb-version-3.12.4 +7c4d34a5ec57f927a70cbc7af7fa8310c847ac42 cubicweb-debian-version-3.12.4-1 diff -r dc319ece0bd6 -r c2b29631c1a3 __pkginfo__.py --- a/__pkginfo__.py Mon May 16 16:24:00 2011 +0200 +++ b/__pkginfo__.py Mon May 16 16:25:33 2011 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 11, 3) +numversion = (3, 12, 4) 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 dc319ece0bd6 -r c2b29631c1a3 cwconfig.py --- a/cwconfig.py Mon May 16 16:24:00 2011 +0200 +++ b/cwconfig.py Mon May 16 16:25:33 2011 +0200 @@ -1070,9 +1070,10 @@ def instance_md5_version(self): import hashlib infos = [] - for pkg in self.cubes(): + for pkg in sorted(self.cubes()): version = self.cube_version(pkg) infos.append('%s-%s' % (pkg, version)) + infos.append('cubicweb-%s' % str(self.cubicweb_version())) return hashlib.md5(';'.join(infos)).hexdigest() def load_configuration(self): diff -r dc319ece0bd6 -r c2b29631c1a3 cwctl.py --- a/cwctl.py Mon May 16 16:24:00 2011 +0200 +++ b/cwctl.py Mon May 16 16:25:33 2011 +0200 @@ -905,6 +905,7 @@ scripts, args = self.cmdline_parser.largs[1:], self.cmdline_parser.rargs for script in scripts: mih.cmd_process_script(script, scriptargs=args) + mih.commit() else: mih.interactive_shell() finally: diff -r dc319ece0bd6 -r c2b29631c1a3 cwvreg.py --- a/cwvreg.py Mon May 16 16:24:00 2011 +0200 +++ b/cwvreg.py Mon May 16 16:25:33 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': @@ -369,6 +373,16 @@ cls.__initialize__(self.schema) return cls + def fetch_attrs(self, targettypes): + """return intersection of fetch_attrs of each entity type in + `targettypes` + """ + fetchattrs_list = [] + for ttype in targettypes: + etypecls = self.etype_class(ttype) + fetchattrs_list.append(set(etypecls.fetch_attrs)) + return reduce(set.intersection, fetchattrs_list) + VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry @@ -835,18 +849,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 dc319ece0bd6 -r c2b29631c1a3 dataimport.py --- a/dataimport.py Mon May 16 16:24:00 2011 +0200 +++ b/dataimport.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 debian/changelog --- a/debian/changelog Mon May 16 16:24:00 2011 +0200 +++ b/debian/changelog Mon May 16 16:25:33 2011 +0200 @@ -1,3 +1,33 @@ +cubicweb (3.12.4-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 11 May 2011 12:28:42 +0200 + +cubicweb (3.12.3-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Thu, 05 May 2011 16:21:33 +0200 + +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.3-1) unstable; urgency=low * new upstream release @@ -6,7 +36,7 @@ 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 dc319ece0bd6 -r c2b29631c1a3 debian/control --- a/debian/control Mon May 16 16:24:00 2011 +0200 +++ b/debian/control Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 devtools/__init__.py --- a/devtools/__init__.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/__init__.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 devtools/fill.py --- a/devtools/fill.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/fill.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 devtools/qunit.py --- a/devtools/qunit.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/qunit.py Mon May 16 16:25:33 2011 +0200 @@ -5,7 +5,7 @@ from Queue import Queue, Empty from subprocess import Popen, check_call, CalledProcessError from shutil import rmtree, copy as copyfile -from uuid import uuid4 +from uuid import uuid4 # imported by default to simplify further import statements from logilab.common.testlib import unittest_main, with_tempdir, InnerTest, Tags diff -r dc319ece0bd6 -r c2b29631c1a3 devtools/repotest.py --- a/devtools/repotest.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/repotest.py Mon May 16 16:25:33 2011 +0200 @@ -371,8 +371,13 @@ _orig_select_principal = rqlannotation._select_principal def _select_principal(scope, relations): + def sort_key(something): + try: + return something.r_type + except AttributeError: + return (something[0].r_type, something[1]) return _orig_select_principal(scope, relations, - _sort=lambda rels: sorted(rels, key=lambda x: x.r_type)) + _sort=lambda rels: sorted(rels, key=sort_key)) try: from cubicweb.server.msplanner import PartPlanInformation diff -r dc319ece0bd6 -r c2b29631c1a3 devtools/testlib.py --- a/devtools/testlib.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/testlib.py Mon May 16 16:25:33 2011 +0200 @@ -30,6 +30,7 @@ from math import log from contextlib import contextmanager from warnings import warn +from types import NoneType import yams.schema @@ -37,16 +38,15 @@ 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 from cubicweb import ValidationError, NoSelectableObject, AuthenticationError -from cubicweb import cwconfig, devtools, web, server -from cubicweb.dbapi import ProgrammingError, DBAPISession, repo_connect +from cubicweb import cwconfig, dbapi, devtools, web, server 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 @@ -252,7 +252,7 @@ # cnx is now an instance property that use a class protected attributes. cls.set_cnx(cnx) cls.vreg = cls.repo.vreg - cls.websession = DBAPISession(cnx, cls.admlogin) + cls.websession = dbapi.DBAPISession(cnx, cls.admlogin) cls._orig_cnx = (cnx, cls.websession) cls.config.repository = lambda x=None: cls.repo @@ -354,23 +354,64 @@ else: return req.user - def create_user(self, login, groups=('users',), password=None, req=None, - commit=True, **kwargs): + @iclassmethod # XXX turn into a class method + def create_user(self, req, login=None, groups=('users',), password=None, + email=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=2) + if not isinstance(groups, (tuple, list)): + password = groups + groups = login + elif isinstance(login, tuple): + groups = login + login = req + 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)' % ','.join(repr(str(g)) for g in groups), {'x': user.eid}) + if email is not None: + req.create_entity('EmailAddress', address=unicode(email), + reverse_primary_email=user) 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=2) + 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 = getattr(entity, 'eid', entity) + 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: @@ -380,8 +421,8 @@ autoclose = kwargs.pop('autoclose', True) if not kwargs: kwargs['password'] = str(login) - self.set_cnx(repo_connect(self.repo, unicode(login), **kwargs)) - self.websession = DBAPISession(self.cnx) + self.set_cnx(dbapi.repo_connect(self.repo, unicode(login), **kwargs)) + self.websession = dbapi.DBAPISession(self.cnx) if login == self.vreg.config.anonymous_user()[0]: self.cnx.anonymous_connection = True if autoclose: @@ -423,7 +464,7 @@ def rollback(self): try: self.cnx.rollback() - except ProgrammingError: + except dbapi.ProgrammingError: pass # connection closed finally: self.session.set_pool() # ensure pool still set after commit @@ -439,21 +480,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__, {}) @@ -469,6 +495,10 @@ entity.cw_attr_cache.pop('modification_date', None) self.failUnless(entity.modification_date > olddate) + def assertItemsEqual(self, it1, it2, *args, **kwargs): + it1 = set(getattr(x, 'eid', x) for x in it1) + it2 = set(getattr(x, 'eid', x) for x in it2) + super(CubicWebTC, self).assertItemsEqual(it1, it2, *args, **kwargs) # workflow utilities ####################################################### @@ -696,9 +726,9 @@ def assertAuthFailure(self, req, nbsessions=0): self.app.connect(req) - self.assertIsInstance(req.session, DBAPISession) + self.assertIsInstance(req.session, dbapi.DBAPISession) self.assertEqual(req.session.cnx, None) - self.assertEqual(req.cnx, None) + self.assertIsInstance(req.cnx, (dbapi._NeedAuthAccessMock, NoneType)) self.assertEqual(len(self.open_sessions), nbsessions) clear_cache(req, 'get_authorization') diff -r dc319ece0bd6 -r c2b29631c1a3 doc/book/en/annexes/faq.rst --- a/doc/book/en/annexes/faq.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/annexes/faq.rst Mon May 16 16:25:33 2011 +0200 @@ -380,11 +380,14 @@ You can prefer use a migration script similar to this shell invocation instead:: $ cubicweb-ctl shell + >>> from cubicweb import Binary >>> from cubicweb.server.utils import crypt_password >>> crypted = crypt_password('joepass') >>> rset = rql('Any U WHERE U is CWUser, U login "joe"') >>> joe = rset.get_entity(0,0) - >>> joe.set_attributes(upassword=crypted) + >>> joe.set_attributes(upassword=Binary(crypted)) + +Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file. The more experimented people would use RQL request directly:: diff -r dc319ece0bd6 -r c2b29631c1a3 doc/book/en/devrepo/vreg.rst --- a/doc/book/en/devrepo/vreg.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devrepo/vreg.rst Mon May 16 16:25:33 2011 +0200 @@ -79,7 +79,7 @@ .. autoclass:: cubicweb.selectors.has_add_permission .. autoclass:: cubicweb.selectors.has_mimetype .. autoclass:: cubicweb.selectors.is_in_state -.. autoclass:: cubicweb.selectors.on_transition +.. autoclass:: cubicweb.selectors.on_fire_transition .. autoclass:: cubicweb.selectors.implements diff -r dc319ece0bd6 -r c2b29631c1a3 doc/tools/pyjsrest.py --- a/doc/tools/pyjsrest.py Mon May 16 16:24:00 2011 +0200 +++ b/doc/tools/pyjsrest.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 entities/authobjs.py --- a/entities/authobjs.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/authobjs.py Mon May 16 16:25:33 2011 +0200 @@ -29,9 +29,21 @@ fetch_attrs, fetch_order = fetch_config(['name']) fetch_unrelated_order = fetch_order - def db_key_name(self): - """XXX goa specific""" - return self.get('name') + def grant_permission(self, entity, pname, plabel=None): + """grant local `pname` permission on `entity` to this group using + :class:`CWPermission`. + + If a similar permission already exists, add the group to it, else create + a new one. + """ + if not self._cw.execute( + 'SET X require_group G WHERE E eid %(e)s, G eid %(g)s, ' + 'E require_permission X, X name %(name)s, X label %(label)s', + {'e': entity.eid, 'g': self.eid, + 'name': pname, 'label': plabel}): + self._cw.create_entity('CWPermission', name=pname, label=plabel, + require_group=self, + reverse_require_permission=entity) class CWUser(AnyEntity): @@ -156,10 +168,6 @@ dc_long_title = name - def db_key_name(self): - """XXX goa specific""" - return self.get('login') - from logilab.common.deprecation import class_renamed EUser = class_renamed('EUser', CWUser) EGroup = class_renamed('EGroup', CWGroup) diff -r dc319ece0bd6 -r c2b29631c1a3 entities/schemaobjs.py --- a/entities/schemaobjs.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/schemaobjs.py Mon May 16 16:25:33 2011 +0200 @@ -45,10 +45,6 @@ return u'%s <<%s>>' % (self.dc_title(), ', '.join(stereotypes)) return self.dc_title() - def db_key_name(self): - """XXX goa specific""" - return self.get('name') - class CWRType(AnyEntity): __regid__ = 'CWRType' @@ -87,10 +83,6 @@ "has cardinality=%(card)s") raise ValidationError(self.eid, {qname: msg % locals()}) - def db_key_name(self): - """XXX goa specific""" - return self.get('name') - class CWRelation(AnyEntity): __regid__ = 'CWRelation' diff -r dc319ece0bd6 -r c2b29631c1a3 entities/test/unittest_base.py --- a/entities/test/unittest_base.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/test/unittest_base.py Mon May 16 16:25:33 2011 +0200 @@ -34,7 +34,8 @@ class BaseEntityTC(CubicWebTC): def setup_database(self): - self.member = self.create_user('member') + req = self.request() + self.member = self.create_user(req, 'member') diff -r dc319ece0bd6 -r c2b29631c1a3 entities/test/unittest_wfobjs.py --- a/entities/test/unittest_wfobjs.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/test/unittest_wfobjs.py Mon May 16 16:25:33 2011 +0200 @@ -95,13 +95,15 @@ class WorkflowTC(CubicWebTC): def setup_database(self): + req = self.request() rschema = self.schema['in_state'] for rdef in rschema.rdefs.values(): self.assertEqual(rdef.cardinality, '1*') - self.member = self.create_user('member') + self.member = self.create_user(req, 'member') def test_workflow_base(self): - e = self.create_user('toto') + req = self.request() + e = self.create_user(req, 'toto') iworkflowable = e.cw_adapt_to('IWorkflowable') self.assertEqual(iworkflowable.state, 'activated') iworkflowable.change_state('deactivated', u'deactivate 1') @@ -170,13 +172,14 @@ self.assertEqual(trinfo.transition.name, 'deactivate') def test_goback_transition(self): + req = self.request() wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow asleep = wf.add_state('asleep') wf.add_transition('rest', (wf.state_by_name('activated'), wf.state_by_name('deactivated')), asleep) wf.add_transition('wake up', asleep) - user = self.create_user('stduser') + user = self.create_user(req, 'stduser') iworkflowable = user.cw_adapt_to('IWorkflowable') iworkflowable.fire_transition('rest') self.commit() @@ -196,7 +199,8 @@ def _test_stduser_deactivate(self): ueid = self.member.eid - self.create_user('tutu') + req = self.request() + self.create_user(req, 'tutu') cnx = self.login('tutu') req = self.request() iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable') @@ -393,7 +397,8 @@ class CustomWorkflowTC(CubicWebTC): def setup_database(self): - self.member = self.create_user('member') + req = self.request() + self.member = self.create_user(req, 'member') def test_custom_wf_replace_state_no_history(self): """member in inital state with no previous history, state is simply @@ -493,7 +498,8 @@ def test_auto_transition_fired(self): wf = self.setup_custom_wf() - user = self.create_user('member') + req = self.request() + user = self.create_user(req, 'member') iworkflowable = user.cw_adapt_to('IWorkflowable') self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s', {'wf': wf.eid, 'x': user.eid}) @@ -523,7 +529,8 @@ def test_auto_transition_custom_initial_state_fired(self): wf = self.setup_custom_wf() - user = self.create_user('member', surname=u'toto') + req = self.request() + user = self.create_user(req, 'member', surname=u'toto') self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s', {'wf': wf.eid, 'x': user.eid}) self.commit() @@ -538,7 +545,8 @@ type=u'auto', conditions=({'expr': u'X surname "toto"', 'mainvars': u'X'},)) self.commit() - user = self.create_user('member', surname=u'toto') + req = self.request() + user = self.create_user(req, 'member', surname=u'toto') self.commit() iworkflowable = user.cw_adapt_to('IWorkflowable') self.assertEqual(iworkflowable.state, 'dead') @@ -554,7 +562,8 @@ self.s_deactivated = self.wf.state_by_name('deactivated').eid self.s_dummy = self.wf.add_state(u'dummy').eid self.wf.add_transition(u'dummy', (self.s_deactivated,), self.s_dummy) - ueid = self.create_user('stduser', commit=False).eid + req = self.request() + ueid = self.create_user(req, 'stduser', commit=False).eid # test initial state is set rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s', {'x' : ueid}) diff -r dc319ece0bd6 -r c2b29631c1a3 entity.py --- a/entity.py Mon May 16 16:24:00 2011 +0200 +++ b/entity.py Mon May 16 16:25:33 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) @@ -202,8 +204,6 @@ restriction = '%s %s %s' % (mainvar, attr, var) restrictions.append(restriction) if not rschema.final: - # XXX this does not handle several destination types - desttype = rschema.objects(eschema.type)[0] card = rdef.cardinality[0] if card not in '?1': cls.warning('bad relation %s specified in fetch attrs for %s', @@ -216,11 +216,17 @@ # that case the relation may still be missing. As we miss this # later information here, systematically add it. restrictions[-1] += '?' + targettypes = rschema.objects(eschema.type) # XXX user._cw.vreg iiiirk - destcls = user._cw.vreg['etypes'].etype_class(desttype) - destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs, - selection, orderby, restrictions, - user, ordermethod, visited=visited) + etypecls = user._cw.vreg['etypes'].etype_class(targettypes[0]) + if len(targettypes) > 1: + # find fetch_attrs common to all destination types + fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes) + else: + fetchattrs = etypecls.fetch_attrs + etypecls._fetch_restrictions(var, varmaker, fetchattrs, + selection, orderby, restrictions, + user, ordermethod, visited=visited) if ordermethod is not None: orderterm = getattr(cls, ordermethod)(attr, var) if orderterm: @@ -264,6 +270,7 @@ restrictions = set() pending_relations = [] eschema = cls.e_schema + qargs = {} for attr, value in kwargs.items(): if attr.startswith('reverse_'): attr = attr[len('reverse_'):] @@ -277,10 +284,11 @@ value = iter(value).next() else: # prepare IN clause - del kwargs[attr] - pending_relations.append( (attr, value) ) + pending_relations.append( (attr, role, value) ) continue - if hasattr(value, 'eid'): # non final relation + if rschema.final: # attribute + relations.append('X %s %%(%s)s' % (attr, attr)) + else: rvar = attr.upper() if role == 'object': relations.append('%s %s X' % (rvar, attr)) @@ -289,21 +297,21 @@ restriction = '%s eid %%(%s)s' % (rvar, attr) if not restriction in restrictions: restrictions.add(restriction) - kwargs[attr] = value.eid - else: # attribute - relations.append('X %s %%(%s)s' % (attr, attr)) + if hasattr(value, 'eid'): + value = value.eid + qargs[attr] = value if relations: rql = '%s: %s' % (rql, ', '.join(relations)) if restrictions: rql = '%s WHERE %s' % (rql, ', '.join(restrictions)) - created = execute(rql, kwargs).get_entity(0, 0) - for attr, values in pending_relations: - if attr.startswith('reverse_'): - restr = 'Y %s X' % attr[len('reverse_'):] + created = execute(rql, qargs).get_entity(0, 0) + for attr, role, values in pending_relations: + if role == 'object': + restr = 'Y %s X' % attr else: restr = 'X %s Y' % attr execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % ( - restr, ','.join(str(r.eid) for r in values)), + restr, ','.join(str(getattr(r, 'eid', r)) for r in values)), {'x': created.eid}, build_descr=False) return created @@ -728,17 +736,13 @@ else: restriction += ', X is IN (%s)' % ','.join(targettypes) card = greater_card(rschema, targettypes, (self.e_schema,), 1) + etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0]) if len(targettypes) > 1: - fetchattrs_list = [] - for ttype in targettypes: - etypecls = self._cw.vreg['etypes'].etype_class(ttype) - fetchattrs_list.append(set(etypecls.fetch_attrs)) - fetchattrs = reduce(set.intersection, fetchattrs_list) - rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs, - settype=False) + fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes) else: - etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0]) - rql = etypecls.fetch_rql(self._cw.user, [restriction], settype=False) + fetchattrs = etypecls.fetch_attrs + rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs, + settype=False) # optimisation: remove ORDERBY if cardinality is 1 or ? (though # greater_card return 1 for those both cases) if card == '1': @@ -762,7 +766,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 +776,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 +864,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) @@ -930,8 +960,9 @@ """add relations to the given object. To set a relation where this entity is the object of the relation, use 'reverse_' as argument name. - Values may be an entity, a list of entities, or None (meaning that all - relations of the given type from or to this object should be deleted). + Values may be an entity or eid, a list of entities or eids, or None + (meaning that all relations of the given type from or to this object + should be deleted). """ # XXX update cache _check_cw_unsafe(kwargs) @@ -946,9 +977,17 @@ continue if not isinstance(values, (tuple, list, set, frozenset)): values = (values,) + eids = [] + for val in values: + try: + eids.append(str(val.eid)) + except AttributeError: + try: + eids.append(str(typed_eid(val))) + except (ValueError, TypeError): + raise Exception('expected an Entity or eid, got %s' % val) self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % ( - restr, ','.join(str(r.eid) for r in values)), - {'x': self.eid}) + restr, ','.join(eids)), {'x': self.eid}) def cw_delete(self, **kwargs): assert self.has_eid(), self.eid diff -r dc319ece0bd6 -r c2b29631c1a3 hooks/metadata.py --- a/hooks/metadata.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/metadata.py Mon May 16 16:25:33 2011 +0200 @@ -67,13 +67,12 @@ def precommit_event(self): session = self.session - for eid in self.get_data(): - if session.deleted_in_transaction(eid): - # entity have been created and deleted in the same transaction - continue - entity = session.entity_from_eid(eid) - if not entity.created_by: - session.add_relation(eid, 'created_by', session.user.eid) + relations = [(eid, session.user.eid) for eid in self.get_data() + # don't consider entities that have been created and + # deleted in the same transaction + if not session.deleted_in_transaction(eid) and \ + not session.entity_from_eid(eid).created_by] + session.add_relations([('created_by', relations)]) class SetOwnershipHook(MetaDataHook): diff -r dc319ece0bd6 -r c2b29631c1a3 hooks/test/unittest_hooks.py --- a/hooks/test/unittest_hooks.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/test/unittest_hooks.py Mon May 16 16:25:33 2011 +0200 @@ -42,25 +42,31 @@ def test_html_tidy_hook(self): req = self.request() - entity = req.create_entity('Workflow', name=u'wf1', description_format=u'text/html', - description=u'yo') + entity = req.create_entity('Workflow', name=u'wf1', + description_format=u'text/html', + description=u'yo') self.assertEqual(entity.description, u'yo') - entity = req.create_entity('Workflow', name=u'wf2', description_format=u'text/html', - description=u'yo') + entity = req.create_entity('Workflow', name=u'wf2', + description_format=u'text/html', + description=u'yo') self.assertEqual(entity.description, u'yo') - entity = req.create_entity('Workflow', name=u'wf3', description_format=u'text/html', - description=u'yo') + entity = req.create_entity('Workflow', name=u'wf3', + description_format=u'text/html', + description=u'yo') self.assertEqual(entity.description, u'yo') - entity = req.create_entity('Workflow', name=u'wf4', description_format=u'text/html', - description=u'R&D') + entity = req.create_entity('Workflow', name=u'wf4', + description_format=u'text/html', + description=u'R&D') self.assertEqual(entity.description, u'R&D') - entity = req.create_entity('Workflow', name=u'wf5', description_format=u'text/html', - description=u"
c'est l'été") + entity = req.create_entity('Workflow', name=u'wf5', + description_format=u'text/html', + description=u"
c'est l'été") self.assertEqual(entity.description, u"
c'est l'été
") def test_nonregr_html_tidy_hook_no_update(self): - entity = self.request().create_entity('Workflow', name=u'wf1', description_format=u'text/html', - description=u'yo') + entity = self.request().create_entity('Workflow', name=u'wf1', + description_format=u'text/html', + description=u'yo') entity.set_attributes(name=u'wf2') self.assertEqual(entity.description, u'yo') entity.set_attributes(description=u'R&D

yo') @@ -90,7 +96,8 @@ self.assertEqual(entity.owned_by[0].eid, self.session.user.eid) def test_user_login_stripped(self): - u = self.create_user(' joe ') + req = self.request() + u = self.create_user(req, ' joe ') tname = self.execute('Any L WHERE E login L, E eid %(e)s', {'e': u.eid})[0][0] self.assertEqual(tname, 'joe') @@ -104,7 +111,8 @@ class UserGroupHooksTC(CubicWebTC): def test_user_synchronization(self): - self.create_user('toto', password='hop', commit=False) + req = self.request() + self.create_user(req, 'toto', password='hop', commit=False) self.assertRaises(AuthenticationError, self.repo.connect, u'toto', password='hop') self.commit() @@ -129,7 +137,8 @@ self.assertEqual(user.groups, set(('managers',))) def test_user_composite_owner(self): - ueid = self.create_user('toto').eid + req = self.request() + ueid = self.create_user(req, 'toto').eid # composite of euser should be owned by the euser regardless of who created it self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", U use_email X ' 'WHERE U login "toto"') diff -r dc319ece0bd6 -r c2b29631c1a3 hooks/test/unittest_syncschema.py --- a/hooks/test/unittest_syncschema.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/test/unittest_syncschema.py Mon May 16 16:25:33 2011 +0200 @@ -251,7 +251,8 @@ 'RT name "surname", E name "CWUser"') self.commit() # should not be able anymore to add cwuser without surname - self.assertRaises(ValidationError, self.create_user, "toto") + req = self.request() + self.assertRaises(ValidationError, self.create_user, req, "toto") self.rollback() self.execute('SET DEF cardinality "?1" ' 'WHERE DEF relation_type RT, DEF from_entity E,' diff -r dc319ece0bd6 -r c2b29631c1a3 i18n/de.po --- a/i18n/de.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/de.po Mon May 16 16:25:33 2011 +0200 @@ -162,7 +162,7 @@ msgstr "" #, python-format -msgid "'%s' action require 'linkattr' option" +msgid "'%s' action requires 'linkattr' option" msgstr "" msgid "(UNEXISTANT EID)" @@ -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." @@ -1049,10 +1061,6 @@ msgid "add WorkflowTransition transition_of Workflow object" msgstr "Workflow-Übergang" -#, python-format -msgid "add a %s" -msgstr "" - msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" msgstr "einen Entitätstyp hinzufügen" @@ -2743,6 +2751,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 dc319ece0bd6 -r c2b29631c1a3 i18n/en.po --- a/i18n/en.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/en.po Mon May 16 16:25:33 2011 +0200 @@ -154,7 +154,7 @@ msgstr "" #, python-format -msgid "'%s' action require 'linkattr' option" +msgid "'%s' action requires 'linkattr' option" msgstr "" msgid "(UNEXISTANT EID)" @@ -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 "" @@ -1009,10 +1021,6 @@ msgid "add WorkflowTransition transition_of Workflow object" msgstr "workflow-transition" -#, python-format -msgid "add a %s" -msgstr "" - msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" msgstr "add an entity type" @@ -2668,6 +2676,9 @@ "is created" msgstr "" +msgid "indifferent" +msgstr "indifferent" + msgid "info" msgstr "" @@ -3305,7 +3316,7 @@ msgstr "" msgid "read_permission" -msgstr "can be read by" +msgstr "read permission" msgctxt "CWAttribute" msgid "read_permission" @@ -3324,11 +3335,11 @@ msgctxt "CWGroup" msgid "read_permission_object" -msgstr "can be read by" +msgstr "has permission to read" msgctxt "RQLExpression" msgid "read_permission_object" -msgstr "can be read by" +msgstr "has permission to read" msgid "regexp matching host(s) to which this config applies" msgstr "" diff -r dc319ece0bd6 -r c2b29631c1a3 i18n/es.po --- a/i18n/es.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/es.po Mon May 16 16:25:33 2011 +0200 @@ -4,9 +4,9 @@ msgid "" msgstr "" "Project-Id-Version: cubicweb 2.46.0\n" -"PO-Revision-Date: 2010-11-27 07:59+0100\n" -"Last-Translator: Celso Flores, Carlos Balderas " -"\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2011-05-03 12:53-0600\n" +"Last-Translator: CreaLibre \n" "Language-Team: es \n" "Language: \n" "MIME-Version: 1.0\n" @@ -43,9 +43,12 @@ #, python-format msgid "\"action\" must be specified in options; allowed values are %s" msgstr "" +"\"action\" debe estar especificada en opciones; los valores permitidos son : " +"%s" msgid "\"role=subject\" or \"role=object\" must be specified in options" msgstr "" +"\"role=subject\" o \"role=object\" debe ser especificado en las opciones" #, python-format msgid "%(attr)s set to %(newvalue)s" @@ -61,7 +64,7 @@ #, python-format msgid "%(etype)s by %(author)s" -msgstr "" +msgstr "%(etype)s por %(author)s" #, python-format msgid "%(firstname)s %(surname)s" @@ -133,7 +136,7 @@ #, python-format msgid "%s could be supported" -msgstr "" +msgstr "%s podría ser mantenido" #, python-format msgid "%s error report" @@ -145,7 +148,7 @@ #, python-format msgid "%s relation should not be in mapped" -msgstr "" +msgstr "la relación %s no debería estar mapeada" #, python-format msgid "%s software version of the database" @@ -157,11 +160,11 @@ #, python-format msgid "'%s' action doesn't take any options" -msgstr "" +msgstr "la acción '%s' no acepta opciones" #, python-format -msgid "'%s' action require 'linkattr' option" -msgstr "" +msgid "'%s' action requires 'linkattr' option" +msgstr "la acción '%s' requiere una opción 'linkattr'" msgid "(UNEXISTANT EID)" msgstr "(EID INEXISTENTE" @@ -219,7 +222,7 @@ "div>" msgid "" -msgstr "" +msgstr "" msgid "?*" msgstr "0..1 0..n" @@ -357,28 +360,28 @@ msgstr "Relaciones" msgid "CWSource" -msgstr "" +msgstr "Fuente de datos" msgid "CWSourceHostConfig" -msgstr "" +msgstr "Configuración de Fuente" msgid "CWSourceHostConfig_plural" -msgstr "" +msgstr "Configuraciones de fuente" msgid "CWSourceSchemaConfig" -msgstr "" +msgstr "Configuraciones de Esquema de Fuente" msgid "CWSourceSchemaConfig_plural" -msgstr "" +msgstr "Configuraciones de Esquema de Fuente" msgid "CWSource_plural" -msgstr "" +msgstr "Fuentes de Datos" msgid "CWUniqueTogetherConstraint" -msgstr "" +msgstr "Restricción de Singularidad" msgid "CWUniqueTogetherConstraint_plural" -msgstr "" +msgstr "Restricciones de Singularidad" msgid "CWUser" msgstr "Usuario" @@ -436,7 +439,7 @@ #, python-format msgid "Data connection graph for %s" -msgstr "" +msgstr "Gráfica de conexión de datos para %s" msgid "Date" msgstr "Fecha" @@ -457,7 +460,7 @@ msgstr "Decimales" msgid "Detected problems" -msgstr "" +msgstr "Problemas detectados" msgid "Do you want to delete the following element(s) ?" msgstr "Desea eliminar el(los) elemento(s) siguiente(s)" @@ -479,7 +482,7 @@ msgstr "Entidades" msgid "Entity and relation supported by this source" -msgstr "" +msgstr "Entidades y relaciones aceptadas por esta fuente" msgid "ExternalUri" msgstr "Uri externo" @@ -506,7 +509,7 @@ msgstr "Recolector de basura en memoria" msgid "Got rhythm?" -msgstr "" +msgstr "Tenemos Ritmo?" msgid "Help" msgstr "Ayuda" @@ -533,7 +536,7 @@ msgstr "Clases buscadas" msgid "Manage" -msgstr "" +msgstr "Administración" msgid "Most referenced classes" msgstr "Clases más referenciadas" @@ -575,16 +578,16 @@ msgstr "Nueva definición de relación final" msgid "New CWSource" -msgstr "" +msgstr "Nueva fuente" msgid "New CWSourceHostConfig" -msgstr "" +msgstr "Nueva configuración de fuente" msgid "New CWSourceSchemaConfig" -msgstr "" +msgstr "Nueva parte de mapeo de fuente" msgid "New CWUniqueTogetherConstraint" -msgstr "" +msgstr "Nueva restricción de singularidad" msgid "New CWUser" msgstr "Agregar usuario" @@ -685,7 +688,7 @@ msgstr "Buscar" msgid "Site information" -msgstr "" +msgstr "Información del Sitio" msgid "SizeConstraint" msgstr "Restricción de tamaño" @@ -695,6 +698,9 @@ "authorized keys depending on the source's type, overriding values defined on " "the source." msgstr "" +"Configuración de la fuente por un \"host\" específico. Una clave=valor por " +"línea, las claves permitidas dependen del tipo de fuente. Estos valores son " +"prioritarios a los valores definidos en la fuente." msgid "Startup views" msgstr "Vistas de inicio" @@ -732,6 +738,18 @@ msgid "Submit bug report by mail" msgstr "Enviar este reporte por email" +msgid "TZDatetime" +msgstr "Fecha y hora internacional" + +msgid "TZDatetime_plural" +msgstr "Fechas y horas internacionales" + +msgid "TZTime" +msgstr "Hora internacional" + +msgid "TZTime_plural" +msgstr "Horas internacionales" + #, 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" @@ -780,16 +798,16 @@ msgstr "Esta definición de relación no final" msgid "This CWSource" -msgstr "" +msgstr "Esta fuente" msgid "This CWSourceHostConfig" -msgstr "" +msgstr "Esta configuración de fuente" msgid "This CWSourceSchemaConfig" -msgstr "" +msgstr "Esta parte de mapeo de fuente" msgid "This CWUniqueTogetherConstraint" -msgstr "" +msgstr "Esta restricción de singularidad" msgid "This CWUser" msgstr "Este usuario" @@ -844,6 +862,8 @@ msgid "URLs from which content will be imported. You can put one url per line" msgstr "" +"URLs desde el cual el contenido sera importado. Usted puede incluir un URL " +"por línea." msgid "UniqueConstraint" msgstr "Restricción de Unicidad" @@ -1007,10 +1027,10 @@ msgstr "Definición de relación" msgid "add CWSourceHostConfig cw_host_config_of CWSource object" -msgstr "" +msgstr "configuración del host" msgid "add CWUniqueTogetherConstraint constraint_of CWEType object" -msgstr "" +msgstr "restricción de singularidad" msgid "add CWUser in_group CWGroup object" msgstr "Usuario" @@ -1051,10 +1071,6 @@ msgid "add WorkflowTransition transition_of Workflow object" msgstr "Transición Workflow" -#, python-format -msgid "add a %s" -msgstr "" - msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" msgstr "Agregar un tipo de entidad" @@ -1068,10 +1084,10 @@ msgstr "Agregar un tipo de relación" msgid "add a CWSource" -msgstr "" +msgstr "agregar una fuente" msgid "add a CWSourceSchemaConfig" -msgstr "" +msgstr "agregar una parte de mapeo" msgctxt "inlined:CWUser.use_email.subject" msgid "add a EmailAddress" @@ -1142,7 +1158,7 @@ msgstr "permite definir un Workflow específico para una entidad" msgid "allowed options depends on the source type" -msgstr "" +msgstr "las opciones permitidas dependen del tipo de fuente" msgid "allowed transitions from this state" msgstr "transiciones autorizadas desde este estado" @@ -1198,11 +1214,11 @@ #, python-format msgid "archive for %(author)s" -msgstr "" +msgstr "archivo de %(author)s" #, python-format msgid "archive for %(month)s/%(year)s" -msgstr "" +msgstr "archivo del %(month)s/%(year)s" #, python-format msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)" @@ -1215,6 +1231,8 @@ msgid "attribute/relation can't be mapped, only entity and relation types" msgstr "" +"los atributos y las relaciones no pueden ser mapeados, solamente los tipos " +"de entidad y de relación" msgid "august" msgstr "Agosto" @@ -1326,7 +1344,7 @@ msgstr "no puede modificar el atributo %s" msgid "can't change this relation" -msgstr "" +msgstr "no puede modificar esta relación" #, python-format msgid "can't connect to source %s, some data may be missing" @@ -1340,10 +1358,10 @@ msgstr "no puede tener varias salidas en el mismo estado" msgid "can't mix dontcross and maycross options" -msgstr "" +msgstr "no puede mezclar las opciones dontcross y maycross" msgid "can't mix dontcross and write options" -msgstr "" +msgstr "no puede mezclar las opciones dontcross y write" #, python-format msgid "can't parse %(value)r (expected %(format)s)" @@ -1358,7 +1376,7 @@ "cardinalidad %(card)s" msgid "cancel" -msgstr "" +msgstr "anular" msgid "cancel select" msgstr "Cancelar la selección" @@ -1391,7 +1409,7 @@ msgstr "Ver la entidad creada" msgid "click here to see edited entity" -msgstr "" +msgstr "seleccione aquí para ver la entidad modificada" msgid "click on the box to cancel the deletion" msgstr "Seleccione la zona de edición para cancelar la eliminación" @@ -1475,15 +1493,15 @@ msgstr "condiciones" msgid "config" -msgstr "" +msgstr "configuración" msgctxt "CWSource" msgid "config" -msgstr "" +msgstr "configuración" msgctxt "CWSourceHostConfig" msgid "config" -msgstr "" +msgstr "configuración" msgid "config mode" msgstr "Modo de configuración" @@ -1516,18 +1534,18 @@ msgstr "Fábrica de restricciones" msgid "constraint_of" -msgstr "" +msgstr "restricción de" msgctxt "CWUniqueTogetherConstraint" msgid "constraint_of" -msgstr "" +msgstr "restricción de" msgid "constraint_of_object" -msgstr "" +msgstr "restringida por" msgctxt "CWEType" msgid "constraint_of_object" -msgstr "" +msgstr "restringida por" msgid "constraints" msgstr "Restricciones" @@ -1622,12 +1640,12 @@ msgid "" "creating CWSourceHostConfig (CWSourceHostConfig cw_host_config_of CWSource " "%(linkto)s)" -msgstr "" +msgstr "creación de una configuración host para la fuente %(linkto)s" msgid "" "creating CWUniqueTogetherConstraint (CWUniqueTogetherConstraint " "constraint_of CWEType %(linkto)s)" -msgstr "" +msgstr "creación de una restricción de singularidad en %(linkto)s" msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)" msgstr "Creación de un usuario para agregar al grupo %(linkto)s" @@ -1807,7 +1825,7 @@ msgstr "Metadatos de la Entidad" msgid "ctxcomponents_metadata_description" -msgstr "" +msgstr "espacio que incluye los metadatos de la entidad actual" msgid "ctxcomponents_possible_views_box" msgstr "Caja de Vistas Posibles" @@ -1844,10 +1862,10 @@ msgstr "Muestra las vistas de inicio de la aplicación" msgid "ctxcomponents_userstatus" -msgstr "" +msgstr "estado del usuario" msgid "ctxcomponents_userstatus_description" -msgstr "" +msgstr "establece el estado del usuario" msgid "ctxcomponents_wfhistory" msgstr "Histórico del workflow." @@ -1867,64 +1885,64 @@ msgstr "Workflow de" msgid "cw_for_source" -msgstr "" +msgstr "fuente" msgctxt "CWSourceSchemaConfig" msgid "cw_for_source" -msgstr "" +msgstr "fuente" msgid "cw_for_source_object" -msgstr "" +msgstr "elemento de mapeo" msgctxt "CWSource" msgid "cw_for_source_object" -msgstr "" +msgstr "elemento de mapeo" msgid "cw_host_config_of" -msgstr "" +msgstr "configuración del host de" msgctxt "CWSourceHostConfig" msgid "cw_host_config_of" -msgstr "" +msgstr "configuración del host de" msgid "cw_host_config_of_object" -msgstr "" +msgstr "tiene la configuración del host" msgctxt "CWSource" msgid "cw_host_config_of_object" -msgstr "" +msgstr "tiene la configuración del host" msgid "cw_schema" -msgstr "" +msgstr "esquema" msgctxt "CWSourceSchemaConfig" msgid "cw_schema" -msgstr "" +msgstr "esquema" msgid "cw_schema_object" -msgstr "" +msgstr "mapeado por" msgctxt "CWAttribute" msgid "cw_schema_object" -msgstr "" +msgstr "mapeado por" msgctxt "CWEType" msgid "cw_schema_object" -msgstr "" +msgstr "mapeado por" msgctxt "CWRType" msgid "cw_schema_object" -msgstr "" +msgstr "mapeado por" msgctxt "CWRelation" msgid "cw_schema_object" -msgstr "" +msgstr "mapeado por" msgid "cw_source" -msgstr "" +msgstr "desde la fuente de datos" msgid "cw_source_object" -msgstr "" +msgstr "entidades" msgid "cwetype-box" msgstr "Vista \"caja\"" @@ -1954,10 +1972,10 @@ msgstr "Permisos" msgid "cwsource-main" -msgstr "" +msgstr "descripción" msgid "cwsource-mapping" -msgstr "" +msgstr "mapeo" msgid "cwuri" msgstr "Uri Interna" @@ -1966,16 +1984,16 @@ msgstr "Url del repertorio de datos" msgid "data sources" -msgstr "" +msgstr "fuente de datos" msgid "data sources management" -msgstr "" +msgstr "administración de fuentes de datos" msgid "date" msgstr "Fecha" msgid "day" -msgstr "" +msgstr "día" msgid "deactivate" msgstr "Desactivar" @@ -2066,7 +2084,7 @@ msgstr "Define como salir de un sub-Workflow" msgid "defines a sql-level multicolumn unique index" -msgstr "" +msgstr "define un índice SQL único a través de varias columnas" msgid "" "defines what's the property is applied for. You must select this first to be " @@ -2312,6 +2330,8 @@ msgid "entity and relation types can't be mapped, only attributes or relations" msgstr "" +"los tipos de entidad y relación no pueden ser mapeados, solo los atributos y " +"las relaciones" msgid "entity copied" msgstr "Entidad copiada" @@ -2354,7 +2374,7 @@ msgstr "Actualización de la Entidad" msgid "error" -msgstr "" +msgstr "error" msgid "error while embedding page" msgstr "Error durante la inclusión de la página" @@ -2418,10 +2438,10 @@ msgstr "Faceta creada por" msgid "facets_cw_source-facet" -msgstr "" +msgstr "faceta \"fuente de datos\"" msgid "facets_cw_source-facet_description" -msgstr "" +msgstr "fuente de datos" msgid "facets_cwfinal-facet" msgstr "Faceta \"final\"" @@ -2574,7 +2594,7 @@ msgstr "Texto indexado" msgid "gc" -msgstr "" +msgstr "fuga de memoria" msgid "generic plot" msgstr "Gráfica Genérica" @@ -2637,10 +2657,10 @@ msgstr "Contiene el texto" msgid "header-left" -msgstr "" +msgstr "encabezado (izquierdo)" msgid "header-right" -msgstr "" +msgstr "encabezado (derecho)" msgid "hide filter form" msgstr "Esconder el filtro" @@ -2773,6 +2793,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" @@ -2807,6 +2830,8 @@ #, python-format msgid "inlined relation %(rtype)s of %(etype)s should be supported" msgstr "" +"la relación %(rtype)s del tipo de entidad %(etype)s debe ser aceptada " +"('inlined')" msgid "instance home" msgstr "Repertorio de la Instancia" @@ -2912,17 +2937,17 @@ msgstr "Fecha de la última modificación de una entidad " msgid "latest synchronization time" -msgstr "" +msgstr "fecha de la última sincronización" msgid "latest update on" msgstr "Actualizado el" msgid "latest_retrieval" -msgstr "" +msgstr "última sincronización" msgctxt "CWSource" msgid "latest_retrieval" -msgstr "" +msgstr "fecha de la última sincronización de la fuente" msgid "left" msgstr "izquierda" @@ -2980,7 +3005,7 @@ msgstr "Usuario" msgid "login / password" -msgstr "" +msgstr "usuario / contraseña" msgid "login or email" msgstr "Usuario o dirección de correo" @@ -2999,7 +3024,7 @@ msgstr "Informaciones Generales" msgid "main_tab" -msgstr "" +msgstr "descripción" msgid "mainvars" msgstr "Variables principales" @@ -3030,11 +3055,11 @@ msgstr "Marzo" msgid "match_host" -msgstr "" +msgstr "para el host" msgctxt "CWSourceHostConfig" msgid "match_host" -msgstr "" +msgstr "para el host" msgid "maximum number of characters in short description" msgstr "Máximo de caracteres en las descripciones cortas" @@ -3062,7 +3087,7 @@ msgstr "Parámetros faltantes a la entidad %s" msgid "modification" -msgstr "" +msgstr "modificación" msgid "modification_date" msgstr "Fecha de modificación" @@ -3074,7 +3099,7 @@ msgstr "Lunes" msgid "month" -msgstr "" +msgstr "mes" msgid "more actions" msgstr "Más acciones" @@ -3121,7 +3146,7 @@ msgctxt "CWSource" msgid "name" -msgstr "" +msgstr "nombre" msgctxt "State" msgid "name" @@ -3150,7 +3175,7 @@ "selección de ser necesario (separarlas con comas)" msgid "name of the source" -msgstr "" +msgstr "nombre de la fuente" msgid "name or identifier of the permission" msgstr "Nombre o identificador del permiso" @@ -3267,7 +3292,7 @@ msgctxt "CWSourceSchemaConfig" msgid "options" -msgstr "" +msgstr "opciones" msgid "order" msgstr "Orden" @@ -3308,14 +3333,16 @@ msgstr "Página no encontrada." msgid "parser" -msgstr "" +msgstr "analizador (parser)" msgctxt "CWSource" msgid "parser" -msgstr "" +msgstr "analizador (parser)" msgid "parser to use to extract entities from content retrieved at given URLs." msgstr "" +"analizador (parser) que sirve para extraer entidades y relaciones del " +"contenido recuperado de las URLs." msgid "password" msgstr "Contraseña" @@ -3396,7 +3423,7 @@ msgstr "Dirección principal de correo electrónico de" msgid "profile" -msgstr "" +msgstr "perfil" msgid "progress" msgstr "Progreso" @@ -3414,7 +3441,7 @@ msgstr "Permisos" msgid "rdf" -msgstr "" +msgstr "rdf" msgid "read" msgstr "Lectura" @@ -3447,6 +3474,8 @@ msgid "regexp matching host(s) to which this config applies" msgstr "" +"expresión regular de los nombres de hosts a los cuales esta configuración " +"aplica" msgid "registry" msgstr "Registro" @@ -3469,18 +3498,24 @@ "relation %(rtype)s with %(etype)s as %(role)s is supported but no target " "type supported" msgstr "" +"la relación %(rtype)s con %(etype)s como %(role)s es aceptada pero ningún " +"tipo target es aceptado" #, python-format msgid "" "relation %(type)s with %(etype)s as %(role)s and target type %(target)s is " "mandatory but not supported" msgstr "" +"la relación %(type)s con %(etype)s como %(role)s y tipo objetivo %(target)s " +"es obligatoria pero no mantenida" #, python-format msgid "" "relation %s is supported but none if its definitions matches supported " "entities" msgstr "" +"la relación %s es aceptada pero ninguna de sus definiciones corresponden a " +"los tipos de entidades aceptadas" msgid "relation add" msgstr "Agregar Relación" @@ -3507,21 +3542,21 @@ msgstr "Definición de Relaciones" msgid "relations" -msgstr "" +msgstr "relaciones" msgctxt "CWUniqueTogetherConstraint" msgid "relations" -msgstr "" +msgstr "relaciones" msgid "relations deleted" msgstr "Relaciones Eliminadas" msgid "relations_object" -msgstr "" +msgstr "relaciones de" msgctxt "CWRType" msgid "relations_object" -msgstr "" +msgstr "relaciones de" msgid "relative url of the bookmarked page" msgstr "Url relativa de la página" @@ -3633,7 +3668,7 @@ msgstr "Seguridad" msgid "see more" -msgstr "" +msgstr "ver más" msgid "see them all" msgstr "Ver todos" @@ -3728,7 +3763,7 @@ msgstr "Una propiedad específica al Sistema no puede ser propia al usuario" msgid "siteinfo" -msgstr "" +msgstr "información" msgid "some errors occurred:" msgstr "Algunos errores encontrados :" @@ -3744,6 +3779,8 @@ "source's configuration. One key=value per line, authorized keys depending on " "the source's type" msgstr "" +"configuración de fuentes. Una clave=valor por línea, las claves permitidas " +"dependen del tipo de la fuente." msgid "sparql xml" msgstr "XML Sparql" @@ -3767,7 +3804,7 @@ #, python-format msgid "specifying %s is mandatory" -msgstr "" +msgstr "especificar %s es obligatorio" msgid "startup views" msgstr "Vistas de inicio" @@ -3899,7 +3936,7 @@ msgstr "Simétrico" msgid "synchronization-interval must be greater than 1 minute" -msgstr "" +msgstr "synchronization-interval debe ser mayor a 1 minuto" msgid "table" msgstr "Tabla" @@ -3933,6 +3970,7 @@ msgid "the system source has its configuration stored on the file-system" msgstr "" +"el sistema fuente tiene su configuración almacenada en el sistema de archivos" #, python-format msgid "the value \"%s\" is already used, use another one" @@ -3945,13 +3983,13 @@ msgstr "Esta Entidad es propiedad de" msgid "this parser doesn't use a mapping" -msgstr "" +msgstr "este analizador (parser) no utiliza mapeo" msgid "this resource does not exist" msgstr "Este recurso no existe" msgid "this source doesn't use a mapping" -msgstr "" +msgstr "esta fuente no utiliza mapeo" msgid "thursday" msgstr "Jueves" @@ -4022,7 +4060,7 @@ msgstr "Transición hacia este Estado" msgid "today" -msgstr "" +msgstr "hoy" msgid "todo_by" msgstr "Asignada a" @@ -4031,11 +4069,11 @@ msgstr "Cambiar valor" msgid "tr_count" -msgstr "" +msgstr "n° de transición" msgctxt "TrInfo" msgid "tr_count" -msgstr "" +msgstr "n° de transición" msgid "transaction undoed" msgstr "Transacciones Anuladas" @@ -4090,7 +4128,7 @@ msgctxt "CWSource" msgid "type" -msgstr "" +msgstr "tipo" msgctxt "Transition" msgid "type" @@ -4104,7 +4142,7 @@ msgstr "Escriba aquí su consulta en Sparql" msgid "type of the source" -msgstr "" +msgstr "tipo de la fuente" msgid "ui" msgstr "Interfaz Genérica" @@ -4159,18 +4197,18 @@ #, python-format msgid "unknown option(s): %s" -msgstr "" +msgstr "opcion(es) desconocida(s): %s" #, python-format msgid "unknown options %s" -msgstr "" +msgstr "opciones desconocidas: %s" #, python-format msgid "unknown property key %s" msgstr "Clave de Propiedad desconocida: %s" msgid "unknown source type" -msgstr "" +msgstr "tipo de fuente desconocida" msgid "unknown vocabulary:" msgstr "Vocabulario desconocido: " @@ -4225,11 +4263,11 @@ msgstr "URI" msgid "url" -msgstr "" +msgstr "url" msgctxt "CWSource" msgid "url" -msgstr "" +msgstr "url" msgid "use template languages" msgstr "Utilizar plantillas de lenguaje" @@ -4297,10 +4335,10 @@ msgstr "Usuarios" msgid "users and groups" -msgstr "" +msgstr "usuarios y grupos" msgid "users and groups management" -msgstr "" +msgstr "usuarios y grupos de administradores" msgid "users using this bookmark" msgstr "Usuarios utilizando este Favorito" @@ -4377,13 +4415,13 @@ #, python-format msgid "violates unique_together constraints (%s)" -msgstr "" +msgstr "viola el principio (o restricción) de singularidad (%s)" msgid "visible" msgstr "Visible" msgid "warning" -msgstr "" +msgstr "atención" msgid "we are not yet ready to handle this query" msgstr "Aún no podemos manejar este tipo de consulta Sparql" @@ -4482,77 +4520,16 @@ #, python-format msgid "you may want to specify something for %s" -msgstr "" +msgstr "usted desea quizás especificar algo para la relación %s" msgid "you should probably delete that property" -msgstr "Debería probablamente suprimir esta propriedad" +msgstr "probablamente debería suprimir esta propriedad" #, python-format msgid "you should un-inline relation %s which is supported and may be crossed " msgstr "" - -#~ msgid "Attributes with non default permissions:" -#~ msgstr "Atributos con permisos no estándares" - -#~ msgid "Entity types" -#~ msgstr "Tipos de entidades" - -#~ msgid "Index" -#~ msgstr "Índice" - -#~ msgid "Permissions for entity types" -#~ msgstr "Permisos por tipos de entidad" - -#~ msgid "Permissions for relations" -#~ msgstr "Permisos por las relaciones" - -#~ msgid "Relation types" -#~ msgstr "Tipos de relación" - -#~ msgid "am/pm calendar (month)" -#~ msgstr "calendario am/pm (mes)" - -#~ msgid "am/pm calendar (semester)" -#~ msgstr "calendario am/pm (semestre)" - -#~ msgid "am/pm calendar (week)" -#~ msgstr "calendario am/pm (semana)" - -#~ msgid "am/pm calendar (year)" -#~ msgstr "calendario am/pm (año)" - -#~ msgid "application entities" -#~ msgstr "Entidades de la aplicación" - -#~ msgid "calendar (month)" -#~ msgstr "calendario (mensual)" - -#~ msgid "calendar (semester)" -#~ msgstr "calendario (semestral)" - -#~ msgid "calendar (week)" -#~ msgstr "calendario (semanal)" - -#~ msgid "calendar (year)" -#~ msgstr "calendario (anual)" - -#~ msgid "create an index page" -#~ msgstr "Crear una página de inicio" - -#~ msgid "edit the index page" -#~ msgstr "Modificar la página de inicio" - -#~ msgid "schema entities" -#~ msgstr "Entidades del esquema" - -#~ msgid "schema-security" -#~ msgstr "Seguridad" - -#~ msgid "system entities" -#~ msgstr "Entidades del sistema" - -#~ msgid "timestamp of the latest source synchronization." -#~ msgstr "Fecha de la última sincronización de la fuente." - -#~ msgid "up" -#~ msgstr "Arriba" +"usted debe quitar la puesta en línea de la relación %s que es aceptada y " +"puede ser cruzada" + +#~ msgid "add a %s" +#~ msgstr "agregar un %s" diff -r dc319ece0bd6 -r c2b29631c1a3 i18n/fr.po --- a/i18n/fr.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/fr.po Mon May 16 16:25:33 2011 +0200 @@ -162,7 +162,7 @@ msgstr "l'action '%s' ne prend pas d'option" #, python-format -msgid "'%s' action require 'linkattr' option" +msgid "'%s' action requires 'linkattr' option" msgstr "l'action '%s' nécessite une option 'linkattr'" msgid "(UNEXISTANT EID)" @@ -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" @@ -1057,10 +1069,6 @@ msgid "add WorkflowTransition transition_of Workflow object" msgstr "transition workflow" -#, python-format -msgid "add a %s" -msgstr "ajouter un %s" - msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" msgstr "ajouter un type d'entité sujet" @@ -1934,10 +1942,10 @@ msgstr "mappé par" msgid "cw_source" -msgstr "from data source" +msgstr "source" msgid "cw_source_object" -msgstr "entities" +msgstr "entités" msgid "cwetype-box" msgstr "vue \"boîte\"" @@ -2783,6 +2791,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" @@ -4535,6 +4546,9 @@ #~ msgid "Relation types" #~ msgstr "Types de relation" +#~ msgid "add a %s" +#~ msgstr "ajouter un %s" + #~ msgid "am/pm calendar (month)" #~ msgstr "calendrier am/pm (mois)" diff -r dc319ece0bd6 -r c2b29631c1a3 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 Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,2 @@ +add_entity_type('TZDatetime') +add_entity_type('TZTime') diff -r dc319ece0bd6 -r c2b29631c1a3 misc/scripts/chpasswd.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/scripts/chpasswd.py Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,48 @@ +# 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. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . + +import sys +import getpass + +from cubicweb import Binary +from cubicweb.server.utils import crypt_password + + +if __args__: + login = __args__.pop() +else: + login = raw_input("login ? ") + +rset = rql('Any U WHERE U is CWUser, U login %(login)s', {'login': login}) + +if len(rset) != 1: + sys.exit("user '%s' does not exist!" % login) + +pass1 = getpass.getpass(prompt='Enter new password ? ') +pass2 = getpass.getpass(prompt='Confirm ? ') + +if pass1 != pass2: + sys.exit("passwords don't match!") + +crypted = crypt_password(pass1) + +cwuser = rset.get_entity(0,0) +cwuser.set_attributes(upassword=Binary(crypted)) +commit() + +print("password updated.") diff -r dc319ece0bd6 -r c2b29631c1a3 rqlrewrite.py diff -r dc319ece0bd6 -r c2b29631c1a3 schema.py --- a/schema.py Mon May 16 16:24:00 2011 +0200 +++ b/schema.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 selectors.py --- a/selectors.py Mon May 16 16:24:00 2011 +0200 +++ b/selectors.py Mon May 16 16:25:33 2011 +0200 @@ -196,7 +196,7 @@ from warnings import warn from operator import eq -from logilab.common.deprecation import class_renamed +from logilab.common.deprecation import class_renamed, deprecated from logilab.common.compat import all, any from logilab.common.interface import implements as implements_iface @@ -1160,9 +1160,12 @@ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity lookup / score rules according to the input context. """ - def __init__(self, expression, once_is_enough=False): + def __init__(self, expression, once_is_enough=False, user_condition=False): super(rql_condition, self).__init__(once_is_enough) - if 'U' in frozenset(split_expression(expression)): + self.user_condition = user_condition + if user_condition: + rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression + elif 'U' in frozenset(split_expression(expression)): rql = 'Any COUNT(X) WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression else: rql = 'Any COUNT(X) WHERE X eid %%(x)s, %s' % expression @@ -1171,13 +1174,30 @@ def __str__(self): return '%s(%r)' % (self.__class__.__name__, self.rql) - def score(self, req, rset, row, col): + @lltrace + def __call__(self, cls, req, **kwargs): + if self.user_condition: + try: + return req.execute(self.rql, {'u': req.user.eid})[0][0] + except Unauthorized: + return 0 + else: + return super(rql_condition, self).__call__(cls, req, **kwargs) + + def _score(self, req, eid): try: - return req.execute(self.rql, {'x': rset[row][col], - 'u': req.user.eid})[0][0] + return req.execute(self.rql, {'x': eid, 'u': req.user.eid})[0][0] except Unauthorized: return 0 + def score(self, req, rset, row, col): + return self._score(req, rset[row][col]) + + def score_entity(self, entity): + return self._score(entity._cw, entity.eid) + + +# workflow selectors ########################################################### class is_in_state(score_entity): """Return 1 if entity is in one of the states given as argument list @@ -1189,9 +1209,8 @@ * you must use the latest tr info thru the workflow adapter for repository side checking of the current state - In debug mode, this selector can raise: - :raises: :exc:`ValueError` for unknown states names - (etype workflow only not checked in custom workflow) + In debug mode, this selector can raise :exc:`ValueError` for unknown states names + (only checked on entities without a custom workflow) :rtype: int """ @@ -1231,40 +1250,46 @@ ','.join(str(s) for s in self.expected)) -class on_transition(is_in_state): - """Return 1 if entity is in one of the transitions given as argument list - - Especially useful to match passed transition to enable notifications when - your workflow allows several transition to the same states. +def on_fire_transition(etype, tr_name, from_state_name=None): + """Return 1 when entity of the type `etype` is going through transition of + the name `tr_name`. If `from_state_name` is specified, this selector will + also check the incoming state. - Note that if workflow `change_state` adapter method is used, this selector - will not be triggered. - - You should use this instead of your own :class:`score_entity` selector to - avoid some gotchas: - - * possible views gives a fake entity with no state - * you must use the latest tr info thru the workflow adapter for repository - side checking of the current state + You should use this selector on 'after_add_entity' hook, since it's actually + looking for addition of `TrInfo` entities. Hence in the hook, `self.entity` + will reference the matching `TrInfo` entity, allowing to get all the + transition details (including the entity to which is applied the transition + but also its original state, transition, destination state, user...). See + :class:`cubicweb.entities.wfobjs.TrInfo` for more information. + """ + def match_etype_and_transition(trinfo): + # take care trinfo.transition is None when calling change_state + return (trinfo.transition and trinfo.transition.name == tr_name + # is_instance() first two arguments are 'cls' (unused, so giving + # None is fine) and the request/session + and is_instance(etype)(None, trinfo._cw, entity=trinfo.for_entity)) - In debug mode, this selector can raise: - :raises: :exc:`ValueError` for unknown transition names - (etype workflow only not checked in custom workflow) + return is_instance('TrInfo') & score_entity(match_etype_and_transition) + - :rtype: int +class match_transition(ExpectedValueSelector): + """Return 1 if `transition` argument is found in the input context which has + a `.name` attribute matching one of the expected names given to the + initializer. + + This selector is expected to be used to customise the status change form in + the web ui. """ - def _score(self, adapted): - trinfo = adapted.latest_trinfo() - if trinfo and trinfo.by_transition: - return trinfo.by_transition[0].name in self.expected - - def _validate(self, adapted): - wf = adapted.current_workflow - valid = [n.name for n in wf.reverse_transition_of] - unknown = sorted(self.expected.difference(valid)) - if unknown: - raise ValueError("%s: unknown transition(s): %s" - % (wf.name, ",".join(unknown))) + @lltrace + def __call__(self, cls, req, transition=None, **kwargs): + # XXX check this is a transition that apply to the object? + if transition is None: + treid = req.form.get('treid', None) + if treid: + transition = req.entity_from_eid(treid) + if transition is not None and getattr(transition, 'name', None) in self.expected: + return 1 + return 0 # logged user selectors ######################################################## @@ -1504,23 +1529,6 @@ # Other selectors ############################################################## -# XXX deprecated ? maybe use on_transition selector instead ? -class match_transition(ExpectedValueSelector): - """Return 1 if `transition` argument is found in the input context which has - a `.name` attribute matching one of the expected names given to the - initializer. - """ - @lltrace - def __call__(self, cls, req, transition=None, **kwargs): - # XXX check this is a transition that apply to the object? - if transition is None: - treid = req.form.get('treid', None) - if treid: - transition = req.entity_from_eid(treid) - if transition is not None and getattr(transition, 'name', None) in self.expected: - return 1 - return 0 - class match_exception(ExpectedValueSelector): """Return 1 if a view is specified an as its registry id is in one of the @@ -1544,6 +1552,47 @@ ## deprecated stuff ############################################################ + +class on_transition(is_in_state): + """Return 1 if entity is in one of the transitions given as argument list + + Especially useful to match passed transition to enable notifications when + your workflow allows several transition to the same states. + + Note that if workflow `change_state` adapter method is used, this selector + will not be triggered. + + You should use this instead of your own :class:`score_entity` selector to + avoid some gotchas: + + * possible views gives a fake entity with no state + * you must use the latest tr info thru the workflow adapter for repository + side checking of the current state + + In debug mode, this selector can raise: + :raises: :exc:`ValueError` for unknown transition names + (etype workflow only not checked in custom workflow) + + :rtype: int + """ + @deprecated('[3.12] on_transition is deprecated, you should rather use ' + 'on_fire_transition(etype, trname)') + def __init__(self, *expected): + super(on_transition, self).__init__(*expected) + + def _score(self, adapted): + trinfo = adapted.latest_trinfo() + if trinfo and trinfo.by_transition: + return trinfo.by_transition[0].name in self.expected + + def _validate(self, adapted): + wf = adapted.current_workflow + valid = [n.name for n in wf.reverse_transition_of] + unknown = sorted(self.expected.difference(valid)) + if unknown: + raise ValueError("%s: unknown transition(s): %s" + % (wf.name, ",".join(unknown))) + entity_implements = class_renamed('entity_implements', is_instance) class _but_etype(EntitySelector): diff -r dc319ece0bd6 -r c2b29631c1a3 server/hook.py --- a/server/hook.py Mon May 16 16:24:00 2011 +0200 +++ b/server/hook.py Mon May 16 16:25:33 2011 +0200 @@ -270,13 +270,17 @@ 'session_open', 'session_close')) ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS -def _iter_kwargs(entities, kwargs): - if not entities: +def _iter_kwargs(entities, eids_from_to, kwargs): + if not entities and not eids_from_to: yield kwargs - else: + elif entities: for entity in entities: kwargs['entity'] = entity yield kwargs + else: + for subject, object in eids_from_to: + kwargs.update({'eidfrom': subject, 'eidto': object}) + yield kwargs class HooksRegistry(CWRegistry): @@ -304,12 +308,19 @@ if 'entities' in kwargs: assert 'entity' not in kwargs, \ 'can\'t pass "entities" and "entity" arguments simultaneously' + assert 'eids_from_to' not in kwargs, \ + 'can\'t pass "entities" and "eids_from_to" arguments simultaneously' entities = kwargs.pop('entities') + eids_from_to = [] + elif 'eids_from_to' in kwargs: + entities = [] + eids_from_to = kwargs.pop('eids_from_to') else: entities = [] + eids_from_to = [] # by default, hooks are executed with security turned off with security_enabled(session, read=False): - for _kwargs in _iter_kwargs(entities, kwargs): + for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs): hooks = sorted(self.possible_objects(session, **_kwargs), key=lambda x: x.order) with security_enabled(session, write=False): diff -r dc319ece0bd6 -r c2b29631c1a3 server/migractions.py --- a/server/migractions.py Mon May 16 16:24:00 2011 +0200 +++ b/server/migractions.py Mon May 16 16:25:33 2011 +0200 @@ -162,7 +162,7 @@ # server specific migration methods ######################################## - def backup_database(self, backupfile=None, askconfirm=True): + def backup_database(self, backupfile=None, askconfirm=True, format='native'): config = self.config repo = self.repo_connect() # paths @@ -185,16 +185,24 @@ # backup tmpdir = tempfile.mkdtemp() try: + failed = False for source in repo.sources: try: - source.backup(osp.join(tmpdir, source.uri), self.confirm) + source.backup(osp.join(tmpdir, source.uri), self.confirm, format=format) except Exception, ex: print '-> error trying to backup %s [%s]' % (source.uri, ex) if not self.confirm('Continue anyway?', default='n'): raise SystemExit(1) else: - break - else: + failed = True + with open(osp.join(tmpdir, 'format.txt'), 'w') as format_file: + format_file.write('%s\n' % format) + with open(osp.join(tmpdir, 'versions.txt'), 'w') as version_file: + versions = repo.get_versions() + for cube, version in versions.iteritems(): + version_file.write('%s %s\n' % (cube, version)) + + if not failed: bkup = tarfile.open(backupfile, 'w|gz') for filename in os.listdir(tmpdir): bkup.add(osp.join(tmpdir, filename), filename) @@ -207,7 +215,7 @@ shutil.rmtree(tmpdir) def restore_database(self, backupfile, drop=True, systemonly=True, - askconfirm=True): + askconfirm=True, format='native'): # check if not osp.exists(backupfile): raise ExecutionError("Backup file %s doesn't exist" % backupfile) @@ -229,13 +237,18 @@ bkup = tarfile.open(backupfile, 'r|gz') bkup.extractall(path=tmpdir) bkup.close() + if osp.isfile(osp.join(tmpdir, 'format.txt')): + with open(osp.join(tmpdir, 'format.txt')) as format_file: + written_format = format_file.readline().strip() + if written_format in ('portable', 'native'): + format = written_format self.config.open_connections_pools = False repo = self.repo_connect() for source in repo.sources: if systemonly and source.uri != 'system': continue try: - source.restore(osp.join(tmpdir, source.uri), self.confirm, drop) + source.restore(osp.join(tmpdir, source.uri), self.confirm, drop, format) except Exception, exc: print '-> error trying to restore %s [%s]' % (source.uri, exc) if not self.confirm('Continue anyway?', default='n'): @@ -438,7 +451,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 +771,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() @@ -1248,6 +1262,12 @@ self.commit() return wf + def cmd_get_workflow_for(self, etype): + """return default workflow for the given entity type""" + rset = self.rqlexec('Workflow X WHERE ET default_workflow X, ET name %(et)s', + {'et': etype}) + return rset.get_entity(0, 0) + # XXX remove once cmd_add_[state|transition] are removed def _get_or_create_wf(self, etypes): if not isinstance(etypes, (list, tuple)): diff -r dc319ece0bd6 -r c2b29631c1a3 server/msplanner.py --- a/server/msplanner.py Mon May 16 16:24:00 2011 +0200 +++ b/server/msplanner.py Mon May 16 16:25:33 2011 +0200 @@ -483,7 +483,12 @@ else: var = vref.variable for rel in var.stinfo['relations'] - var.stinfo['rhsrelations']: - if rel.r_type in ('eid', 'name'): + # skip neged eid relation since it's the kind of query + # generated when clearing old value of '?1" relation, + # cw_source included. See + # unittest_ldapuser.test_copy_to_system_source + if rel.r_type == 'name' or \ + (rel.r_type == 'eid' and not rel.neged(strict=True)): if rel.r_type == 'eid': slist = sourceeids else: diff -r dc319ece0bd6 -r c2b29631c1a3 server/querier.py --- a/server/querier.py Mon May 16 16:24:00 2011 +0200 +++ b/server/querier.py Mon May 16 16:25:33 2011 +0200 @@ -556,6 +556,8 @@ def insert_relation_defs(self): session = self.session repo = session.repo + edited_entities = {} + relations = {} for subj, rtype, obj in self.relation_defs(): # if a string is given into args instead of an int, we get it here if isinstance(subj, basestring): @@ -567,12 +569,21 @@ elif not isinstance(obj, (int, long)): obj = obj.entity.eid if repo.schema.rschema(rtype).inlined: - entity = session.entity_from_eid(subj) - edited = EditedEntity(entity) + if subj not in edited_entities: + entity = session.entity_from_eid(subj) + edited = EditedEntity(entity) + edited_entities[subj] = edited + else: + edited = edited_entities[subj] edited.edited_attribute(rtype, obj) - repo.glob_update_entity(session, edited) else: - repo.glob_add_relation(session, subj, rtype, obj) + if rtype in relations: + relations[rtype].append((subj, obj)) + else: + relations[rtype] = [(subj, obj)] + repo.glob_add_relations(session, relations) + for edited in edited_entities.itervalues(): + repo.glob_update_entity(session, edited) class QuerierHelper(object): diff -r dc319ece0bd6 -r c2b29631c1a3 server/repository.py --- a/server/repository.py Mon May 16 16:24:00 2011 +0200 +++ b/server/repository.py Mon May 16 16:25:33 2011 +0200 @@ -59,13 +59,18 @@ security_enabled from cubicweb.server.ssplanner import EditedEntity +NO_CACHE_RELATIONS = set( [('require_permission', 'object'), + ('owned_by', 'object'), + ('created_by', 'object'), + ('cw_source', 'object'), + ]) def prefill_entity_caches(entity, relations): session = entity._cw # prefill entity relation caches for rschema in entity.e_schema.subject_relations(): rtype = str(rschema) - if rtype in schema.VIRTUAL_RTYPES: + if rtype in schema.VIRTUAL_RTYPES or (rtype, 'subject') in NO_CACHE_RELATIONS: continue if rschema.final: entity.cw_attr_cache.setdefault(rtype, None) @@ -74,7 +79,7 @@ session.empty_rset()) for rschema in entity.e_schema.object_relations(): rtype = str(rschema) - if rtype in schema.VIRTUAL_RTYPES: + if rtype in schema.VIRTUAL_RTYPES or (rtype, 'object') in NO_CACHE_RELATIONS: continue entity.cw_set_relation_cache(rtype, 'object', session.empty_rset()) # set inlined relation cache before call to after_add_entity @@ -178,8 +183,8 @@ # information (eg dump/restore/...) config._cubes = () # only load hooks and entity classes in the registry - config.cube_appobject_path = set(('hooks', 'entities')) - config.cubicweb_appobject_path = set(('hooks', 'entities')) + config.__class__.cube_appobject_path = set(('hooks', 'entities')) + config.__class__.cubicweb_appobject_path = set(('hooks', 'entities')) self.set_schema(config.load_schema()) config['connections-pool-size'] = 1 # will be reinitialized later from cubes found in the database @@ -1375,19 +1380,45 @@ def glob_add_relation(self, session, subject, rtype, object): """add a relation to the repository""" - if server.DEBUG & server.DBG_REPO: - print 'ADD relation', subject, rtype, object - source = self.locate_relation_source(session, subject, rtype, object) - if source.should_call_hooks: - del_existing_rel_if_needed(session, subject, rtype, object) - self.hm.call_hooks('before_add_relation', session, - eidfrom=subject, rtype=rtype, eidto=object) - source.add_relation(session, subject, rtype, object) - rschema = self.schema.rschema(rtype) - session.update_rel_cache_add(subject, rtype, object, rschema.symmetric) - if source.should_call_hooks: - self.hm.call_hooks('after_add_relation', session, - eidfrom=subject, rtype=rtype, eidto=object) + self.glob_add_relations(session, {rtype: [(subject, object)]}) + + def glob_add_relations(self, session, relations): + """add several relations to the repository + + relations is a dictionary rtype: [(subj_eid, obj_eid), ...] + """ + sources = {} + for rtype, eids_subj_obj in relations.iteritems(): + if server.DEBUG & server.DBG_REPO: + for subject, object in relations: + print 'ADD relation', subject, rtype, object + for subject, object in eids_subj_obj: + source = self.locate_relation_source(session, subject, rtype, object) + if source not in sources: + relations_by_rtype = {} + sources[source] = relations_by_rtype + else: + relations_by_rtype = sources[source] + if rtype in relations_by_rtype: + relations_by_rtype[rtype].append((subject, object)) + else: + relations_by_rtype[rtype] = [(subject, object)] + for source, relations_by_rtype in sources.iteritems(): + if source.should_call_hooks: + for rtype, source_relations in relations_by_rtype.iteritems(): + for subject, object in source_relations: + del_existing_rel_if_needed(session, subject, rtype, object) + self.hm.call_hooks('before_add_relation', session, + rtype=rtype, eids_from_to=source_relations) + for rtype, source_relations in relations_by_rtype.iteritems(): + source.add_relations(session, rtype, source_relations) + rschema = self.schema.rschema(rtype) + for subject, object in source_relations: + session.update_rel_cache_add(subject, rtype, object, rschema.symmetric) + if source.should_call_hooks: + for rtype, source_relations in relations_by_rtype.iteritems(): + self.hm.call_hooks('after_add_relation', session, + rtype=rtype, eids_from_to=source_relations) def glob_delete_relation(self, session, subject, rtype, object): """delete a relation from the repository""" diff -r dc319ece0bd6 -r c2b29631c1a3 server/rqlannotation.py --- a/server/rqlannotation.py Mon May 16 16:24:00 2011 +0200 +++ b/server/rqlannotation.py Mon May 16 16:25:33 2011 +0200 @@ -78,18 +78,19 @@ continue lhs, rhs = rel.get_parts() onlhs = ref is lhs + role = 'subject' if onlhs else 'object' if rel.r_type == 'eid': if not (onlhs and len(stinfo['relations']) > 1): break if not stinfo['constnode']: - joins.add(rel) + joins.add( (rel, role) ) continue elif rel.r_type == 'identity': # identity can't be used as principal, so check other relation are used # XXX explain rhs.operator == '=' if rhs.operator != '=' or len(stinfo['relations']) <= 1: #(stinfo['constnode'] and rhs.operator == '='): break - joins.add(rel) + joins.add( (rel, role) ) continue rschema = getrschema(rel.r_type) if rel.optional: @@ -116,7 +117,7 @@ # need join anyway if the variable appears in a final or # inlined relation break - joins.add(rel) + joins.add( (rel, role) ) continue if not stinfo['constnode']: if rschema.inlined and rel.neged(strict=True): @@ -129,7 +130,7 @@ break elif rschema.symmetric and stinfo['selected']: break - joins.add(rel) + joins.add( (rel, role) ) else: # if there is at least one ambigous relation and no other to # restrict types, can't be invariant since we need to filter out @@ -169,10 +170,15 @@ diffscope_rels = {} ored_rels = set() diffscope_rels = set() - for rel in _sort(relations): + for rel, role in _sort(relations): # note: only eid and has_text among all final relations may be there if rel.r_type in ('eid', 'identity'): continue + if rel.optional is not None and len(relations) > 1: + if role == 'subject' and rel.optional == 'right': + continue + if role == 'object' and rel.optional == 'left': + continue if rel.ored(traverse_scope=True): ored_rels.add(rel) elif rel.scope is scope: diff -r dc319ece0bd6 -r c2b29631c1a3 server/schemaserial.py --- a/server/schemaserial.py Mon May 16 16:24:00 2011 +0200 +++ b/server/schemaserial.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 server/serverctl.py --- a/server/serverctl.py Mon May 16 16:24:00 2011 +0200 +++ b/server/serverctl.py Mon May 16 16:25:33 2011 +0200 @@ -691,19 +691,20 @@ 'Continue anyway?' % filename): raise ExecutionError('Error while deleting remote dump at /tmp/%s' % filename) -def _local_dump(appid, output): + +def _local_dump(appid, output, format='native'): config = ServerConfiguration.config_for(appid) config.quick_start = True mih = config.migration_handler(connect=False, verbosity=1) - mih.backup_database(output, askconfirm=False) + mih.backup_database(output, askconfirm=False, format=format) mih.shutdown() -def _local_restore(appid, backupfile, drop, systemonly=True): +def _local_restore(appid, backupfile, drop, systemonly=True, format='native'): config = ServerConfiguration.config_for(appid) config.verbosity = 1 # else we won't be asked for confirmation on problems config.quick_start = True mih = config.migration_handler(connect=False, verbosity=1) - mih.restore_database(backupfile, drop, systemonly, askconfirm=False) + mih.restore_database(backupfile, drop, systemonly, askconfirm=False, format=format) repo = mih.repo_connect() # version of the database dbversions = repo.get_versions() @@ -777,6 +778,12 @@ 'default' : False, 'help': 'Use sudo on the remote host.'} ), + ('format', + {'short': 'f', 'default': 'native', 'type': 'choice', + 'choices': ('native', 'portable'), + 'help': '"native" format uses db backend utilities to dump the database. ' + '"portable" format uses a database independent format'} + ), ) def run(self, args): @@ -785,7 +792,9 @@ host, appid = appid.split(':') _remote_dump(host, appid, self.config.output, self.config.sudo) else: - _local_dump(appid, self.config.output) + _local_dump(appid, self.config.output, format=self.config.format) + + class DBRestoreCommand(Command): @@ -811,13 +820,33 @@ 'instance data. In that case, is expected to be the ' 'timestamp of the backup to restore, not a file'} ), + ('format', + {'short': 'f', 'default': 'native', 'type': 'choice', + 'choices': ('native', 'portable'), + 'help': 'the format used when dumping the database'}), ) def run(self, args): appid, backupfile = args + if self.config.format == 'portable': + # we need to ensure a DB exist before restoring from portable format + if not self.config.no_drop: + try: + CWCTL.run(['db-create', '--automatic', appid]) + except SystemExit, exc: + # continue if the command exited with status 0 (success) + if exc.code: + raise _local_restore(appid, backupfile, drop=not self.config.no_drop, - systemonly=not self.config.restore_all) + systemonly=not self.config.restore_all, + format=self.config.format) + if self.config.format == 'portable': + try: + CWCTL.run(['db-rebuild-fti', appid]) + except SystemExit, exc: + if exc.code: + raise class DBCopyCommand(Command): @@ -850,6 +879,12 @@ 'default' : False, 'help': 'Use sudo on the remote host.'} ), + ('format', + {'short': 'f', 'default': 'native', 'type': 'choice', + 'choices': ('native', 'portable'), + 'help': '"native" format uses db backend utilities to dump the database. ' + '"portable" format uses a database independent format'} + ), ) def run(self, args): @@ -861,8 +896,9 @@ host, srcappid = srcappid.split(':') _remote_dump(host, srcappid, output, self.config.sudo) else: - _local_dump(srcappid, output) - _local_restore(destappid, output, not self.config.no_drop) + _local_dump(srcappid, output, format=self.config.format) + _local_restore(destappid, output, not self.config.no_drop, + self.config.format) if self.config.keep_dump: print '-> you can get the dump file at', output else: diff -r dc319ece0bd6 -r c2b29631c1a3 server/session.py --- a/server/session.py Mon May 16 16:24:00 2011 +0200 +++ b/server/session.py Mon May 16 16:25:33 2011 +0200 @@ -33,12 +33,14 @@ from yams import BASE_TYPES from cubicweb import Binary, UnknownEid, QueryError, schema +from cubicweb.selectors import objectify_selector from cubicweb.req import RequestSessionBase from cubicweb.dbapi import ConnectionProperties from cubicweb.utils import make_uid, RepeatList from cubicweb.rqlrewrite import RQLRewriter from cubicweb.server.edition import EditedEntity + ETYPE_PYOBJ_MAP[Binary] = 'Bytes' NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy() @@ -58,6 +60,20 @@ description.append(term.get_type(solution, args)) return description +@objectify_selector +def is_user_session(cls, req, **kwargs): + """repository side only selector returning 1 if the session is a regular + user session and not an internal session + """ + return not req.is_internal_session + +@objectify_selector +def is_internal_session(cls, req, **kwargs): + """repository side only selector returning 1 if the session is not a regular + user session but an internal session + """ + return req.is_internal_session + class hooks_control(object): """context manager to control activated hooks categories. @@ -166,6 +182,7 @@ self.__threaddata = threading.local() self._threads_in_transaction = set() self._closed = False + self._closed_lock = threading.Lock() def __unicode__(self): return '<%ssession %s (%s 0x%x)>' % ( @@ -213,14 +230,34 @@ You may use this in hooks when you know both eids of the relation you want to add. """ + self.add_relations([(rtype, [(fromeid, toeid)])]) + + def add_relations(self, relations): + '''set many relation using a shortcut similar to the one in add_relation + + relations is a list of 2-uples, the first element of each + 2-uple is the rtype, and the second is a list of (fromeid, + toeid) tuples + ''' + edited_entities = {} + relations_dict = {} with security_enabled(self, False, False): - if self.vreg.schema[rtype].inlined: - entity = self.entity_from_eid(fromeid) - edited = EditedEntity(entity) - edited.edited_attribute(rtype, toeid) + for rtype, eids in relations: + if self.vreg.schema[rtype].inlined: + for fromeid, toeid in eids: + if fromeid not in edited_entities: + entity = self.entity_from_eid(fromeid) + edited = EditedEntity(entity) + edited_entities[fromeid] = edited + else: + edited = edited_entities[fromeid] + edited.edited_attribute(rtype, toeid) + else: + relations_dict[rtype] = eids + self.repo.glob_add_relations(self, relations_dict) + for edited in edited_entities.itervalues(): self.repo.glob_update_entity(self, edited) - else: - self.repo.glob_add_relation(self, fromeid, rtype, toeid) + def delete_relation(self, fromeid, rtype, toeid): """provide direct access to the repository method to delete a relation. @@ -582,21 +619,22 @@ def set_pool(self): """the session need a pool to execute some queries""" - if self._closed: - self.reset_pool(True) - raise Exception('try to set pool on a closed session') - if self.pool is None: - # get pool first to avoid race-condition - self._threaddata.pool = pool = self.repo._get_pool() - try: - pool.pool_set() - except: - self._threaddata.pool = None - self.repo._free_pool(pool) - raise - self._threads_in_transaction.add( - (threading.currentThread(), pool) ) - return self._threaddata.pool + with self._closed_lock: + if self._closed: + self.reset_pool(True) + raise Exception('try to set pool on a closed session') + if self.pool is None: + # get pool first to avoid race-condition + self._threaddata.pool = pool = self.repo._get_pool() + try: + pool.pool_set() + except: + self._threaddata.pool = None + self.repo._free_pool(pool) + raise + self._threads_in_transaction.add( + (threading.currentThread(), pool) ) + return self._threaddata.pool def _free_thread_pool(self, thread, pool, force_close=False): try: @@ -834,7 +872,8 @@ def close(self): """do not close pool on session close, since they are shared now""" - self._closed = True + with self._closed_lock: + self._closed = True # copy since _threads_in_transaction maybe modified while waiting for thread, pool in self._threads_in_transaction.copy(): if thread is threading.currentThread(): diff -r dc319ece0bd6 -r c2b29631c1a3 server/sources/__init__.py --- a/server/sources/__init__.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/__init__.py Mon May 16 16:25:33 2011 +0200 @@ -139,11 +139,11 @@ return -1 return cmp(self.uri, other.uri) - def backup(self, backupfile, confirm): + def backup(self, backupfile, confirm, format='native'): """method called to create a backup of source's data""" pass - def restore(self, backupfile, confirm, drop): + def restore(self, backupfile, confirm, drop, format='native'): """method called to restore a backup of source's data""" pass @@ -434,6 +434,13 @@ """add a relation to the source""" raise NotImplementedError() + def add_relations(self, session, rtype, subj_obj_list): + """add a relations to the source""" + # override in derived classes if you feel you can + # optimize + for subject, object in subj_obj_list: + self.add_relation(session, subject, rtype, object) + def delete_relation(self, session, subject, rtype, object): """delete a relation from the source""" raise NotImplementedError() diff -r dc319ece0bd6 -r c2b29631c1a3 server/sources/datafeed.py --- a/server/sources/datafeed.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/datafeed.py Mon May 16 16:25:33 2011 +0200 @@ -120,7 +120,7 @@ return False return datetime.now() < (self.latest_retrieval + self.synchro_interval) - def pull_data(self, session, force=False): + def pull_data(self, session, force=False, raise_on_error=False): if not force and self.fresh(): return {} if self.config['delete-entities']: @@ -135,6 +135,8 @@ if parser.process(url): error = True except IOError, exc: + if raise_on_error: + raise self.error('could not pull data while processing %s: %s', url, exc) error = True diff -r dc319ece0bd6 -r c2b29631c1a3 server/sources/native.py --- a/server/sources/native.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/native.py Mon May 16 16:25:33 2011 +0200 @@ -28,21 +28,29 @@ __docformat__ = "restructuredtext en" -from pickle import loads, dumps +try: + from cPickle import loads, dumps + import cPickle as pickle +except ImportError: + from pickle import loads, dumps + import pickle from threading import Lock from datetime import datetime from base64 import b64decode, b64encode from contextlib import contextmanager -from os.path import abspath +from os.path import abspath, basename import re import itertools +import zipfile +import logging +import sys from logilab.common.compat import any from logilab.common.cache import Cache from logilab.common.decorators import cached, clear_cache from logilab.common.configuration import Method from logilab.common.shellutils import getlogin -from logilab.database import get_db_helper +from logilab.database import get_db_helper, sqlgen from yams import schema2sql as y2sql from yams.schema import role_name @@ -354,24 +362,44 @@ _pool.pool_reset() self.repo._free_pool(_pool) - def backup(self, backupfile, confirm): + def backup(self, backupfile, confirm, format='native'): """method called to create a backup of the source's data""" - self.close_pool_connections() - try: - self.backup_to_file(backupfile, confirm) - finally: - self.open_pool_connections() + if format == 'portable': + self.repo.fill_schema() + self.set_schema(self.repo.schema) + helper = DatabaseIndependentBackupRestore(self) + self.close_pool_connections() + try: + helper.backup(backupfile) + finally: + self.open_pool_connections() + elif format == 'native': + self.close_pool_connections() + try: + self.backup_to_file(backupfile, confirm) + finally: + self.open_pool_connections() + else: + raise ValueError('Unknown format %r' % format) - def restore(self, backupfile, confirm, drop): + + def restore(self, backupfile, confirm, drop, format='native'): """method called to restore a backup of source's data""" if self.repo.config.open_connections_pools: self.close_pool_connections() try: - self.restore_from_file(backupfile, confirm, drop=drop) + if format == 'portable': + helper = DatabaseIndependentBackupRestore(self) + helper.restore(backupfile) + elif format == 'native': + self.restore_from_file(backupfile, confirm, drop=drop) + else: + raise ValueError('Unknown format %r' % format) finally: if self.repo.config.open_connections_pools: self.open_pool_connections() + def init(self, activated, source_entity): self.init_creating(source_entity._cw.pool) @@ -628,22 +656,42 @@ def add_relation(self, session, subject, rtype, object, inlined=False): """add a relation to the source""" - self._add_relation(session, subject, rtype, object, inlined) + self._add_relations(session, rtype, [(subject, object)], inlined) if session.undoable_action('A', rtype): self._record_tx_action(session, 'tx_relation_actions', 'A', eid_from=subject, rtype=rtype, eid_to=object) - def _add_relation(self, session, subject, rtype, object, inlined=False): + def add_relations(self, session, rtype, subj_obj_list, inlined=False): + """add a relations to the source""" + self._add_relations(session, rtype, subj_obj_list, inlined) + if session.undoable_action('A', rtype): + for subject, object in subj_obj_list: + self._record_tx_action(session, 'tx_relation_actions', 'A', + eid_from=subject, rtype=rtype, eid_to=object) + + def _add_relations(self, session, rtype, subj_obj_list, inlined=False): """add a relation to the source""" + sql = [] if inlined is False: - attrs = {'eid_from': subject, 'eid_to': object} - sql = self.sqlgen.insert('%s_relation' % rtype, attrs) + attrs = [{'eid_from': subject, 'eid_to': object} + for subject, object in subj_obj_list] + sql.append((self.sqlgen.insert('%s_relation' % rtype, attrs[0]), attrs)) else: # used by data import - etype = session.describe(subject)[0] - attrs = {'cw_eid': subject, SQL_PREFIX + rtype: object} - sql = self.sqlgen.update(SQL_PREFIX + etype, attrs, - ['cw_eid']) - self.doexec(session, sql, attrs) + etypes = {} + for subject, object in subj_obj_list: + etype = session.describe(subject)[0] + if etype in etypes: + etypes[etype].append((subject, object)) + else: + etypes[etype] = [(subject, object)] + for subj_etype, subj_obj_list in etypes.iteritems(): + attrs = [{'cw_eid': subject, SQL_PREFIX + rtype: object} + for subject, object in subj_obj_list] + sql.append((self.sqlgen.update(SQL_PREFIX + etype, attrs[0], + ['cw_eid']), + attrs)) + for statement, attrs in sql: + self.doexecmany(session, statement, attrs) def delete_relation(self, session, subject, rtype, object): """delete a relation from the source""" @@ -1232,7 +1280,7 @@ self.repo.hm.call_hooks('before_add_relation', session, eidfrom=subj, rtype=rtype, eidto=obj) # add relation in the database - self._add_relation(session, subj, rtype, obj, rdef.rtype.inlined) + self._add_relations(session, rtype, [(subj, obj)], rdef.rtype.inlined) # set related cache session.update_rel_cache_add(subj, rtype, obj, rdef.rtype.symmetric) self.repo.hm.call_hooks('after_add_relation', session, @@ -1544,3 +1592,218 @@ login = rset.rows[0][0] authinfo['email_auth'] = True return self.source.repo.check_auth_info(session, login, authinfo) + +class DatabaseIndependentBackupRestore(object): + """Helper class to perform db backend agnostic backup and restore + + The backup and restore methods are used to dump / restore the + system database in a database independent format. The file is a + Zip archive containing the following files: + + * format.txt: the format of the archive. Currently '1.0' + * tables.txt: list of filenames in the archive tables/ directory + * sequences.txt: list of filenames in the archive sequences/ directory + * versions.txt: the list of cube versions from CWProperty + * tables/.: pickled data + * sequences/: pickled data + + The pickled data format for tables and sequences is a tuple of 3 elements: + * the table name + * a tuple of column names + * a list of rows (as tuples with one element per column) + + Tables are saved in chunks in different files in order to prevent + a too high memory consumption. + """ + def __init__(self, source): + """ + :param: source an instance of the system source + """ + self._source = source + self.logger = logging.getLogger('cubicweb.ctl') + self.logger.setLevel(logging.INFO) + self.logger.addHandler(logging.StreamHandler(sys.stdout)) + self.schema = self._source.schema + self.dbhelper = self._source.dbhelper + self.cnx = None + self.cursor = None + self.sql_generator = sqlgen.SQLGenerator() + + def get_connection(self): + return self._source.get_connection() + + def backup(self, backupfile): + archive=zipfile.ZipFile(backupfile, 'w') + self.cnx = self.get_connection() + try: + self.cursor = self.cnx.cursor() + self.cursor.arraysize=100 + self.logger.info('writing metadata') + self.write_metadata(archive) + for seq in self.get_sequences(): + self.logger.info('processing sequence %s', seq) + self.write_sequence(archive, seq) + for table in self.get_tables(): + self.logger.info('processing table %s', table) + self.write_table(archive, table) + finally: + archive.close() + self.cnx.close() + self.logger.info('done') + + def get_tables(self): + non_entity_tables = ['entities', + 'deleted_entities', + 'transactions', + 'tx_entity_actions', + 'tx_relation_actions', + ] + etype_tables = [] + relation_tables = [] + prefix = 'cw_' + for etype in self.schema.entities(): + eschema = self.schema.eschema(etype) + print etype, eschema.final + if eschema.final: + continue + etype_tables.append('%s%s'%(prefix, etype)) + for rtype in self.schema.relations(): + rschema = self.schema.rschema(rtype) + if rschema.final or rschema.inlined: + continue + relation_tables.append('%s_relation' % rtype) + return non_entity_tables + etype_tables + relation_tables + + def get_sequences(self): + return ['entities_id_seq'] + + def write_metadata(self, archive): + archive.writestr('format.txt', '1.0') + archive.writestr('tables.txt', '\n'.join(self.get_tables())) + archive.writestr('sequences.txt', '\n'.join(self.get_sequences())) + versions = self._get_versions() + versions_str = '\n'.join('%s %s' % (k,v) + for k,v in versions) + archive.writestr('versions.txt', versions_str) + + def write_sequence(self, archive, seq): + sql = self.dbhelper.sql_sequence_current_state(seq) + columns, rows_iterator = self._get_cols_and_rows(sql) + rows = list(rows_iterator) + serialized = self._serialize(seq, columns, rows) + archive.writestr('sequences/%s' % seq, serialized) + + def write_table(self, archive, table): + sql = 'SELECT * FROM %s' % table + columns, rows_iterator = self._get_cols_and_rows(sql) + self.logger.info('number of rows: %d', self.cursor.rowcount) + if table.startswith('cw_'): # entities + blocksize = 2000 + else: # relations and metadata + blocksize = 10000 + if self.cursor.rowcount > 0: + for i, start in enumerate(xrange(0, self.cursor.rowcount, blocksize)): + rows = list(itertools.islice(rows_iterator, blocksize)) + serialized = self._serialize(table, columns, rows) + archive.writestr('tables/%s.%04d' % (table, i), serialized) + self.logger.debug('wrote rows %d to %d (out of %d) to %s.%04d', + start, start+len(rows)-1, + self.cursor.rowcount, + table, i) + else: + rows = [] + serialized = self._serialize(table, columns, rows) + archive.writestr('tables/%s.%04d' % (table, 0), serialized) + + def _get_cols_and_rows(self, sql): + process_result = self._source.iter_process_result + self.cursor.execute(sql) + columns = (d[0] for d in self.cursor.description) + rows = process_result(self.cursor) + return tuple(columns), rows + + def _serialize(self, name, columns, rows): + return dumps((name, columns, rows), pickle.HIGHEST_PROTOCOL) + + def restore(self, backupfile): + archive = zipfile.ZipFile(backupfile, 'r') + self.cnx = self.get_connection() + self.cursor = self.cnx.cursor() + sequences, tables, table_chunks = self.read_metadata(archive, backupfile) + for seq in sequences: + self.logger.info('restoring sequence %s', seq) + self.read_sequence(archive, seq) + for table in tables: + self.logger.info('restoring table %s', table) + self.read_table(archive, table, sorted(table_chunks[table])) + self.cnx.close() + archive.close() + self.logger.info('done') + + def read_metadata(self, archive, backupfile): + formatinfo = archive.read('format.txt') + self.logger.info('checking metadata') + if formatinfo.strip() != "1.0": + self.logger.critical('Unsupported format in archive: %s', formatinfo) + raise ValueError('Unknown format in %s: %s' % (backupfile, formatinfo)) + tables = archive.read('tables.txt').splitlines() + sequences = archive.read('sequences.txt').splitlines() + file_versions = self._parse_versions(archive.read('versions.txt')) + versions = set(self._get_versions()) + if file_versions != versions: + self.logger.critical('Unable to restore : versions do not match') + self.logger.critical('Expected:\n%s', '\n'.join(list(sorted(versions)))) + self.logger.critical('Found:\n%s', '\n'.join(list(sorted(file_versions)))) + raise ValueError('Unable to restore : versions do not match') + table_chunks = {} + for name in archive.namelist(): + if not name.startswith('tables/'): + continue + filename = basename(name) + tablename, _ext = filename.rsplit('.', 1) + table_chunks.setdefault(tablename, []).append(name) + return sequences, tables, table_chunks + + def read_sequence(self, archive, seq): + seqname, columns, rows = loads(archive.read('sequences/%s' % seq)) + assert seqname == seq + assert len(rows) == 1 + assert len(rows[0]) == 1 + value = rows[0][0] + sql = self.dbhelper.sql_restart_sequence(seq, value) + self.cursor.execute(sql) + self.cnx.commit() + + def read_table(self, archive, table, filenames): + merge_args = self._source.merge_args + self.cursor.execute('DELETE FROM %s' % table) + self.cnx.commit() + row_count = 0 + for filename in filenames: + tablename, columns, rows = loads(archive.read(filename)) + assert tablename == table + if not rows: + continue + insert = self.sql_generator.insert(table, + dict(zip(columns, rows[0]))) + for row in rows: + self.cursor.execute(insert, merge_args(dict(zip(columns, row)), {})) + row_count += len(rows) + self.cnx.commit() + self.logger.info('inserted %d rows', row_count) + + + def _parse_versions(self, version_str): + versions = set() + for line in version_str.splitlines(): + versions.add(tuple(line.split())) + return versions + + def _get_versions(self): + version_sql = 'SELECT cw_pkey, cw_value FROM cw_CWProperty' + versions = [] + self.cursor.execute(version_sql) + for pkey, value in self.cursor.fetchall(): + if pkey.startswith(u'system.version'): + versions.append((pkey, value)) + return versions diff -r dc319ece0bd6 -r c2b29631c1a3 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/rql2sql.py Mon May 16 16:25:33 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 @@ -82,6 +84,7 @@ newvar.prepare_annotation() newvar.stinfo['scope'] = select newvar._q_invariant = False + select.selection.append(VariableRef(newvar)) return newvar def _fill_to_wrap_rel(var, newselect, towrap, schema): @@ -91,10 +94,12 @@ towrap.add( (var, rel) ) for vref in rel.children[1].iget_nodes(VariableRef): newivar = _new_var(newselect, vref.name) - newselect.selection.append(VariableRef(newivar)) _fill_to_wrap_rel(vref.variable, newselect, towrap, schema) elif rschema.final: towrap.add( (var, rel) ) + for vref in rel.children[1].iget_nodes(VariableRef): + newivar = _new_var(newselect, vref.name) + newivar.stinfo['attrvar'] = (var, rel.r_type) def rewrite_unstable_outer_join(select, solutions, unstable, schema): """if some optional variables are unstable, they should be selected in a @@ -114,11 +119,6 @@ # extract aliases / selection newvar = _new_var(newselect, var.name) newselect.selection = [VariableRef(newvar)] - for avar in select.defined_vars.itervalues(): - if avar.stinfo['attrvar'] is var: - newavar = _new_var(newselect, avar.name) - newavar.stinfo['attrvar'] = newvar - newselect.selection.append(VariableRef(newavar)) towrap_rels = set() _fill_to_wrap_rel(var, newselect, towrap_rels, schema) # extract relations @@ -588,16 +588,16 @@ rconditions.append(condition) else: lconditions.append(condition) - else: - if louter is not None: - raise BadRQLQuery() + elif louter is None: # merge chains self.outer_chains.remove(lchain) + rchain += lchain self.mark_as_used_in_outer_join(leftalias) - rchain += lchain for alias, (aouter, aconditions, achain) in outer_tables.iteritems(): if achain is lchain: outer_tables[alias] = (aouter, aconditions, rchain) + else: + raise BadRQLQuery() # sql generation helpers ################################################### @@ -1424,6 +1424,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 dc319ece0bd6 -r c2b29631c1a3 server/sqlutils.py --- a/server/sqlutils.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sqlutils.py Mon May 16 16:25:33 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 @@ -204,6 +204,12 @@ def process_result(self, cursor, column_callbacks=None, session=None): """return a list of CubicWeb compliant values from data in the given cursor """ + return list(self.iter_process_result(cursor, column_callbacks, session)) + + def iter_process_result(self, cursor, column_callbacks=None, session=None): + """return a iterator on tuples of CubicWeb compliant values from data + in the given cursor + """ # use two different implementations to avoid paying the price of # callback lookup for each *cell* in results when there is nothing to # lookup @@ -219,16 +225,19 @@ process_value = self._process_value binary = Binary # /end - results = cursor.fetchall() - for i, line in enumerate(results): - result = [] - for col, value in enumerate(line): - if value is None: - result.append(value) - continue - result.append(process_value(value, descr[col], encoding, binary)) - results[i] = result - return results + cursor.arraysize = 100 + while True: + results = cursor.fetchmany() + if not results: + break + for line in results: + result = [] + for col, value in enumerate(line): + if value is None: + result.append(value) + continue + result.append(process_value(value, descr[col], encoding, binary)) + yield result def _cb_process_result(self, cursor, column_callbacks, session): # begin bind to locals for optimization @@ -237,22 +246,25 @@ process_value = self._process_value binary = Binary # /end - results = cursor.fetchall() - for i, line in enumerate(results): - result = [] - for col, value in enumerate(line): - if value is None: + cursor.arraysize = 100 + while True: + results = cursor.fetchmany() + if not results: + break + for line in results: + result = [] + for col, value in enumerate(line): + if value is None: + result.append(value) + continue + cbstack = column_callbacks.get(col, None) + if cbstack is None: + value = process_value(value, descr[col], encoding, binary) + else: + for cb in cbstack: + value = cb(self, session, value) result.append(value) - continue - cbstack = column_callbacks.get(col, None) - if cbstack is None: - value = process_value(value, descr[col], encoding, binary) - else: - for cb in cbstack: - value = cb(self, session, value) - result.append(value) - results[i] = result - return results + yield result def preprocess_entity(self, entity): """return a dictionary to use as extra argument to cursor.execute @@ -274,10 +286,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 +343,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 dc319ece0bd6 -r c2b29631c1a3 server/ssplanner.py --- a/server/ssplanner.py Mon May 16 16:24:00 2011 +0200 +++ b/server/ssplanner.py Mon May 16 16:25:33 2011 +0200 @@ -559,6 +559,7 @@ session = self.plan.session repo = session.repo edefs = {} + relations = {} # insert relations if self.children: result = self.execute_child() @@ -578,9 +579,14 @@ edefs[eid] = edited = EditedEntity(edef) edited.edited_attribute(str(rschema), rhsval) else: - repo.glob_add_relation(session, lhsval, str(rschema), rhsval) + str_rschema = str(rschema) + if str_rschema in relations: + relations[str_rschema].append((lhsval, rhsval)) + else: + relations[str_rschema] = [(lhsval, rhsval)] result[i] = newrow # update entities + repo.glob_add_relations(session, relations) for eid, edited in edefs.iteritems(): repo.glob_update_entity(session, edited) return result diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/data/schema.py --- a/server/test/data/schema.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/data/schema.py Mon May 16 16:25:33 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',), @@ -219,3 +220,26 @@ class require_state(RelationDefinition): subject = 'CWPermission' object = 'State' + +class personne_composite(RelationDefinition): + subject='Personne' + object='Personne' + composite='subject' + +class personne_inlined(RelationDefinition): + subject='Personne' + object='Personne' + cardinality='?*' + inlined=True + + +class login_user(RelationDefinition): + subject = 'Personne' + object = 'CWUser' + cardinality = '??' + +class ambiguous_inlined(RelationDefinition): + subject = ('Affaire', 'Note') + object = 'CWUser' + inlined = True + cardinality = '?*' diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/data/sources_fti --- a/server/test/data/sources_fti Mon May 16 16:24:00 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 dc319ece0bd6 -r c2b29631c1a3 server/test/data/sources_postgres --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/data/sources_postgres Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_fti.py --- a/server/test/unittest_fti.py Mon May 16 16:24:00 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 dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_ldapuser.py --- a/server/test/unittest_ldapuser.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_ldapuser.py Mon May 16 16:25:33 2011 +0200 @@ -260,7 +260,8 @@ self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"') def test_multiple_entities_from_different_sources(self): - self.create_user('cochon') + req = self.request() + self.create_user(req, 'cochon') self.failUnless(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})) def test_exists1(self): @@ -274,16 +275,18 @@ self.assertEqual(rset.rows, [['admin', 'activated'], [SYT, 'activated']]) def test_exists2(self): - self.create_user('comme') - self.create_user('cochon') + req = self.request() + self.create_user(req, 'comme') + self.create_user(req, 'cochon') self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"') rset = self.sexecute('Any GN ORDERBY GN WHERE X in_group G, G name GN, ' '(G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))') self.assertEqual(rset.rows, [['managers'], ['users']]) def test_exists3(self): - self.create_user('comme') - self.create_user('cochon') + req = self.request() + self.create_user(req, 'comme') + self.create_user(req, 'cochon') self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"') self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')) self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}) @@ -293,9 +296,10 @@ self.assertEqual(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', SYT]]) def test_exists4(self): - self.create_user('comme') - self.create_user('cochon', groups=('users', 'guests')) - self.create_user('billy') + req = self.request() + self.create_user(req, 'comme') + self.create_user(req, 'cochon', groups=('users', 'guests')) + self.create_user(req, 'billy') self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"') self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"') self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"') @@ -315,9 +319,10 @@ self.assertEqual(sorted(rset.rows), sorted(all.rows)) def test_exists5(self): - self.create_user('comme') - self.create_user('cochon', groups=('users', 'guests')) - self.create_user('billy') + req = self.request() + self.create_user(req, 'comme') + self.create_user(req, 'cochon', groups=('users', 'guests')) + self.create_user(req, 'billy') self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"') self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"') self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"') @@ -347,7 +352,8 @@ sorted(r[0] for r in afeids + ueids)) def _init_security_test(self): - self.create_user('iaminguestsgrouponly', groups=('guests',)) + req = self.request() + self.create_user(req, 'iaminguestsgrouponly', groups=('guests',)) cnx = self.login('iaminguestsgrouponly') return cnx.cursor() diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_migractions.py Mon May 16 16:25:33 2011 +0200 @@ -364,8 +364,9 @@ 'X from_entity FE, FE name "Personne",' 'X ordernum O')] expected = [u'nom', u'prenom', u'sexe', u'promo', u'ass', u'adel', u'titre', - u'web', u'tel', u'fax', u'datenaiss', u'test', 'description', u'firstname', - u'creation_date', 'cwuri', u'modification_date'] + u'web', u'tel', u'fax', u'datenaiss', u'tzdatenaiss', u'test', + u'description', u'firstname', + u'creation_date', u'cwuri', u'modification_date'] self.assertEqual(rinorder, expected) # test permissions synchronization #################################### diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_msplanner.py Mon May 16 16:25:33 2011 +0200 @@ -1991,12 +1991,12 @@ ]) def test_source_specified_2_0(self): - self._test('Card X WHERE X cw_source S, NOT S eid 1', - [('OneFetchStep', [('Any X WHERE X is Card', - [{'X': 'Card'}])], - None, None, - [self.cards],{}, []) - ]) + # self._test('Card X WHERE X cw_source S, NOT S eid 1', + # [('OneFetchStep', [('Any X WHERE X is Card', + # [{'X': 'Card'}])], + # None, None, + # [self.cards],{}, []) + # ]) self._test('Card X WHERE NOT X cw_source S, S eid 1', [('OneFetchStep', [('Any X WHERE X is Card', [{'X': 'Card'}])], diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_postgres.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/unittest_postgres.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_querier.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_repository.py Mon May 16 16:25:33 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, " @@ -276,13 +277,16 @@ cnxid = repo.connect(self.admlogin, password=self.admpassword) repo.execute(cnxid, 'INSERT CWUser X: X login "toto", X upassword "tutu", X in_group G WHERE G name "users"') repo.commit(cnxid) + lock = threading.Lock() + lock.acquire() # close has to be in the thread due to sqlite limitations def close_in_a_few_moment(): - time.sleep(0.1) + lock.acquire() repo.close(cnxid) t = threading.Thread(target=close_in_a_few_moment) t.start() def run_transaction(): + lock.release() repo.execute(cnxid, 'DELETE CWUser X WHERE X login "toto"') repo.commit(cnxid) try: @@ -327,7 +331,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',)) @@ -684,5 +688,160 @@ req.cnx.commit() self.assertEqual(cm.exception.errors, {'inline1-subject': u'RQLUniqueConstraint S type T, S inline1 A1, A1 todo_by C, Y type T, Y inline1 A2, A2 todo_by C failed'}) + def test_add_relations_at_creation_with_del_existing_rel(self): + req = self.request() + person = req.create_entity('Personne', nom=u'Toto', prenom=u'Lanturlu', sexe=u'M') + users_rql = 'Any U WHERE U is CWGroup, U name "users"' + users = self.execute(users_rql).get_entity(0, 0) + req.create_entity('CWUser', + login=u'Toto', + upassword=u'firstname', + firstname=u'firstname', + surname=u'surname', + reverse_login_user=person, + in_group=users) + self.commit() + + +class PerformanceTest(CubicWebTC): + def setup_database(self): + import logging + logger = logging.getLogger('cubicweb.session') + #logger.handlers = [logging.StreamHandler(sys.stdout)] + logger.setLevel(logging.INFO) + self.info = logger.info + + def test_composite_deletion(self): + req = self.request() + personnes = [] + t0 = time.time() + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M') + for j in xrange(0, 2000, 100): + abraham.set_relations(personne_composite=personnes[j:j+100]) + t1 = time.time() + self.info('creation: %.2gs', (t1 - t0)) + req.cnx.commit() + t2 = time.time() + self.info('commit creation: %.2gs', (t2 - t1)) + self.execute('DELETE Personne P WHERE P eid %(eid)s', {'eid': abraham.eid}) + t3 = time.time() + self.info('deletion: %.2gs', (t3 - t2)) + req.cnx.commit() + t4 = time.time() + self.info("commit deletion: %2gs", (t4 - t3)) + + def test_add_relation_non_inlined(self): + req = self.request() + personnes = [] + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + req.cnx.commit() + t0 = time.time() + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M', + personne_composite=personnes[:100]) + t1 = time.time() + self.info('creation: %.2gs', (t1 - t0)) + for j in xrange(100, 2000, 100): + abraham.set_relations(personne_composite=personnes[j:j+100]) + t2 = time.time() + self.info('more relations: %.2gs', (t2-t1)) + req.cnx.commit() + t3 = time.time() + self.info('commit creation: %.2gs', (t3 - t2)) + + def test_add_relation_inlined(self): + req = self.request() + personnes = [] + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + req.cnx.commit() + t0 = time.time() + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M', + personne_inlined=personnes[:100]) + t1 = time.time() + self.info('creation: %.2gs', (t1 - t0)) + for j in xrange(100, 2000, 100): + abraham.set_relations(personne_inlined=personnes[j:j+100]) + t2 = time.time() + self.info('more relations: %.2gs', (t2-t1)) + req.cnx.commit() + t3 = time.time() + self.info('commit creation: %.2gs', (t3 - t2)) + + + def test_session_add_relation(self): + """ to be compared with test_session_add_relations""" + req = self.request() + personnes = [] + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M') + req.cnx.commit() + t0 = time.time() + add_relation = self.session.add_relation + for p in personnes: + add_relation(abraham.eid, 'personne_composite', p.eid) + req.cnx.commit() + t1 = time.time() + self.info('add relation: %.2gs', t1-t0) + + def test_session_add_relations (self): + """ to be compared with test_session_add_relation""" + req = self.request() + personnes = [] + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M') + req.cnx.commit() + t0 = time.time() + add_relations = self.session.add_relations + relations = [('personne_composite', [(abraham.eid, p.eid) for p in personnes])] + add_relations(relations) + req.cnx.commit() + t1 = time.time() + self.info('add relations: %.2gs', t1-t0) + def test_session_add_relation_inlined(self): + """ to be compared with test_session_add_relations""" + req = self.request() + personnes = [] + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M') + req.cnx.commit() + t0 = time.time() + add_relation = self.session.add_relation + for p in personnes: + add_relation(abraham.eid, 'personne_inlined', p.eid) + req.cnx.commit() + t1 = time.time() + self.info('add relation (inlined): %.2gs', t1-t0) + + def test_session_add_relations_inlined (self): + """ to be compared with test_session_add_relation""" + req = self.request() + personnes = [] + for i in xrange(2000): + p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M') + personnes.append(p) + abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M') + req.cnx.commit() + t0 = time.time() + add_relations = self.session.add_relations + relations = [('personne_inlined', [(abraham.eid, p.eid) for p in personnes])] + add_relations(relations) + req.cnx.commit() + t1 = time.time() + self.info('add relations (inlined): %.2gs', t1-t0) + + + if __name__ == '__main__': unittest_main() diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_rql2sql.py Mon May 16 16:25:33 2011 +0200 @@ -835,9 +835,9 @@ WHERE _X.cw_eid=12''' ), ("Any P WHERE X eid 12, P? concerne X, X todo_by S", - '''SELECT rel_concerne0.eid_from -FROM todo_by_relation AS rel_todo_by1 LEFT OUTER JOIN concerne_relation AS rel_concerne0 ON (rel_concerne0.eid_to=12) -WHERE rel_todo_by1.eid_from=12''' + '''SELECT rel_concerne1.eid_from +FROM todo_by_relation AS rel_todo_by0 LEFT OUTER JOIN concerne_relation AS rel_concerne1 ON (rel_concerne1.eid_to=12) +WHERE rel_todo_by0.eid_from=12''' ), ('Any GN, TN ORDERBY GN WHERE T tags G?, T name TN, G name GN', @@ -909,7 +909,32 @@ ('Any O,AD WHERE NOT S inline1 O, S eid 123, O todo_by AD?', '''SELECT _O.cw_eid, rel_todo_by0.eid_to FROM cw_Note AS _S, cw_Affaire AS _O LEFT OUTER JOIN todo_by_relation AS rel_todo_by0 ON (rel_todo_by0.eid_from=_O.cw_eid) -WHERE (_S.cw_inline1 IS NULL OR _S.cw_inline1!=_O.cw_eid) AND _S.cw_eid=123''') +WHERE (_S.cw_inline1 IS NULL OR _S.cw_inline1!=_O.cw_eid) AND _S.cw_eid=123'''), + + ('Any X,AE WHERE X multisource_inlined_rel S?, S ambiguous_inlined A, A modification_date AE', + '''SELECT _X.cw_eid, _T0.C2 +FROM cw_Card AS _X LEFT OUTER JOIN (SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2 +FROM cw_Affaire AS _S, cw_CWUser AS _A +WHERE _S.cw_ambiguous_inlined=_A.cw_eid +UNION ALL +SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2 +FROM cw_CWUser AS _A, cw_Note AS _S +WHERE _S.cw_ambiguous_inlined=_A.cw_eid) AS _T0 ON (_X.cw_multisource_inlined_rel=_T0.C0) +UNION ALL +SELECT _X.cw_eid, _T0.C2 +FROM cw_Note AS _X LEFT OUTER JOIN (SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2 +FROM cw_Affaire AS _S, cw_CWUser AS _A +WHERE _S.cw_ambiguous_inlined=_A.cw_eid +UNION ALL +SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2 +FROM cw_CWUser AS _A, cw_Note AS _S +WHERE _S.cw_ambiguous_inlined=_A.cw_eid) AS _T0 ON (_X.cw_multisource_inlined_rel=_T0.C0)''' + ), + + ('Any X,T,OT WHERE X tags T, OT? tags X, X is Tag, X eid 123', + '''SELECT rel_tags0.eid_from, rel_tags0.eid_to, rel_tags1.eid_from +FROM tags_relation AS rel_tags0 LEFT OUTER JOIN tags_relation AS rel_tags1 ON (rel_tags1.eid_to=123) +WHERE rel_tags0.eid_from=123'''), ] VIRTUAL_VARS = [ @@ -1225,9 +1250,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 +1268,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 +1279,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''', {}) @@ -1576,7 +1605,7 @@ '''SELECT 1 WHERE NOT (EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group0))''') - def test_nonregr_subquery_missing_join(self): + def test_nonregr_outer_join_multiple(self): self._check('Any COUNT(P1148),G GROUPBY G ' 'WHERE G owned_by D, D eid 1122, K1148 bookmarked_by P1148, ' 'K1148 eid 1148, P1148? in_group G', @@ -1586,7 +1615,7 @@ GROUP BY _G.cw_eid''' ) - def test_nonregr_subquery_missing_join2(self): + def test_nonregr_outer_join_multiple2(self): self._check('Any COUNT(P1148),G GROUPBY G ' 'WHERE G owned_by D, D eid 1122, K1148 bookmarked_by P1148?, ' 'K1148 eid 1148, P1148? in_group G', diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_security.py --- a/server/test/unittest_security.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_security.py Mon May 16 16:25:33 2011 +0200 @@ -29,7 +29,8 @@ def setup_database(self): super(BaseSecurityTC, self).setup_database() - self.create_user('iaminusersgrouponly') + req = self.request() + self.create_user(req, 'iaminusersgrouponly') readoriggroups = self.schema['Personne'].permissions['read'] addoriggroups = self.schema['Personne'].permissions['add'] def fix_perm(): @@ -260,7 +261,8 @@ def test_user_can_change_its_upassword(self): - ueid = self.create_user('user').eid + req = self.request() + ueid = self.create_user(req, 'user').eid cnx = self.login('user') cu = cnx.cursor() cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s', @@ -271,7 +273,8 @@ cnx.close() def test_user_cant_change_other_upassword(self): - ueid = self.create_user('otheruser').eid + req = self.request() + ueid = self.create_user(req, 'otheruser').eid cnx = self.login('iaminusersgrouponly') cu = cnx.cursor() cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s', diff -r dc319ece0bd6 -r c2b29631c1a3 server/test/unittest_undo.py --- a/server/test/unittest_undo.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_undo.py Mon May 16 16:25:33 2011 +0200 @@ -23,9 +23,11 @@ class UndoableTransactionTC(CubicWebTC): + def setup_database(self): + req = self.request() self.session.undo_actions = set('CUDAR') - self.toto = self.create_user('toto', password='toto', groups=('users',), + self.toto = self.create_user(req, 'toto', password='toto', groups=('users',), commit=False) self.txuuid = self.commit() @@ -246,7 +248,8 @@ def test_undo_creation_integrity_1(self): session = self.session - tutu = self.create_user('tutu', commit=False) + req = self.request() + tutu = self.create_user(req, 'tutu', commit=False) txuuid = self.commit() email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org') prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format', diff -r dc319ece0bd6 -r c2b29631c1a3 sobjects/parsers.py --- a/sobjects/parsers.py Mon May 16 16:24:00 2011 +0200 +++ b/sobjects/parsers.py Mon May 16 16:25:33 2011 +0200 @@ -15,7 +15,21 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""datafeed parser for xml generated by cubicweb""" +"""datafeed parser for xml generated by cubicweb + +Example of mapping for CWEntityXMLParser:: + + {u'CWUser': { # EntityType + (u'in_group', u'subject', u'link'): [ # (rtype, role, action) + (u'CWGroup', {u'linkattr': u'name'})], # -> rules = [(EntityType, options), ...] + (u'tags', u'object', u'link-or-create'): [ # (...) + (u'Tag', {u'linkattr': u'name'})], # -> ... + (u'use_email', u'subject', u'copy'): [ # (...) + (u'EmailAddress', {})] # -> ... + } + } + +""" import urllib2 import StringIO @@ -34,11 +48,12 @@ from cubicweb import ValidationError, typed_eid from cubicweb.server.sources import datafeed -def ensure_str_keys(dict): - for key in dict: - dict[str(key)] = dict.pop(key) +def ensure_str_keys(dic): + for key in dic: + dic[str(key)] = dic.pop(key) -# see cubicweb.web.views.xmlrss.SERIALIZERS +# XXX see cubicweb.cwvreg.YAMS_TO_PY +# XXX see cubicweb.web.views.xmlrss.SERIALIZERS DEFAULT_CONVERTERS = BASE_CONVERTERS.copy() DEFAULT_CONVERTERS['String'] = unicode DEFAULT_CONVERTERS['Password'] = lambda x: x.encode('utf8') @@ -76,7 +91,7 @@ typeddict[rschema.type] = converters[attrtype](stringdict[rschema]) return typeddict -def _entity_etree(parent): +def _parse_entity_etree(parent): for node in list(parent): try: item = {'cwtype': unicode(node.tag), @@ -92,20 +107,18 @@ rels = {} for child in node: role = child.get('role') - if child.get('role'): + if role: # relation related = rels.setdefault(role, {}).setdefault(child.tag, []) - related += [ritem for ritem, _ in _entity_etree(child)] + related += [ritem for ritem, _ in _parse_entity_etree(child)] else: # attribute item[child.tag] = unicode(child.text) yield item, rels def build_search_rql(etype, attrs): - restrictions = [] - for attr in attrs: - restrictions.append('X %(attr)s %%(%(attr)s)s' % {'attr': attr}) - return 'Any X WHERE X is %s, %s' % (etype, ','.join(restrictions)) + restrictions = ['X %(attr)s %%(%(attr)s)s'%{'attr': attr} for attr in attrs] + return 'Any X WHERE X is %s, %s' % (etype, ', '.join(restrictions)) def rtype_role_rql(rtype, role): if role == 'object': @@ -121,7 +134,7 @@ def _check_linkattr_option(action, options, eid, _): if not 'linkattr' in options: - msg = _("'%s' action require 'linkattr' option") % action + msg = _("'%s' action requires 'linkattr' option") % action raise ValidationError(eid, {rn('options', 'subject'): msg}) @@ -230,19 +243,12 @@ break self.source.info('GET %s', url) stream = _OPENER.open(url) - return _entity_etree(etree.parse(stream).getroot()) - - def process_one(self, url): - # XXX assert len(root.children) == 1 - for item, rels in self.parse(url): - return self.process_item(item, rels) + return _parse_entity_etree(etree.parse(stream).getroot()) def process_item(self, item, rels): - entity = self.extid2entity(str(item.pop('cwuri')), - item.pop('cwtype'), + entity = self.extid2entity(str(item.pop('cwuri')), item.pop('cwtype'), item=item) - if not (self.created_during_pull(entity) - or self.updated_during_pull(entity)): + if not (self.created_during_pull(entity) or self.updated_during_pull(entity)): self.notify_updated(entity) item.pop('eid') # XXX check modification date @@ -250,16 +256,16 @@ entity.set_attributes(**attrs) for (rtype, role, action), rules in self.source.mapping.get(entity.__regid__, {}).iteritems(): try: - rel = rels[role][rtype] + related_items = rels[role][rtype] except KeyError: - self.source.error('relation %s-%s doesn\'t seem exported in %s xml', + self.source.error('relation %s-%s not found in xml export of %s', rtype, role, entity.__regid__) continue try: actionmethod = self.action_methods[action] except KeyError: raise Exception('Unknown action %s' % action) - actionmethod(entity, rtype, role, rel, rules) + actionmethod(entity, rtype, role, related_items, rules) return entity def before_entity_copy(self, entity, sourceparams): @@ -267,89 +273,89 @@ attrs = extract_typed_attrs(entity.e_schema, sourceparams['item']) entity.cw_edited.update(attrs) - def related_copy(self, entity, rtype, role, value, rules): + def related_copy(self, entity, rtype, role, others, rules): """implementation of 'copy' action Takes no option. """ assert not any(x[1] for x in rules), "'copy' action takes no option" ttypes = set([x[0] for x in rules]) - value = [item for item in value if item['cwtype'] in ttypes] + others = [item for item in others if item['cwtype'] in ttypes] eids = [] # local eids - if not value: + if not others: self._clear_relation(entity, rtype, role, ttypes) return - for item in value: - eids.append(self.process_one(self._complete_url(item)).eid) + for item in others: + item, _rels = self._complete_item(item) + other_entity = self.process_item(item, []) + eids.append(other_entity.eid) self._set_relation(entity, rtype, role, eids) - def related_link(self, entity, rtype, role, value, rules): + def related_link(self, entity, rtype, role, others, rules): """implementation of 'link' action requires an options to control search of the linked entity. """ for ttype, options in rules: assert 'linkattr' in options, ( - "'link-or-create' action require a list of attributes used to " + "'link' action requires a list of attributes used to " "search if the entity already exists") - self._related_link(entity, rtype, role, ttype, value, [options['linkattr']], - self._log_not_found) + self._related_link(entity, rtype, role, ttype, others, [options['linkattr']], + create_when_not_found=False) - def related_link_or_create(self, entity, rtype, role, value, rules): + def related_link_or_create(self, entity, rtype, role, others, rules): """implementation of 'link-or-create' action requires an options to control search of the linked entity. """ for ttype, options in rules: assert 'linkattr' in options, ( - "'link-or-create' action require a list of attributes used to " + "'link-or-create' action requires a list of attributes used to " "search if the entity already exists") - self._related_link(entity, rtype, role, ttype, value, [options['linkattr']], - self._create_not_found) - - def _log_not_found(self, entity, rtype, role, ritem, searchvalues): - self.source.error('can find %s entity with attributes %s', - ritem['cwtype'], searchvalues) + self._related_link(entity, rtype, role, ttype, others, [options['linkattr']], + create_when_not_found=True) - def _create_not_found(self, entity, rtype, role, ritem, searchvalues): - ensure_str_keys(searchvalues) # XXX necessary with python < 2.6 - return self._cw.create_entity(ritem['cwtype'], **searchvalues).eid - - def _related_link(self, entity, rtype, role, ttype, value, searchattrs, - notfound_callback): + def _related_link(self, entity, rtype, role, ttype, others, searchattrs, + create_when_not_found): + def issubset(x,y): + return all(z in y for z in x) eids = [] # local eids - for item in value: + for item in others: if item['cwtype'] != ttype: continue - if not all(attr in item for attr in searchattrs): - # need to fetch related entity's xml - ritems = list(self.parse(self._complete_url(item, False))) - assert len(ritems) == 1, 'unexpected xml' - ritem = ritems[0][0] # list of 2-uples - assert all(attr in ritem for attr in searchattrs), \ - 'missing attribute, got %s expected keys %s' % (item, searchattrs) - else: - ritem = item - kwargs = dict((attr, ritem[attr]) for attr in searchattrs) + if not issubset(searchattrs, item): + item, _rels = self._complete_item(item, False) + if not issubset(searchattrs, item): + self.source.error('missing attribute, got %s expected keys %s' + % item, searchattrs) + continue + kwargs = dict((attr, item[attr]) for attr in searchattrs) rql = build_search_rql(item['cwtype'], kwargs) rset = self._cw.execute(rql, kwargs) - if rset: - assert len(rset) == 1 + if len(rset) > 1: + self.source.error('ambiguous link: found %s entity %s with attributes %s', + len(rset), item['cwtype'], kwargs) + elif len(rset) == 1: eids.append(rset[0][0]) + elif create_when_not_found: + ensure_str_keys(kwargs) # XXX necessary with python < 2.6 + eids.append(self._cw.create_entity(item['cwtype'], **kwargs).eid) else: - eid = notfound_callback(entity, rtype, role, ritem, kwargs) - if eid is not None: - eids.append(eid) + self.source.error('can not find %s entity with attributes %s', + item['cwtype'], kwargs) if not eids: self._clear_relation(entity, rtype, role, (ttype,)) else: self._set_relation(entity, rtype, role, eids) - def _complete_url(self, item, add_relations=True): + def _complete_item(self, item, add_relations=True): itemurl = item['cwuri'] + '?vid=xml' - for rtype, role, _ in self.source.mapping.get(item['cwtype'], ()): - itemurl += '&relation=%s_%s' % (rtype, role) - return itemurl + if add_relations: + for rtype, role, _ in self.source.mapping.get(item['cwtype'], ()): + itemurl += '&relation=%s_%s' % (rtype, role) + item_rels = list(self.parse(itemurl)) + assert len(item_rels) == 1 + return item_rels[0] def _clear_relation(self, entity, rtype, role, ttypes): if entity.eid not in self.stats['created']: @@ -361,15 +367,18 @@ {'x': entity.eid}) def _set_relation(self, entity, rtype, role, eids): - eidstr = ','.join(str(eid) for eid in eids) - rql = rtype_role_rql(rtype, role) - self._cw.execute('DELETE %s, NOT Y eid IN (%s)' % (rql, eidstr), - {'x': entity.eid}) - if role == 'object': - rql = 'SET %s, Y eid IN (%s), NOT Y %s X' % (rql, eidstr, rtype) - else: - rql = 'SET %s, Y eid IN (%s), NOT X %s Y' % (rql, eidstr, rtype) + rqlbase = rtype_role_rql(rtype, role) + rql = 'DELETE %s' % rqlbase + if eids: + eidstr = ','.join(str(eid) for eid in eids) + rql += ', NOT Y eid IN (%s)' % eidstr self._cw.execute(rql, {'x': entity.eid}) + if eids: + if role == 'object': + rql = 'SET %s, Y eid IN (%s), NOT Y %s X' % (rqlbase, eidstr, rtype) + else: + rql = 'SET %s, Y eid IN (%s), NOT X %s Y' % (rqlbase, eidstr, rtype) + self._cw.execute(rql, {'x': entity.eid}) def registration_callback(vreg): vreg.register_all(globals().values(), __name__) diff -r dc319ece0bd6 -r c2b29631c1a3 sobjects/test/unittest_email.py --- a/sobjects/test/unittest_email.py Mon May 16 16:24:00 2011 +0200 +++ b/sobjects/test/unittest_email.py Mon May 16 16:25:33 2011 +0200 @@ -54,7 +54,8 @@ self.failIf(rset.rowcount != 1, rset) def test_security_check(self): - self.create_user('toto') + req = self.request() + self.create_user(req, 'toto') email1 = self.execute('INSERT EmailAddress E: E address "client@client.com", U use_email E WHERE U login "admin"')[0][0] self.commit() cnx = self.login('toto') diff -r dc319ece0bd6 -r c2b29631c1a3 sobjects/test/unittest_notification.py --- a/sobjects/test/unittest_notification.py Mon May 16 16:24:00 2011 +0200 +++ b/sobjects/test/unittest_notification.py Mon May 16 16:25:33 2011 +0200 @@ -84,7 +84,7 @@ def test_status_change_view(self): req = self.request() - u = self.create_user('toto', req=req) + u = self.create_user(req, 'toto') u.cw_adapt_to('IWorkflowable').fire_transition('deactivate', comment=u'yeah') self.failIf(MAILBOX) self.commit() diff -r dc319ece0bd6 -r c2b29631c1a3 sobjects/test/unittest_parsers.py --- a/sobjects/test/unittest_parsers.py Mon May 16 16:24:00 2011 +0200 +++ b/sobjects/test/unittest_parsers.py Mon May 16 16:25:33 2011 +0200 @@ -129,7 +129,7 @@ } }) session = self.repo.internal_session() - stats = dfsource.pull_data(session, force=True) + stats = dfsource.pull_data(session, force=True, raise_on_error=True) self.assertEqual(sorted(stats.keys()), ['created', 'updated']) self.assertEqual(len(stats['created']), 2) self.assertEqual(stats['updated'], set()) @@ -156,12 +156,12 @@ self.assertEqual(tag.cwuri, 'http://testing.fr/cubicweb/%s' % tag.eid) self.assertEqual(tag.cw_source[0].name, 'system') - stats = dfsource.pull_data(session, force=True) + stats = dfsource.pull_data(session, force=True, raise_on_error=True) self.assertEqual(stats['created'], set()) self.assertEqual(len(stats['updated']), 2) self.repo._type_source_cache.clear() self.repo._extid_cache.clear() - stats = dfsource.pull_data(session, force=True) + stats = dfsource.pull_data(session, force=True, raise_on_error=True) self.assertEqual(stats['created'], set()) self.assertEqual(len(stats['updated']), 2) diff -r dc319ece0bd6 -r c2b29631c1a3 test/data/schema.py --- a/test/data/schema.py Mon May 16 16:24:00 2011 +0200 +++ b/test/data/schema.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 test/unittest_entity.py --- a/test/unittest_entity.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_entity.py Mon May 16 16:25:33 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) @@ -128,6 +139,27 @@ self.assertEqual(len(p.related('tags', 'object', limit=2)), 2) self.assertEqual(len(p.related('tags', 'object')), 4) + def test_cw_instantiate_relation(self): + req = self.request() + p1 = req.create_entity('Personne', nom=u'di') + p2 = req.create_entity('Personne', nom=u'mascio') + t = req.create_entity('Tag', name=u't1', tags=p1) + self.assertItemsEqual(t.tags, [p1]) + t = req.create_entity('Tag', name=u't2', tags=p1.eid) + self.assertItemsEqual(t.tags, [p1]) + t = req.create_entity('Tag', name=u't3', tags=[p1, p2.eid]) + self.assertItemsEqual(t.tags, [p1, p2]) + + def test_cw_instantiate_reverse_relation(self): + req = self.request() + t1 = req.create_entity('Tag', name=u't1') + t2 = req.create_entity('Tag', name=u't2') + p = req.create_entity('Personne', nom=u'di mascio', reverse_tags=t1) + self.assertItemsEqual(p.reverse_tags, [t1]) + p = req.create_entity('Personne', nom=u'di mascio', reverse_tags=t1.eid) + self.assertItemsEqual(p.reverse_tags, [t1]) + p = req.create_entity('Personne', nom=u'di mascio', reverse_tags=[t1, t2.eid]) + self.assertItemsEqual(p.reverse_tags, [t1, t2]) def test_fetch_rql(self): user = self.user() @@ -136,17 +168,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') @@ -158,9 +192,9 @@ # testing two non final relations Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee') self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC ' - 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, ' - 'X evaluee AE?, AE modification_date AF') + 'Any X,AA,AB,AC,AD,AE ORDERBY AA ASC ' + 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, ' + 'X evaluee AE?') # testing one non final relation with recursion Personne.fetch_attrs = ('nom', 'prenom', 'travaille') Societe.fetch_attrs = ('nom', 'evaluee') @@ -185,8 +219,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,52 +262,90 @@ 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): - self.create_user('toto') + req = self.request() + self.create_user(req, 'toto') self.login('toto') - user = self.request().user + user = req.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() @@ -302,7 +375,8 @@ user = self.request().user rset = user.unrelated('use_email', 'EmailAddress', 'subject') self.assertEqual([x.address for x in rset.entities()], [u'hop']) - self.create_user('toto') + req = self.request() + self.create_user(req, 'toto') self.login('toto') email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0) rset = email.unrelated('use_email', 'CWUser', 'object') @@ -511,6 +585,15 @@ self.assertEqual(person.prenom, u'sylvain') self.assertEqual(person.nom, u'thénault') + def test_set_relations(self): + req = self.request() + person = req.create_entity('Personne', nom=u'chauvat', prenom=u'nicolas') + note = req.create_entity('Note', type=u'x') + note.set_relations(ecrit_par=person) + note = req.create_entity('Note', type=u'y') + note.set_relations(ecrit_par=person.eid) + self.assertEqual(len(person.reverse_ecrit_par), 2) + def test_metainformation_and_external_absolute_url(self): req = self.request() note = req.create_entity('Note', type=u'z') diff -r dc319ece0bd6 -r c2b29631c1a3 test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_rqlrewrite.py Mon May 16 16:25:33 2011 +0200 @@ -353,14 +353,21 @@ self.failUnlessEqual(rqlst.as_string(), u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)") - def test_rqlexpr_not_relation1(self): + def test_rqlexpr_not_relation_1_1(self): constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X') self.failUnlessEqual(rqlst.as_string(), u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') - def test_rqlexpr_not_relation2(self): + def test_rqlexpr_not_relation_1_2(self): + constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') + rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)') + rewrite(rqlst, {('A', 'X'): (constraint,)}, {}, 'X') + self.failUnlessEqual(rqlst.as_string(), + u'Any A WHERE NOT EXISTS(A documented_by C, C is Card), A is Affaire, EXISTS(A owned_by B, B login "hop", B is CWUser)') + + def test_rqlexpr_not_relation_2(self): constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = rqlhelper.parse('Affaire A WHERE NOT A documented_by C', annotate=False) rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X') diff -r dc319ece0bd6 -r c2b29631c1a3 test/unittest_rset.py --- a/test/unittest_rset.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_rset.py Mon May 16 16:25:33 2011 +0200 @@ -114,7 +114,6 @@ description=[['CWUser', 'String']] * 3) rs.req = self.request() rs.vreg = self.vreg - self.assertEqual(rs.limit(2).rows, [[12000, 'adim'], [13000, 'syt']]) rs2 = rs.limit(2, offset=1) self.assertEqual(rs2.rows, [[13000, 'syt'], [14000, 'nico']]) diff -r dc319ece0bd6 -r c2b29631c1a3 test/unittest_schema.py --- a/test/unittest_schema.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_schema.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 test/unittest_selectors.py --- a/test/unittest_selectors.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_selectors.py Mon May 16 16:25:33 2011 +0200 @@ -26,7 +26,7 @@ from cubicweb.appobject import Selector, AndSelector, OrSelector from cubicweb.selectors import (is_instance, adaptable, match_user_groups, multi_lines_rset, score_entity, is_in_state, - on_transition) + on_transition, rql_condition) from cubicweb.web import action @@ -221,7 +221,7 @@ def test_is_in_state(self): for state in ('created', 'validated', 'abandoned'): selector = is_in_state(state) - self.assertEqual(selector(None, self.req, self.rset), + self.assertEqual(selector(None, self.req, rset=self.rset), state=="created") self.adapter.fire_transition('validate') @@ -229,75 +229,75 @@ self.assertEqual(self.adapter.state, 'validated') selector = is_in_state('created') - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) selector = is_in_state('validated') - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) selector = is_in_state('validated', 'abandoned') - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) selector = is_in_state('abandoned') - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) self.adapter.fire_transition('forsake') self._commit() self.assertEqual(self.adapter.state, 'abandoned') selector = is_in_state('created') - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) selector = is_in_state('validated') - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) selector = is_in_state('validated', 'abandoned') - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) self.assertEqual(self.adapter.state, 'abandoned') - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) def test_is_in_state_unvalid_names(self): selector = is_in_state("unknown") with self.assertRaises(ValueError) as cm: - selector(None, self.req, self.rset) + selector(None, self.req, rset=self.rset) self.assertEqual(str(cm.exception), "wf_test: unknown state(s): unknown") selector = is_in_state("weird", "unknown", "created", "weird") with self.assertRaises(ValueError) as cm: - selector(None, self.req, self.rset) + selector(None, self.req, rset=self.rset) self.assertEqual(str(cm.exception), "wf_test: unknown state(s): unknown,weird") def test_on_transition(self): for transition in ('validate', 'forsake'): selector = on_transition(transition) - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) self.adapter.fire_transition('validate') self._commit() self.assertEqual(self.adapter.state, 'validated') selector = on_transition("validate") - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) selector = on_transition("validate", "forsake") - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) selector = on_transition("forsake") - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) self.adapter.fire_transition('forsake') self._commit() self.assertEqual(self.adapter.state, 'abandoned') selector = on_transition("validate") - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) selector = on_transition("validate", "forsake") - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) selector = on_transition("forsake") - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) def test_on_transition_unvalid_names(self): selector = on_transition("unknown") with self.assertRaises(ValueError) as cm: - selector(None, self.req, self.rset) + selector(None, self.req, rset=self.rset) self.assertEqual(str(cm.exception), "wf_test: unknown transition(s): unknown") selector = on_transition("weird", "unknown", "validate", "weird") with self.assertRaises(ValueError) as cm: - selector(None, self.req, self.rset) + selector(None, self.req, rset=self.rset) self.assertEqual(str(cm.exception), "wf_test: unknown transition(s): unknown,weird") @@ -308,11 +308,11 @@ self.assertEqual(self.adapter.state, 'validated') selector = on_transition("validate") - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) selector = on_transition("validate", "forsake") - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) selector = on_transition("forsake") - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) class MatchUserGroupsTC(CubicWebTC): @@ -328,7 +328,8 @@ self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) try: # login as a simple user - self.create_user('john') + req = self.request() + self.create_user(req, 'john') self.login('john') # it should not be possible to use SomeAction not owned objects req = self.request() @@ -361,13 +362,13 @@ def test_default_op_in_selector(self): expected = len(self.rset) selector = multi_lines_rset(expected) - self.assertEqual(selector(None, self.req, self.rset), 1) + self.assertEqual(selector(None, self.req, rset=self.rset), 1) self.assertEqual(selector(None, self.req, None), 0) selector = multi_lines_rset(expected + 1) - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) self.assertEqual(selector(None, self.req, None), 0) selector = multi_lines_rset(expected - 1) - self.assertEqual(selector(None, self.req, self.rset), 0) + self.assertEqual(selector(None, self.req, rset=self.rset), 0) self.assertEqual(selector(None, self.req, None), 0) def test_without_rset(self): @@ -398,7 +399,7 @@ for (expected, operator, assertion) in testdata: selector = multi_lines_rset(expected, operator) - yield self.assertEqual, selector(None, self.req, self.rset), assertion + yield self.assertEqual, selector(None, self.req, rset=self.rset), assertion class ScoreEntitySelectorTC(CubicWebTC): @@ -407,17 +408,31 @@ req = self.request() rset = req.execute('Any E WHERE E eid 1') selector = score_entity(lambda x: None) - self.assertEqual(selector(None, req, rset), 0) + self.assertEqual(selector(None, req, rset=rset), 0) selector = score_entity(lambda x: "something") - self.assertEqual(selector(None, req, rset), 1) + self.assertEqual(selector(None, req, rset=rset), 1) selector = score_entity(lambda x: object) - self.assertEqual(selector(None, req, rset), 1) + self.assertEqual(selector(None, req, rset=rset), 1) rset = req.execute('Any G LIMIT 2 WHERE G is CWGroup') selector = score_entity(lambda x: 10) - self.assertEqual(selector(None, req, rset), 20) + self.assertEqual(selector(None, req, rset=rset), 20) selector = score_entity(lambda x: 10, once_is_enough=True) - self.assertEqual(selector(None, req, rset), 10) + self.assertEqual(selector(None, req, rset=rset), 10) + def test_rql_condition_entity(self): + req = self.request() + selector = rql_condition('X identity U') + rset = req.user.as_rset() + self.assertEqual(selector(None, req, rset=rset), 1) + self.assertEqual(selector(None, req, entity=req.user), 1) + self.assertEqual(selector(None, req), 0) + + def test_rql_condition_user(self): + req = self.request() + selector = rql_condition('U login "admin"', user_condition=True) + self.assertEqual(selector(None, req), 1) + selector = rql_condition('U login "toto"', user_condition=True) + self.assertEqual(selector(None, req), 0) if __name__ == '__main__': unittest_main() diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/ajax_url0.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajax_url0.html Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,3 @@ +

+

Hello

+
diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/ajax_url1.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajax_url1.html Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,6 @@ +
+
+ +
+

Hello

+
diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/ajax_url2.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajax_url2.html Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,7 @@ +
+
+ + +
+

Hello

+
diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/ajaxresult.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajaxresult.json Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,1 @@ +['foo', 'bar'] diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/test_ajax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_ajax.html Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + +
+

cubicweb.ajax.js functions tests

+

+
    + + diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/test_ajax.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_ajax.js Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,244 @@ +$(document).ready(function() { + + module("ajax", { + setup: function() { + this.scriptsLength = $('head script[src]').length-1; + this.cssLength = $('head link[rel=stylesheet]').length-1; + // re-initialize cw loaded cache so that each tests run in a + // clean environment, have a lookt at _loadAjaxHtmlHead implementation + // in cubicweb.ajax.js for more information. + cw.loaded_src = []; + cw.loaded_href = []; + }, + teardown: function() { + $('head script[src]:gt(' + this.scriptsLength + ')').remove(); + $('head link[rel=stylesheet]:gt(' + this.cssLength + ')').remove(); + } + }); + + function jsSources() { + return $.map($('head script[src]'), function(script) { + return script.getAttribute('src'); + }); + } + + test('test simple h1 inclusion (ajax_url0.html)', function() { + expect(3); + equals(jQuery('#main').children().length, 0); + stop(); + jQuery('#main').loadxhtml('/../ajax_url0.html', { + callback: function() { + equals(jQuery('#main').children().length, 1); + equals(jQuery('#main h1').html(), 'Hello'); + start(); + } + }); + }); + + test('test simple html head inclusion (ajax_url1.html)', function() { + expect(6); + var scriptsIncluded = jsSources(); + equals(jQuery.inArray('http://foo.js', scriptsIncluded), - 1); + stop(); + jQuery('#main').loadxhtml('/../ajax_url1.html', { + callback: function() { + var origLength = scriptsIncluded.length; + scriptsIncluded = jsSources(); + // check that foo.js has been *appended* to + equals(scriptsIncluded.length, origLength + 1); + equals(scriptsIncluded[origLength].indexOf('http://foo.js'), 0); + // check that
    has been removed + equals(jQuery('#main').children().length, 1); + equals(jQuery('div.ajaxHtmlHead').length, 0); + equals(jQuery('#main h1').html(), 'Hello'); + start(); + } + }); + }); + + test('test addCallback', function() { + expect(3); + equals(jQuery('#main').children().length, 0); + stop(); + var d = jQuery('#main').loadxhtml('/../ajax_url0.html'); + d.addCallback(function() { + equals(jQuery('#main').children().length, 1); + equals(jQuery('#main h1').html(), 'Hello'); + start(); + }); + }); + + test('test callback after synchronous request', function() { + expect(1); + var deferred = new Deferred(); + var result = jQuery.ajax({ + url: './ajax_url0.html', + async: false, + beforeSend: function(xhr) { + deferred._req = xhr; + }, + success: function(data, status) { + deferred.success(data); + } + }); + stop(); + deferred.addCallback(function() { + // add an assertion to ensure the callback is executed + ok(true, "callback is executed"); + start(); + }); + }); + + test('test addCallback with parameters', function() { + expect(3); + equals(jQuery('#main').children().length, 0); + stop(); + var d = jQuery('#main').loadxhtml('/../ajax_url0.html'); + d.addCallback(function(data, req, arg1, arg2) { + equals(arg1, 'Hello'); + equals(arg2, 'world'); + start(); + }, + 'Hello', 'world'); + }); + + test('test callback after synchronous request with parameters', function() { + var deferred = new Deferred(); + var result = jQuery.ajax({ + url: '/../ajax_url0.html', + async: false, + beforeSend: function(xhr) { + deferred._req = xhr; + }, + success: function(data, status) { + deferred.success(data); + } + }); + deferred.addCallback(function(data, req, arg1, arg2) { + // add an assertion to ensure the callback is executed + ok(true, "callback is executed"); + equals(arg1, 'Hello'); + equals(arg2, 'world'); + }, + 'Hello', 'world'); + }); + + test('test addErrback', function() { + expect(1); + stop(); + var d = jQuery('#main').loadxhtml('/../ajax_url0.html'); + d.addCallback(function() { + // throw an exception to start errback chain + throw new Error(); + }); + d.addErrback(function() { + ok(true, "errback is executed"); + start(); + }); + }); + + test('test callback / errback execution order', function() { + expect(4); + var counter = 0; + stop(); + var d = jQuery('#main').loadxhtml('/../ajax_url0.html', { + callback: function() { + equals(++counter, 1); // should be executed first + start(); + } + }); + d.addCallback(function() { + equals(++counter, 2); // should be executed and break callback chain + throw new Error(); + }); + d.addCallback(function() { + // should not be executed since second callback raised an error + ok(false, "callback is executed"); + }); + d.addErrback(function() { + // should be executed after the second callback + equals(++counter, 3); + }); + d.addErrback(function() { + // should be executed after the first errback + equals(++counter, 4); + }); + }); + + test('test already included resources are ignored (ajax_url1.html)', function() { + expect(10); + var scriptsIncluded = jsSources(); + // NOTE: + equals(jQuery.inArray('http://foo.js', scriptsIncluded), -1); + equals(jQuery('head link').length, 1); + /* use endswith because in pytest context we have an absolute path */ + ok(jQuery('head link').attr('href').endswith('/qunit.css')); + stop(); + jQuery('#main').loadxhtml('/../ajax_url1.html', { + callback: function() { + var origLength = scriptsIncluded.length; + scriptsIncluded = jsSources(); + try { + // check that foo.js has been inserted in + equals(scriptsIncluded.length, origLength + 1); + equals(scriptsIncluded[origLength].indexOf('http://foo.js'), 0); + // check that
    has been removed + equals(jQuery('#main').children().length, 1); + equals(jQuery('div.ajaxHtmlHead').length, 0); + equals(jQuery('#main h1').html(), 'Hello'); + // qunit.css is not added twice + equals(jQuery('head link').length, 1); + /* use endswith because in pytest context we have an absolute path */ + ok(jQuery('head link').attr('href').endswith('/qunit.css')); + } finally { + start(); + } + } + }); + }); + + test('test synchronous request loadRemote', function() { + var res = loadRemote('/../ajaxresult.json', {}, + 'GET', true); + same(res, ['foo', 'bar']); + }); + + test('test event on CubicWeb', function() { + expect(1); + stop(); + var events = null; + jQuery(CubicWeb).bind('server-response', function() { + // check that server-response event on CubicWeb is triggered + events = 'CubicWeb'; + }); + jQuery('#main').loadxhtml('/../ajax_url0.html', { + callback: function() { + equals(events, 'CubicWeb'); + start(); + } + }); + }); + + test('test event on node', function() { + expect(3); + stop(); + var nodes = []; + jQuery('#main').bind('server-response', function() { + nodes.push('node'); + }); + jQuery(CubicWeb).bind('server-response', function() { + nodes.push('CubicWeb'); + }); + jQuery('#main').loadxhtml('/../ajax_url0.html', { + callback: function() { + equals(nodes.length, 2); + // check that server-response event on CubicWeb is triggered + // only once and event server-response on node is triggered + equals(nodes[0], 'CubicWeb'); + equals(nodes[1], 'node'); + start(); + } + }); + }); +}); + diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/test_htmlhelpers.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_htmlhelpers.html Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + +
    +
    +

    cubicweb.htmlhelpers.js functions tests

    +

    +

    +
      + + diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/test_htmlhelpers.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_htmlhelpers.js Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,36 @@ +$(document).ready(function() { + + module("module2", { + setup: function() { + $('#main').append(''); + } + }); + + test("test first selected", function() { + $('#theselect').append('' + + '' + + '' + + ''); + var selected = firstSelected(document.getElementById("theselect")); + equals(selected.value, 'bar'); + }); + + test("test first selected 2", function() { + $('#theselect').append('' + + '' + + '' + + ''); + var selected = firstSelected(document.getElementById("theselect")); + equals(selected, null); + }); + + module("visibilty"); + test('toggleVisibility', function() { + $('#main').append('
      '); + toggleVisibility('foo'); + ok($('#foo').hasClass('hidden'), 'check hidden class is set'); + }); + +}); + diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/test_utils.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_utils.html Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + +
      +
      +

      cw.utils functions tests

      +

      +

      +
        + + diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/test_utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_utils.js Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,92 @@ +$(document).ready(function() { + + module("datetime"); + + test("test full datetime", function() { + equals(cw.utils.toISOTimestamp(new Date(1986, 3, 18, 10, 30, 0, 0)), + '1986-04-18 10:30:00'); + }); + + test("test only date", function() { + equals(cw.utils.toISOTimestamp(new Date(1986, 3, 18)), '1986-04-18 00:00:00'); + }); + + test("test null", function() { + equals(cw.utils.toISOTimestamp(null), null); + }); + + module("parsing"); + test("test basic number parsing", function() { + var d = strptime('2008/08/08', '%Y/%m/%d'); + same(datetuple(d), [2008, 8, 8, 0, 0]); + d = strptime('2008/8/8', '%Y/%m/%d'); + same(datetuple(d), [2008, 8, 8, 0, 0]); + d = strptime('8/8/8', '%Y/%m/%d'); + same(datetuple(d), [8, 8, 8, 0, 0]); + d = strptime('0/8/8', '%Y/%m/%d'); + same(datetuple(d), [0, 8, 8, 0, 0]); + d = strptime('-10/8/8', '%Y/%m/%d'); + same(datetuple(d), [-10, 8, 8, 0, 0]); + d = strptime('-35000', '%Y'); + same(datetuple(d), [-35000, 1, 1, 0, 0]); + }); + + test("test custom format parsing", function() { + var d = strptime('2008-08-08', '%Y-%m-%d'); + same(datetuple(d), [2008, 8, 8, 0, 0]); + d = strptime('2008 - ! 08: 08', '%Y - ! %m: %d'); + same(datetuple(d), [2008, 8, 8, 0, 0]); + d = strptime('2008-08-08 12:14', '%Y-%m-%d %H:%M'); + same(datetuple(d), [2008, 8, 8, 12, 14]); + d = strptime('2008-08-08 1:14', '%Y-%m-%d %H:%M'); + same(datetuple(d), [2008, 8, 8, 1, 14]); + d = strptime('2008-08-08 01:14', '%Y-%m-%d %H:%M'); + same(datetuple(d), [2008, 8, 8, 1, 14]); + }); + + module("sliceList"); + test("test slicelist", function() { + var list = ['a', 'b', 'c', 'd', 'e', 'f']; + same(sliceList(list, 2), ['c', 'd', 'e', 'f']); + same(sliceList(list, 2, -2), ['c', 'd']); + same(sliceList(list, -3), ['d', 'e', 'f']); + same(sliceList(list, 0, -2), ['a', 'b', 'c', 'd']); + same(sliceList(list), list); + }); + + module("formContents", { + setup: function() { + $('#main').append('
        '); + } + }); + // XXX test fckeditor + test("test formContents", function() { + $('#test-form').append(''); + $('#test-form').append(' '); + $('#test-form').append(''); + $('#test-form').append(''); + $('#test-form').append(''); + $('#test-form').append(''); + $('#test-form').append(''); + $('#theselect').append('' + + ''); + //Append an unchecked radio input : should not be in formContents list + $('#test-form').append(''); + $('#test-form').append(''); + same(formContents($('#test-form')[0]), [ + ['input-text', 'mytextarea', 'choice', 'check', 'theselect'], + ['toto', 'Hello World!', 'no', 'no', 'foo'] + ]); + }); +}); + diff -r dc319ece0bd6 -r c2b29631c1a3 testfunc/test/jstests/utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/utils.js Mon May 16 16:25:33 2011 +0200 @@ -0,0 +1,29 @@ +function datetuple(d) { + return [d.getFullYear(), d.getMonth()+1, d.getDate(), + d.getHours(), d.getMinutes()]; +} + +function pprint(obj) { + print('{'); + for(k in obj) { + print(' ' + k + ' = ' + obj[k]); + } + print('}'); +} + +function arrayrepr(array) { + return '[' + array.join(', ') + ']'; +} + +function assertArrayEquals(array1, array2) { + if (array1.length != array2.length) { + throw new crosscheck.AssertionFailure(array1.join(', ') + ' != ' + array2.join(', ')); + } + for (var i=0; i\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, @@ -382,7 +408,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 dc319ece0bd6 -r c2b29631c1a3 view.py --- a/view.py Mon May 16 16:24:00 2011 +0200 +++ b/view.py Mon May 16 16:25:33 2011 +0200 @@ -447,11 +447,14 @@ rqlstdescr = self.cw_rset.syntax_tree().get_description(mainindex, translate)[0] labels = [] - for colindex, label in enumerate(rqlstdescr): - # compute column header - if label == 'Any': # find a better label - label = ','.join(translate(et) - for et in self.cw_rset.column_types(colindex)) + for colidx, label in enumerate(rqlstdescr): + try: + label = getattr(self, 'label_column_%s' % colidx)() + except AttributeError: + # compute column header + if label == 'Any': # find a better label + label = ','.join(translate(et) + for et in self.cw_rset.column_types(colidx)) labels.append(label) return labels diff -r dc319ece0bd6 -r c2b29631c1a3 web/_exceptions.py --- a/web/_exceptions.py Mon May 16 16:24:00 2011 +0200 +++ b/web/_exceptions.py Mon May 16 16:25:33 2011 +0200 @@ -53,6 +53,9 @@ self.status = int(status) self.content = content + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.status, self.content) + class InvalidSession(CubicWebException): """raised when a session id is found but associated session is not found or invalid diff -r dc319ece0bd6 -r c2b29631c1a3 web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.ajax.js Mon May 16 16:25:33 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. @@ -605,7 +605,7 @@ var ajaxArgs = ['render', formparams, registry, compid]; ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4)); var params = ajaxFuncArgs.apply(null, ajaxArgs); - $('#'+domid).loadxhtml('json', params, null, 'swap'); + return $('#'+domid).loadxhtml('json', params, null, 'swap'); } /* ajax tabs ******************************************************************/ diff -r dc319ece0bd6 -r c2b29631c1a3 web/data/cubicweb.css --- a/web/data/cubicweb.css Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.css Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/data/cubicweb.lazy.js --- a/web/data/cubicweb.lazy.js Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ - diff -r dc319ece0bd6 -r c2b29631c1a3 web/data/cubicweb.old.css --- a/web/data/cubicweb.old.css Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.old.css Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/data/cubicweb.reledit.js --- a/web/data/cubicweb.reledit.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.reledit.js Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/data/cubicweb.tabs.js --- a/web/data/cubicweb.tabs.js Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ - diff -r dc319ece0bd6 -r c2b29631c1a3 web/data/jquery.corner.js --- a/web/data/jquery.corner.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/jquery.corner.js Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/data/jquery.js --- a/web/data/jquery.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/jquery.js Mon May 16 16:25:33 2011 +0200 @@ -13,6228 +13,142 @@ * * Date: Sat Feb 13 22:33:48 2010 -0500 */ -(function( window, undefined ) { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // Use the correct document accordingly with window argument (sandbox) - document = window.document, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, - - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - - // Used for trimming whitespace - rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // Has the ready events already been bound? - readyBound = false, - - // The functions to execute on DOM ready - readyList = [], - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwnProperty = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - indexOf = Array.prototype.indexOf; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if ( !selector ) { - return this; - } - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if ( selector === "body" && !context ) { - this.context = document; - this[0] = document.body; - this.selector = "body"; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - doc = (context ? context.ownerDocument || context : document); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; - } - - return jQuery.merge( this, selector ); - - // HANDLE: $("#id") - } else { - elem = document.getElementById( match[2] ); - - if ( elem ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $("TAG") - } else if ( !context && /^\w+$/.test( selector ) ) { - this.selector = selector; - this.context = document; - selector = document.getElementsByTagName( selector ); - return jQuery.merge( this, selector ); - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return (context || rootjQuery).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return jQuery( context ).find( selector ); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.4.2", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return slice.call( this, 0 ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = jQuery(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - - } else { - jQuery.merge( ret, elems ); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) { - ret.selector = this.selector + (this.selector ? " " : "") + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - - // If the DOM is already ready - if ( jQuery.isReady ) { - // Execute the function immediately - fn.call( document, jQuery ); - - // Otherwise, remember the function for later - } else if ( readyList ) { - // Add the function to the wait list - readyList.push( fn ); - } - - return this; - }, - - eq: function( i ) { - return i === -1 ? - this.slice( i ) : - this.slice( i, +i + 1 ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || jQuery(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging object literal values or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { - var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src - : jQuery.isArray(copy) ? [] : {}; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // Handle when the DOM is ready - ready: function() { - // Make sure that the DOM is not already loaded - if ( !jQuery.isReady ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 13 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If there are functions bound, to execute - if ( readyList ) { - // Execute all of them - var fn, i = 0; - while ( (fn = readyList[ i++ ]) ) { - fn.call( document, jQuery ); - } - - // Reset the list of functions - readyList = null; - } - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyBound ) { - return; - } - - readyBound = true; - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - return jQuery.ready(); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent("onreadystatechange", DOMContentLoaded); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch(e) {} - - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return toString.call(obj) === "[object Function]"; - }, - - isArray: function( obj ) { - return toString.call(obj) === "[object Array]"; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { - return false; - } - - // Not own constructor property must be Object - if ( obj.constructor - && !hasOwnProperty.call(obj, "constructor") - && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || hasOwnProperty.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - for ( var name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw msg; - }, - - parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") - .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { - - // Try to use the native JSON parser first - return window.JSON && window.JSON.parse ? - window.JSON.parse( data ) : - (new Function("return " + data))(); - - } else { - jQuery.error( "Invalid JSON: " + data ); - } - }, - - noop: function() {}, - - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && rnotwhite.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - - if ( jQuery.support.scriptEval ) { - script.appendChild( document.createTextNode( data ) ); - } else { - script.text = data; - } - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction(object); - - if ( args ) { - if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - } else { - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} - } - } - - return object; - }, - - trim: function( text ) { - return (text || "").replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; - - if ( array != null ) { - // The window, strings (and functions) also have 'length' - // The extra typeof function check is to prevent crashes - // in Safari 2 (See: #3039) - if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { - push.call( ret, array ); - } else { - jQuery.merge( ret, array ); - } - } - - return ret; - }, - - inArray: function( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; - }, - - merge: function( first, second ) { - var i = first.length, j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var ret = []; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { - if ( !inv !== !callback( elems[ i ], i ) ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var ret = [], value; - - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; - - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - } - - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; - } - - // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } - - // So proxy can be declared as an argument - return proxy; - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); - - var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || - /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || - /(msie) ([\w.]+)/.exec( ua ) || - !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - browser: {} -}); - -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} - -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} - -if ( indexOf ) { - jQuery.inArray = function( elem, array ) { - return indexOf.call( array, elem ); - }; -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); - -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; - -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); - } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch( error ) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -function evalScript( i, elem ) { - if ( elem.src ) { - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - } else { - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } -} - -// Mutifunctional method to get and set values to a collection -// The value/s can be optionally by executed if its a function -function access( elems, key, value, exec, fn, pass ) { - var length = elems.length; - - // Setting many attributes - if ( typeof key === "object" ) { - for ( var k in key ) { - access( elems, k, key[k], exec, fn, value ); - } - return elems; - } - - // Setting one attribute - if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); - - for ( var i = 0; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); - } - - return elems; - } - - // Getting an attribute - return length ? fn( elems[0], key ) : undefined; -} - -function now() { - return (new Date).getTime(); -} -(function() { - - jQuery.support = {}; - - var root = document.documentElement, - script = document.createElement("script"), - div = document.createElement("div"), - id = "script" + now(); - - div.style.display = "none"; - div.innerHTML = "
        a"; - - var all = div.getElementsByTagName("*"), - a = div.getElementsByTagName("a")[0]; - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return; - } - - jQuery.support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText insted) - style: /red/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55$/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: div.getElementsByTagName("input")[0].value === "on", - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, - - parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, - - // Will be defined later - deleteExpando: true, - checkClone: false, - scriptEval: false, - noCloneEvent: true, - boxModel: null - }; - - script.type = "text/javascript"; - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - // Make sure that the execution of code works by injecting a script - // tag with appendChild/createTextNode - // (IE doesn't support this, fails, and uses .text instead) - if ( window[ id ] ) { - jQuery.support.scriptEval = true; - delete window[ id ]; - } - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete script.test; - - } catch(e) { - jQuery.support.deleteExpando = false; - } - - root.removeChild( script ); - - if ( div.attachEvent && div.fireEvent ) { - div.attachEvent("onclick", function click() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - jQuery.support.noCloneEvent = false; - div.detachEvent("onclick", click); - }); - div.cloneNode(true).fireEvent("onclick"); - } - - div = document.createElement("div"); - div.innerHTML = ""; - - var fragment = document.createDocumentFragment(); - fragment.appendChild( div.firstChild ); - - // WebKit doesn't clone checked state correctly in fragments - jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; - - // Figure out if the W3C box model works as expected - // document.body must exist before we can do this - jQuery(function() { - var div = document.createElement("div"); - div.style.width = div.style.paddingLeft = "1px"; - - document.body.appendChild( div ); - jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; - document.body.removeChild( div ).style.display = 'none'; - - div = null; - }); - - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - var eventSupported = function( eventName ) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - var isSupported = (eventName in el); - if ( !isSupported ) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - el = null; - - return isSupported; - }; - - jQuery.support.submitBubbles = eventSupported("submit"); - jQuery.support.changeBubbles = eventSupported("change"); - - // release memory in IE - root = script = div = all = a = null; -})(); - -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; -var expando = "jQuery" + now(), uuid = 0, windowData = {}; - -jQuery.extend({ - cache: {}, - - expando:expando, - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - "object": true, - "applet": true - }, - - data: function( elem, name, data ) { - if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { - return; - } - - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ], cache = jQuery.cache, thisCache; - - if ( !id && typeof name === "string" && data === undefined ) { - return null; - } - - // Compute a unique ID for the element - if ( !id ) { - id = ++uuid; - } - - // Avoid generating a new cache unless none exists and we - // want to manipulate it. - if ( typeof name === "object" ) { - elem[ expando ] = id; - thisCache = cache[ id ] = jQuery.extend(true, {}, name); - - } else if ( !cache[ id ] ) { - elem[ expando ] = id; - cache[ id ] = {}; - } - - thisCache = cache[ id ]; - - // Prevent overriding the named cache with undefined values - if ( data !== undefined ) { - thisCache[ name ] = data; - } - - return typeof name === "string" ? thisCache[ name ] : thisCache; - }, - - removeData: function( elem, name ) { - if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { - return; - } - - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; - - // If we want to remove a specific section of the element's data - if ( name ) { - if ( thisCache ) { - // Remove the section of cache data - delete thisCache[ name ]; - - // If we've removed all the data, remove the element's cache - if ( jQuery.isEmptyObject(thisCache) ) { - jQuery.removeData( elem ); - } - } - - // Otherwise, we want to remove all of the element's data - } else { - if ( jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; - - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } - - // Completely remove the data cache - delete cache[ id ]; - } - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - if ( typeof key === "undefined" && this.length ) { - return jQuery.data( this[0] ); - - } else if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value === undefined ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - if ( data === undefined && this.length ) { - data = jQuery.data( this[0], key ); - } - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - } else { - return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { - jQuery.data( this, key, value ); - }); - } - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); -jQuery.extend({ - queue: function( elem, type, data ) { - if ( !elem ) { - return; - } - - type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( !data ) { - return q || []; - } - - if ( !q || jQuery.isArray(data) ) { - q = jQuery.data( elem, type, jQuery.makeArray(data) ); - - } else { - q.push( data ); - } - - return q; - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), fn = queue.shift(); - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - } - - if ( fn ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift("inprogress"); - } - - fn.call(elem, function() { - jQuery.dequeue(elem, type); - }); - } - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - } - - if ( data === undefined ) { - return jQuery.queue( this[0], type ); - } - return this.each(function( i, elem ) { - var queue = jQuery.queue( this, type, data ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; - type = type || "fx"; - - return this.queue( type, function() { - var elem = this; - setTimeout(function() { - jQuery.dequeue( elem, type ); - }, time ); - }); - }, - - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - } -}); -var rclass = /[\n\t]/g, - rspace = /\s+/, - rreturn = /\r/g, - rspecialurl = /href|src|style/, - rtype = /(button|input)/i, - rfocusable = /(button|input|object|select|textarea)/i, - rclickable = /^(a|area)$/i, - rradiocheck = /radio|checkbox/; - -jQuery.fn.extend({ - attr: function( name, value ) { - return access( this, name, value, true, jQuery.attr ); - }, - - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); - if ( this.nodeType === 1 ) { - this.removeAttribute( name ); - } - }); - }, - - addClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.addClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( value && typeof value === "string" ) { - var classNames = (value || "").split( rspace ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className ) { - elem.className = value; - - } else { - var className = " " + elem.className + " ", setClass = elem.className; - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { - setClass += " " + classNames[c]; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.removeClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - var classNames = (value || "").split(rspace); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - var className = (" " + elem.className + " ").replace(rclass, " "); - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[c] + " ", " "); - } - elem.className = jQuery.trim( className ); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this); - self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, i = 0, self = jQuery(this), - state = stateVal, - classNames = value.split( rspace ); - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - } else if ( type === "undefined" || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery.data( this, "__className__", this.className ); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " "; - for ( var i = 0, l = this.length; i < l; i++ ) { - if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - if ( value === undefined ) { - var elem = this[0]; - - if ( elem ) { - if ( jQuery.nodeName( elem, "option" ) ) { - return (elem.attributes.value || {}).specified ? elem.value : elem.text; - } - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - } - - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { - return elem.getAttribute("value") === null ? "on" : elem.value; - } - - - // Everything else, we just grab the value - return (elem.value || "").replace(rreturn, ""); - - } - - return undefined; - } - - var isFunction = jQuery.isFunction(value); - - return this.each(function(i) { - var self = jQuery(this), val = value; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call(this, i, self.val()); - } - - // Typecast each time if the value is a Function and the appended - // value is therefore different each time. - if ( typeof val === "number" ) { - val += ""; - } - - if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { - this.checked = jQuery.inArray( self.val(), val ) >= 0; - - } else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(val); - - jQuery( "option", this ).each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - this.selectedIndex = -1; - } - - } else { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function( elem, name, value, pass ) { - // don't set attributes on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery(elem)[name](value); - } - - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - - // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; - - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - - // If applicable, access the attribute via the DOM 0 way - if ( name in elem && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - elem[ name ] = value; - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - - return elem[ name ]; - } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; - } - - // elem is actually elem.style ... set the style - // Using attr for specific style information is now deprecated. Use style instead. - return jQuery.style( elem, name, value ); - } -}); -var rnamespaces = /\.(.*)$/, - fcleanup = function( nm ) { - return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { - return "\\" + ch; - }); - }; - -/* - * A number of helper functions used for managing events. - * Many of the ideas behind this code originated from - * Dean Edwards' addEvent library. - */ -jQuery.event = { - - // Bind an event to an element - // Original by Dean Edwards - add: function( elem, types, handler, data ) { - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // For whatever reason, IE has trouble passing the window object - // around, causing it to be cloned in the process - if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { - elem = window; - } - - var handleObjIn, handleObj; - - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure - var elemData = jQuery.data( elem ); - - // If no elemData is found then we must be trying to bind to one of the - // banned noData elements - if ( !elemData ) { - return; - } - - var events = elemData.events = elemData.events || {}, - eventHandle = elemData.handle, eventHandle; - - if ( !eventHandle ) { - elemData.handle = eventHandle = function() { - // Handle the second event of a trigger and when - // an event is called after a page has unloaded - return typeof jQuery !== "undefined" && !jQuery.event.triggered ? - jQuery.event.handle.apply( eventHandle.elem, arguments ) : - undefined; - }; - } - - // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native events in IE. - eventHandle.elem = elem; - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = types.split(" "); - - var type, i = 0, namespaces; - - while ( (type = types[ i++ ]) ) { - handleObj = handleObjIn ? - jQuery.extend({}, handleObjIn) : - { handler: handler, data: data }; - - // Namespaced event handlers - if ( type.indexOf(".") > -1 ) { - namespaces = type.split("."); - type = namespaces.shift(); - handleObj.namespace = namespaces.slice(0).sort().join("."); - - } else { - namespaces = []; - handleObj.namespace = ""; - } - - handleObj.type = type; - handleObj.guid = handler.guid; - - // Get the current list of functions bound to this event - var handlers = events[ type ], - special = jQuery.event.special[ type ] || {}; - - // Init the event handler queue - if ( !handlers ) { - handlers = events[ type ] = []; - - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add the function to the element's handler list - handlers.push( handleObj ); - - // Keep track of which events have been used, for global triggering - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, pos ) { - // don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - elemData = jQuery.data( elem ), - events = elemData && elemData.events; - - if ( !elemData || !events ) { - return; - } - - // types is actually an event object here - if ( types && types.type ) { - handler = types.handler; - types = types.type; - } - - // Unbind all events for the element - if ( !types || typeof types === "string" && types.charAt(0) === "." ) { - types = types || ""; - - for ( type in events ) { - jQuery.event.remove( elem, type + types ); - } - - return; - } - - // Handle multiple events separated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(" "); - - while ( (type = types[ i++ ]) ) { - origType = type; - handleObj = null; - all = type.indexOf(".") < 0; - namespaces = []; - - if ( !all ) { - // Namespaced event handlers - namespaces = type.split("."); - type = namespaces.shift(); - - namespace = new RegExp("(^|\\.)" + - jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") - } - - eventType = events[ type ]; - - if ( !eventType ) { - continue; - } - - if ( !handler ) { - for ( var j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( all || namespace.test( handleObj.namespace ) ) { - jQuery.event.remove( elem, origType, handleObj.handler, j ); - eventType.splice( j--, 1 ); - } - } - - continue; - } - - special = jQuery.event.special[ type ] || {}; - - for ( var j = pos || 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( handler.guid === handleObj.guid ) { - // remove the given handler for the given type - if ( all || namespace.test( handleObj.namespace ) ) { - if ( pos == null ) { - eventType.splice( j--, 1 ); - } - - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - - if ( pos != null ) { - break; - } - } - } - - // remove generic event handler if no more handlers exist - if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - removeEvent( elem, type, elemData.handle ); - } - - ret = null; - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - var handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - delete elemData.events; - delete elemData.handle; - - if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem ); - } - } - }, - - // bubbling is internal - trigger: function( event, data, elem /*, bubbling */ ) { - // Event object or event type - var type = event.type || event, - bubbling = arguments[3]; - - if ( !bubbling ) { - event = typeof event === "object" ? - // jQuery.Event object - event[expando] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); - - if ( type.indexOf("!") >= 0 ) { - event.type = type = type.slice(0, -1); - event.exclusive = true; - } - - // Handle a global trigger - if ( !elem ) { - // Don't bubble custom events when global (to avoid too much overhead) - event.stopPropagation(); - - // Only trigger if we've ever bound an event for it - if ( jQuery.event.global[ type ] ) { - jQuery.each( jQuery.cache, function() { - if ( this.events && this.events[type] ) { - jQuery.event.trigger( event, data, this.handle.elem ); - } - }); - } - } - - // Handle triggering a single element - - // don't do events on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } - - // Clean up in case it is reused - event.result = undefined; - event.target = elem; - - // Clone the incoming data, if any - data = jQuery.makeArray( data ); - data.unshift( event ); - } - - event.currentTarget = elem; - - // Trigger the event, it is assumed that "handle" is a function - var handle = jQuery.data( elem, "handle" ); - if ( handle ) { - handle.apply( elem, data ); - } - - var parent = elem.parentNode || elem.ownerDocument; - - // Trigger an inline bound script - try { - if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { - event.result = false; - } - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (e) {} - - if ( !event.isPropagationStopped() && parent ) { - jQuery.event.trigger( event, data, parent, true ); - - } else if ( !event.isDefaultPrevented() ) { - var target = event.target, old, - isClick = jQuery.nodeName(target, "a") && type === "click", - special = jQuery.event.special[ type ] || {}; - - if ( (!special._default || special._default.call( elem, event ) === false) && - !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { - - try { - if ( target[ type ] ) { - // Make sure that we don't accidentally re-trigger the onFOO events - old = target[ "on" + type ]; - - if ( old ) { - target[ "on" + type ] = null; - } - - jQuery.event.triggered = true; - target[ type ](); - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (e) {} - - if ( old ) { - target[ "on" + type ] = old; - } - - jQuery.event.triggered = false; - } - } - }, - - handle: function( event ) { - var all, handlers, namespaces, namespace, events; - - event = arguments[0] = jQuery.event.fix( event || window.event ); - event.currentTarget = this; - - // Namespaced event handlers - all = event.type.indexOf(".") < 0 && !event.exclusive; - - if ( !all ) { - namespaces = event.type.split("."); - event.type = namespaces.shift(); - namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - var events = jQuery.data(this, "events"), handlers = events[ event.type ]; - - if ( events && handlers ) { - // Clone the handlers to prevent manipulation - handlers = handlers.slice(0); - - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; - - // Filter the functions by class - if ( all || namespace.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; - - var ret = handleObj.handler.apply( this, arguments ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - } - - return event.result; - }, - - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), - - fix: function( event ) { - if ( event[ expando ] ) { - return event; - } - - // store a copy of the original event object - // and "clone" to set read-only properties - var originalEvent = event; - event = jQuery.Event( originalEvent ); - - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary - if ( !event.target ) { - event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either - } - - // check if target is a textnode (safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; - } - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, body = document.body; - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for key events - if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { - event.which = event.charCode || event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - } - - return event; - }, - - // Deprecated, use jQuery.guid instead - guid: 1E8, - - // Deprecated, use jQuery.proxy instead - proxy: jQuery.proxy, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady, - teardown: jQuery.noop - }, - - live: { - add: function( handleObj ) { - jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); - }, - - remove: function( handleObj ) { - var remove = true, - type = handleObj.origType.replace(rnamespaces, ""); - - jQuery.each( jQuery.data(this, "events").live || [], function() { - if ( type === this.origType.replace(rnamespaces, "") ) { - remove = false; - return false; - } - }); - - if ( remove ) { - jQuery.event.remove( this, handleObj.origType, liveHandler ); - } - } - - }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( this.setInterval ) { - this.onbeforeunload = eventHandle; - } - - return false; - }, - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; - } - } - } - } -}; - -var removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - elem.removeEventListener( type, handle, false ); - } : - function( elem, type, handle ) { - elem.detachEvent( "on" + type, handle ); - }; - -jQuery.Event = function( src ) { - // Allow instantiation without the 'new' keyword - if ( !this.preventDefault ) { - return new jQuery.Event( src ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - // Event type - } else { - this.type = src; - } - - // timeStamp is buggy for some events on Firefox(#3843) - // So we won't rely on the native value - this.timeStamp = now(); - - // Mark it as fixed - this[ expando ] = true; -}; - -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - - // if preventDefault exists run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - } - // otherwise set the returnValue property of the original event to false (IE) - e.returnValue = false; - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - // if stopPropagation exists run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; - -// Checks if an event happened on an element within another element -// Used in jQuery.event.special.mouseenter and mouseleave handlers -var withinElement = function( event ) { - // Check if mouse(over|out) are still within the same parent element - var parent = event.relatedTarget; - - // Firefox sometimes assigns relatedTarget a XUL element - // which we cannot access the parentNode property of - try { - // Traverse up the tree - while ( parent && parent !== this ) { - parent = parent.parentNode; - } - - if ( parent !== this ) { - // set the correct event type - event.type = event.data; - - // handle event if we actually just moused on to a non sub-element - jQuery.event.handle.apply( this, arguments ); - } - - // assuming we've left the element since we most likely mousedover a xul element - } catch(e) { } -}, - -// In case of event delegation, we only need to rename the event.type, -// liveHandler will take care of the rest. -delegate = function( event ) { - event.type = event.data; - jQuery.event.handle.apply( this, arguments ); -}; - -// Create mouseenter and mouseleave events -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - setup: function( data ) { - jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); - }, - teardown: function( data ) { - jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); - } - }; -}); - -// submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function( data, namespaces ) { - if ( this.nodeName.toLowerCase() !== "form" ) { - jQuery.event.add(this, "click.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; - - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - return trigger( "submit", this, arguments ); - } - }); - - jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; - - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - return trigger( "submit", this, arguments ); - } - }); - - } else { - return false; - } - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialSubmit" ); - } - }; - -} - -// change delegation, happens here so we have bind. -if ( !jQuery.support.changeBubbles ) { - - var formElems = /textarea|input|select/i, - - changeFilters, - - getVal = function( elem ) { - var type = elem.type, val = elem.value; - - if ( type === "radio" || type === "checkbox" ) { - val = elem.checked; - - } else if ( type === "select-multiple" ) { - val = elem.selectedIndex > -1 ? - jQuery.map( elem.options, function( elem ) { - return elem.selected; - }).join("-") : - ""; - - } else if ( elem.nodeName.toLowerCase() === "select" ) { - val = elem.selectedIndex; - } - - return val; - }, - - testChange = function testChange( e ) { - var elem = e.target, data, val; - - if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { - return; - } - - data = jQuery.data( elem, "_change_data" ); - val = getVal(elem); - - // the current data will be also retrieved by beforeactivate - if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery.data( elem, "_change_data", val ); - } - - if ( data === undefined || val === data ) { - return; - } - - if ( data != null || val ) { - e.type = "change"; - return jQuery.event.trigger( e, arguments[1], elem ); - } - }; - - jQuery.event.special.change = { - filters: { - focusout: testChange, - - click: function( e ) { - var elem = e.target, type = elem.type; - - if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { - return testChange.call( this, e ); - } - }, - - // Change has to be called before submit - // Keydown will be called before keypress, which is used in submit-event delegation - keydown: function( e ) { - var elem = e.target, type = elem.type; - - if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - return testChange.call( this, e ); - } - }, - - // Beforeactivate happens also before the previous element is blurred - // with this event you can't trigger a change event, but you can store - // information/focus[in] is not needed anymore - beforeactivate: function( e ) { - var elem = e.target; - jQuery.data( elem, "_change_data", getVal(elem) ); - } - }, - - setup: function( data, namespaces ) { - if ( this.type === "file" ) { - return false; - } - - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); - } - - return formElems.test( this.nodeName ); - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialChange" ); - - return formElems.test( this.nodeName ); - } - }; - - changeFilters = jQuery.event.special.change.filters; -} - -function trigger( type, elem, args ) { - args[0].type = type; - return jQuery.event.handle.apply( elem, args ); -} - -// Create "bubbling" focus and blur events -if ( document.addEventListener ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - jQuery.event.special[ fix ] = { - setup: function() { - this.addEventListener( orig, handler, true ); - }, - teardown: function() { - this.removeEventListener( orig, handler, true ); - } - }; - - function handler( e ) { - e = jQuery.event.fix( e ); - e.type = fix; - return jQuery.event.handle.call( this, e ); - } - }); -} - -jQuery.each(["bind", "one"], function( i, name ) { - jQuery.fn[ name ] = function( type, data, fn ) { - // Handle object literals - if ( typeof type === "object" ) { - for ( var key in type ) { - this[ name ](key, data, type[key], fn); - } - return this; - } - - if ( jQuery.isFunction( data ) ) { - fn = data; - data = undefined; - } - - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; - - if ( type === "unload" && name !== "one" ) { - this.one( type, data, fn ); - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.add( this[i], type, handler, data ); - } - } - - return this; - }; -}); - -jQuery.fn.extend({ - unbind: function( type, fn ) { - // Handle object literals - if ( typeof type === "object" && !type.preventDefault ) { - for ( var key in type ) { - this.unbind(key, type[key]); - } - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.remove( this[i], type, fn ); - } - } - - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.live( types, data, fn, selector ); - }, - - undelegate: function( selector, types, fn ) { - if ( arguments.length === 0 ) { - return this.unbind( "live" ); - - } else { - return this.die( types, null, fn, selector ); - } - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - - triggerHandler: function( type, data ) { - if ( this[0] ) { - var event = jQuery.Event( type ); - event.preventDefault(); - event.stopPropagation(); - jQuery.event.trigger( event, data, this[0] ); - return event.result; - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, i = 1; - - // link all the functions, so any of them can unbind this click handler - while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); - } - - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -var liveMap = { - focus: "focusin", - blur: "focusout", - mouseenter: "mouseover", - mouseleave: "mouseout" -}; - -jQuery.each(["live", "die"], function( i, name ) { - jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { - var type, i = 0, match, namespaces, preType, - selector = origSelector || this.selector, - context = origSelector ? this : jQuery( this.context ); - - if ( jQuery.isFunction( data ) ) { - fn = data; - data = undefined; - } - - types = (types || "").split(" "); - - while ( (type = types[ i++ ]) != null ) { - match = rnamespaces.exec( type ); - namespaces = ""; - - if ( match ) { - namespaces = match[0]; - type = type.replace( rnamespaces, "" ); - } - - if ( type === "hover" ) { - types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); - continue; - } - - preType = type; - - if ( type === "focus" || type === "blur" ) { - types.push( liveMap[ type ] + namespaces ); - type = type + namespaces; - - } else { - type = (liveMap[ type ] || type) + namespaces; - } - - if ( name === "live" ) { - // bind live handler - context.each(function(){ - jQuery.event.add( this, liveConvert( type, selector ), - { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); - }); - - } else { - // unbind live handler - context.unbind( liveConvert( type, selector ), fn ); - } - } - - return this; - } -}); - -function liveHandler( event ) { - var stop, elems = [], selectors = [], args = arguments, - related, match, handleObj, elem, j, i, l, data, - events = jQuery.data( this, "events" ); - - // Make sure we avoid non-left-click bubbling in Firefox (#3861) - if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { - return; - } - - event.liveFired = this; - - var live = events.live.slice(0); - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { - selectors.push( handleObj.selector ); - - } else { - live.splice( j--, 1 ); - } - } - - match = jQuery( event.target ).closest( selectors, event.currentTarget ); - - for ( i = 0, l = match.length; i < l; i++ ) { - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( match[i].selector === handleObj.selector ) { - elem = match[i].elem; - related = null; - - // Those two events require additional checking - if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { - related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; - } - - if ( !related || related !== elem ) { - elems.push({ elem: elem, handleObj: handleObj }); - } - } - } - } - - for ( i = 0, l = elems.length; i < l; i++ ) { - match = elems[i]; - event.currentTarget = match.elem; - event.data = match.handleObj.data; - event.handleObj = match.handleObj; - - if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { - stop = false; - break; - } - } - - return stop; -} - -function liveConvert( type, selector ) { - return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); -} - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( fn ) { - return fn ? this.bind( name, fn ) : this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } -}); - -// Prevent memory leaks in IE -// Window isn't included so as not to unbind existing unload events -// More info: -// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ -if ( window.attachEvent && !window.addEventListener ) { - window.attachEvent("onunload", function() { - for ( var id in jQuery.cache ) { - if ( jQuery.cache[ id ].handle ) { - // Try/Catch is to handle iframes being unloaded, see #4280 - try { - jQuery.event.remove( jQuery.cache[ id ].handle.elem ); - } catch(e) {} - } - } - }); -} -/*! - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function(){ - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function(selector, context, results, seed) { - results = results || []; - var origContext = context = context || document; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set ); - } - } - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } - - if ( context ) { - var ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } - - while ( parts.length ) { - var cur = parts.pop(), pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } - - return results; -}; - -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; - -Sizzle.find = function(expr, context, isXML){ - var set, match; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice(1,1); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context, isXML ); - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = context.getElementsByTagName("*"); - } - - return {set: set, expr: expr}; -}; - -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var filter = Expr.filter[ type ], found, item, left = match[1]; - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - leftMatch: {}, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - ">": function(checkSet, part){ - var isPartStr = typeof part === "string"; - - if ( isPartStr && !/\W/.test(part) ) { - part = part.toLowerCase(); - - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( typeof part === "string" && !/\W/.test(part) ) { - var nodeCheck = part = part.toLowerCase(); - checkFn = dirNodeCheck; - } - - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( typeof part === "string" && !/\W/.test(part) ) { - var nodeCheck = part = part.toLowerCase(); - checkFn = dirNodeCheck; - } - - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - find: { - ID: function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? [m] : []; - } - }, - NAME: function(match, context){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); - } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - return match[1].toLowerCase(); - }, - CHILD: function(match){ - if ( match[1] === "nth" ) { - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); - } - return false; - } - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - // Accessing this property makes selected-by-default - // options in Safari work properly - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!Sizzle( match[3], elem ).length; - }, - header: function(elem){ - return /h\d/i.test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; - }, - input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); - } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; - }, - gt: function(elem, i, match){ - return i > match[3] - 0; - }, - nth: function(elem, i, match){ - return match[3] - 0 === i; - }, - eq: function(elem, i, match){ - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; - - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { - return false; - } - } - - return true; - } else { - Sizzle.error( "Syntax error, unrecognized expression: " + name ); - } - }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - if ( type === "first" ) { - return true; - } - node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - return true; - case 'nth': - var first = match[2], last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - if ( first === 0 ) { - return diff === 0; - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ - return "\\" + (num - 0 + 1); - })); -} - -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - } else { - for ( var i = 0; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - if ( a == b ) { - hasDuplicate = true; - } - return a.compareDocumentPosition ? -1 : 1; - } - - var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( "sourceIndex" in document.documentElement ) { - sortOrder = function( a, b ) { - if ( !a.sourceIndex || !b.sourceIndex ) { - if ( a == b ) { - hasDuplicate = true; - } - return a.sourceIndex ? -1 : 1; - } - - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - if ( !a.ownerDocument || !b.ownerDocument ) { - if ( a == b ) { - hasDuplicate = true; - } - return a.ownerDocument ? -1 : 1; - } - - var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); - aRange.setStart(a, 0); - aRange.setEnd(a, 0); - bRange.setStart(b, 0); - bRange.setEnd(b, 0); - var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} - -// Utility function for retreiving the text value of an array of DOM nodes -function getText( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -} - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date).getTime(); - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; - } - }; - - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - root = form = null; // release memory in IE -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); - }; - } - - div = null; // release memory in IE -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

        "; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - div = null; // release memory in IE - })(); -} - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "
        "; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - div = null; // release memory in IE -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -var contains = document.compareDocumentPosition ? function(a, b){ - return !!(a.compareDocumentPosition(b) & 16); -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; - -var isXML = function(elem){ - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = getText; -jQuery.isXMLDoc = isXML; -jQuery.contains = contains; - -return; - -window.Sizzle = Sizzle; - -})(); -var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - slice = Array.prototype.slice; - -// Implement the identical functionality for filter and not -var winnow = function( elements, qualifier, keep ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return (elem === qualifier) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem, i ) { - return (jQuery.inArray( elem, qualifier ) >= 0) === keep; - }); -}; - -jQuery.fn.extend({ - find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), length = 0; - - for ( var i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( var n = length; n < ret.length; n++ ) { - for ( var r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function( target ) { - var targets = jQuery( target ); - return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); - }, - - is: function( selector ) { - return !!selector && jQuery.filter( selector, this ).length > 0; - }, - - closest: function( selectors, context ) { - if ( jQuery.isArray( selectors ) ) { - var ret = [], cur = this[0], match, matches = {}, selector; - - if ( cur && selectors.length ) { - for ( var i = 0, l = selectors.length; i < l; i++ ) { - selector = selectors[i]; - - if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? - jQuery( selector, context || this.context ) : - selector; - } - } - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( selector in matches ) { - match = matches[selector]; - - if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { - ret.push({ selector: selector, elem: cur }); - delete matches[selector]; - } - } - cur = cur.parentNode; - } - } - - return ret; - } - - var pos = jQuery.expr.match.POS.test( selectors ) ? - jQuery( selectors, context || this.context ) : null; - - return this.map(function( i, cur ) { - while ( cur && cur.ownerDocument && cur !== context ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { - return cur; - } - cur = cur.parentNode; - } - return null; - }); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - if ( !elem || typeof elem === "string" ) { - return jQuery.inArray( this[0], - // If it receives a string, the selector is used - // If it receives nothing, the siblings are used - elem ? jQuery( elem ) : this.parent().children() ); - } - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context || this.context ) : - jQuery.makeArray( selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); - }, - - andSelf: function() { - return this.add( this.prevObject ); - } -}); - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); - }, - prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 ? jQuery.unique( ret ) : ret; - - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret, name, slice.call(arguments).join(",") ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], cur = elem[dir]; - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); -var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, - rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, - rtagName = /<([\w:]+)/, - rtbody = /"; - }, - wrapMap = { - option: [ 1, "" ], - legend: [ 1, "
        ", "
        " ], - thead: [ 1, "", "
        " ], - tr: [ 2, "", "
        " ], - td: [ 3, "", "
        " ], - col: [ 2, "", "
        " ], - area: [ 1, "", "" ], - _default: [ 0, "", "" ] - }; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize and -
        -

        Hello

        -
        diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/ajax_url2.html --- a/web/test/jstests/ajax_url2.html Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -
        -
        - - -
        -

        Hello

        -
        diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/ajaxresult.json --- a/web/test/jstests/ajaxresult.json Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -['foo', 'bar'] diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/test_ajax.html --- a/web/test/jstests/test_ajax.html Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - -
        -

        cubicweb.ajax.js functions tests

        -

        -
          - - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/test_ajax.js --- a/web/test/jstests/test_ajax.js Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,244 +0,0 @@ -$(document).ready(function() { - - module("ajax", { - setup: function() { - this.scriptsLength = $('head script[src]').length-1; - this.cssLength = $('head link[rel=stylesheet]').length-1; - // re-initialize cw loaded cache so that each tests run in a - // clean environment, have a lookt at _loadAjaxHtmlHead implementation - // in cubicweb.ajax.js for more information. - cw.loaded_src = []; - cw.loaded_href = []; - }, - teardown: function() { - $('head script[src]:gt(' + this.scriptsLength + ')').remove(); - $('head link[rel=stylesheet]:gt(' + this.cssLength + ')').remove(); - } - }); - - function jsSources() { - return $.map($('head script[src]'), function(script) { - return script.getAttribute('src'); - }); - } - - test('test simple h1 inclusion (ajax_url0.html)', function() { - expect(3); - equals(jQuery('#main').children().length, 0); - stop(); - jQuery('#main').loadxhtml('/../ajax_url0.html', { - callback: function() { - equals(jQuery('#main').children().length, 1); - equals(jQuery('#main h1').html(), 'Hello'); - start(); - } - }); - }); - - test('test simple html head inclusion (ajax_url1.html)', function() { - expect(6); - var scriptsIncluded = jsSources(); - equals(jQuery.inArray('http://foo.js', scriptsIncluded), - 1); - stop(); - jQuery('#main').loadxhtml('/../ajax_url1.html', { - callback: function() { - var origLength = scriptsIncluded.length; - scriptsIncluded = jsSources(); - // check that foo.js has been *appended* to - equals(scriptsIncluded.length, origLength + 1); - equals(scriptsIncluded[origLength].indexOf('http://foo.js'), 0); - // check that
          has been removed - equals(jQuery('#main').children().length, 1); - equals(jQuery('div.ajaxHtmlHead').length, 0); - equals(jQuery('#main h1').html(), 'Hello'); - start(); - } - }); - }); - - test('test addCallback', function() { - expect(3); - equals(jQuery('#main').children().length, 0); - stop(); - var d = jQuery('#main').loadxhtml('/../ajax_url0.html'); - d.addCallback(function() { - equals(jQuery('#main').children().length, 1); - equals(jQuery('#main h1').html(), 'Hello'); - start(); - }); - }); - - test('test callback after synchronous request', function() { - expect(1); - var deferred = new Deferred(); - var result = jQuery.ajax({ - url: './ajax_url0.html', - async: false, - beforeSend: function(xhr) { - deferred._req = xhr; - }, - success: function(data, status) { - deferred.success(data); - } - }); - stop(); - deferred.addCallback(function() { - // add an assertion to ensure the callback is executed - ok(true, "callback is executed"); - start(); - }); - }); - - test('test addCallback with parameters', function() { - expect(3); - equals(jQuery('#main').children().length, 0); - stop(); - var d = jQuery('#main').loadxhtml('/../ajax_url0.html'); - d.addCallback(function(data, req, arg1, arg2) { - equals(arg1, 'Hello'); - equals(arg2, 'world'); - start(); - }, - 'Hello', 'world'); - }); - - test('test callback after synchronous request with parameters', function() { - var deferred = new Deferred(); - var result = jQuery.ajax({ - url: '/../ajax_url0.html', - async: false, - beforeSend: function(xhr) { - deferred._req = xhr; - }, - success: function(data, status) { - deferred.success(data); - } - }); - deferred.addCallback(function(data, req, arg1, arg2) { - // add an assertion to ensure the callback is executed - ok(true, "callback is executed"); - equals(arg1, 'Hello'); - equals(arg2, 'world'); - }, - 'Hello', 'world'); - }); - - test('test addErrback', function() { - expect(1); - stop(); - var d = jQuery('#main').loadxhtml('/../ajax_url0.html'); - d.addCallback(function() { - // throw an exception to start errback chain - throw new Error(); - }); - d.addErrback(function() { - ok(true, "errback is executed"); - start(); - }); - }); - - test('test callback / errback execution order', function() { - expect(4); - var counter = 0; - stop(); - var d = jQuery('#main').loadxhtml('/../ajax_url0.html', { - callback: function() { - equals(++counter, 1); // should be executed first - start(); - } - }); - d.addCallback(function() { - equals(++counter, 2); // should be executed and break callback chain - throw new Error(); - }); - d.addCallback(function() { - // should not be executed since second callback raised an error - ok(false, "callback is executed"); - }); - d.addErrback(function() { - // should be executed after the second callback - equals(++counter, 3); - }); - d.addErrback(function() { - // should be executed after the first errback - equals(++counter, 4); - }); - }); - - test('test already included resources are ignored (ajax_url1.html)', function() { - expect(10); - var scriptsIncluded = jsSources(); - // NOTE: - equals(jQuery.inArray('http://foo.js', scriptsIncluded), -1); - equals(jQuery('head link').length, 1); - /* use endswith because in pytest context we have an absolute path */ - ok(jQuery('head link').attr('href').endswith('/qunit.css')); - stop(); - jQuery('#main').loadxhtml('/../ajax_url1.html', { - callback: function() { - var origLength = scriptsIncluded.length; - scriptsIncluded = jsSources(); - try { - // check that foo.js has been inserted in - equals(scriptsIncluded.length, origLength + 1); - equals(scriptsIncluded[origLength].indexOf('http://foo.js'), 0); - // check that
          has been removed - equals(jQuery('#main').children().length, 1); - equals(jQuery('div.ajaxHtmlHead').length, 0); - equals(jQuery('#main h1').html(), 'Hello'); - // qunit.css is not added twice - equals(jQuery('head link').length, 1); - /* use endswith because in pytest context we have an absolute path */ - ok(jQuery('head link').attr('href').endswith('/qunit.css')); - } finally { - start(); - } - } - }); - }); - - test('test synchronous request loadRemote', function() { - var res = loadRemote('/../ajaxresult.json', {}, - 'GET', true); - same(res, ['foo', 'bar']); - }); - - test('test event on CubicWeb', function() { - expect(1); - stop(); - var events = null; - jQuery(CubicWeb).bind('server-response', function() { - // check that server-response event on CubicWeb is triggered - events = 'CubicWeb'; - }); - jQuery('#main').loadxhtml('/../ajax_url0.html', { - callback: function() { - equals(events, 'CubicWeb'); - start(); - } - }); - }); - - test('test event on node', function() { - expect(3); - stop(); - var nodes = []; - jQuery('#main').bind('server-response', function() { - nodes.push('node'); - }); - jQuery(CubicWeb).bind('server-response', function() { - nodes.push('CubicWeb'); - }); - jQuery('#main').loadxhtml('/../ajax_url0.html', { - callback: function() { - equals(nodes.length, 2); - // check that server-response event on CubicWeb is triggered - // only once and event server-response on node is triggered - equals(nodes[0], 'CubicWeb'); - equals(nodes[1], 'node'); - start(); - } - }); - }); -}); - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/test_htmlhelpers.html --- a/web/test/jstests/test_htmlhelpers.html Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ - - - - - - - - - - - - - -
          -
          -

          cubicweb.htmlhelpers.js functions tests

          -

          -

          -
            - - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/test_htmlhelpers.js --- a/web/test/jstests/test_htmlhelpers.js Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -$(document).ready(function() { - - module("module2", { - setup: function() { - $('#main').append(''); - } - }); - - test("test first selected", function() { - $('#theselect').append('' + - '' + - '' + - ''); - var selected = firstSelected(document.getElementById("theselect")); - equals(selected.value, 'bar'); - }); - - test("test first selected 2", function() { - $('#theselect').append('' + - '' + - '' + - ''); - var selected = firstSelected(document.getElementById("theselect")); - equals(selected, null); - }); - - module("visibilty"); - test('toggleVisibility', function() { - $('#main').append('
            '); - toggleVisibility('foo'); - ok($('#foo').hasClass('hidden'), 'check hidden class is set'); - }); - -}); - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/test_utils.html --- a/web/test/jstests/test_utils.html Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - -
            -
            -

            cw.utils functions tests

            -

            -

            -
              - - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/test_utils.js --- a/web/test/jstests/test_utils.js Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -$(document).ready(function() { - - module("datetime"); - - test("test full datetime", function() { - equals(cw.utils.toISOTimestamp(new Date(1986, 3, 18, 10, 30, 0, 0)), - '1986-04-18 10:30:00'); - }); - - test("test only date", function() { - equals(cw.utils.toISOTimestamp(new Date(1986, 3, 18)), '1986-04-18 00:00:00'); - }); - - test("test null", function() { - equals(cw.utils.toISOTimestamp(null), null); - }); - - module("parsing"); - test("test basic number parsing", function() { - var d = strptime('2008/08/08', '%Y/%m/%d'); - same(datetuple(d), [2008, 8, 8, 0, 0]); - d = strptime('2008/8/8', '%Y/%m/%d'); - same(datetuple(d), [2008, 8, 8, 0, 0]); - d = strptime('8/8/8', '%Y/%m/%d'); - same(datetuple(d), [8, 8, 8, 0, 0]); - d = strptime('0/8/8', '%Y/%m/%d'); - same(datetuple(d), [0, 8, 8, 0, 0]); - d = strptime('-10/8/8', '%Y/%m/%d'); - same(datetuple(d), [-10, 8, 8, 0, 0]); - d = strptime('-35000', '%Y'); - same(datetuple(d), [-35000, 1, 1, 0, 0]); - }); - - test("test custom format parsing", function() { - var d = strptime('2008-08-08', '%Y-%m-%d'); - same(datetuple(d), [2008, 8, 8, 0, 0]); - d = strptime('2008 - ! 08: 08', '%Y - ! %m: %d'); - same(datetuple(d), [2008, 8, 8, 0, 0]); - d = strptime('2008-08-08 12:14', '%Y-%m-%d %H:%M'); - same(datetuple(d), [2008, 8, 8, 12, 14]); - d = strptime('2008-08-08 1:14', '%Y-%m-%d %H:%M'); - same(datetuple(d), [2008, 8, 8, 1, 14]); - d = strptime('2008-08-08 01:14', '%Y-%m-%d %H:%M'); - same(datetuple(d), [2008, 8, 8, 1, 14]); - }); - - module("sliceList"); - test("test slicelist", function() { - var list = ['a', 'b', 'c', 'd', 'e', 'f']; - same(sliceList(list, 2), ['c', 'd', 'e', 'f']); - same(sliceList(list, 2, -2), ['c', 'd']); - same(sliceList(list, -3), ['d', 'e', 'f']); - same(sliceList(list, 0, -2), ['a', 'b', 'c', 'd']); - same(sliceList(list), list); - }); - - module("formContents", { - setup: function() { - $('#main').append('
              '); - } - }); - // XXX test fckeditor - test("test formContents", function() { - $('#test-form').append(''); - $('#test-form').append(' '); - $('#test-form').append(''); - $('#test-form').append(''); - $('#test-form').append(''); - $('#test-form').append(''); - $('#test-form').append(''); - $('#theselect').append('' + - ''); - //Append an unchecked radio input : should not be in formContents list - $('#test-form').append(''); - $('#test-form').append(''); - same(formContents($('#test-form')[0]), [ - ['input-text', 'mytextarea', 'choice', 'check', 'theselect'], - ['toto', 'Hello World!', 'no', 'no', 'foo'] - ]); - }); -}); - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/jstests/utils.js --- a/web/test/jstests/utils.js Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -function datetuple(d) { - return [d.getFullYear(), d.getMonth()+1, d.getDate(), - d.getHours(), d.getMinutes()]; -} - -function pprint(obj) { - print('{'); - for(k in obj) { - print(' ' + k + ' = ' + obj[k]); - } - print('}'); -} - -function arrayrepr(array) { - return '[' + array.join(', ') + ']'; -} - -function assertArrayEquals(array1, array2) { - if (array1.length != array2.length) { - throw new crosscheck.AssertionFailure(array1.join(', ') + ' != ' + array2.join(', ')); - } - for (var i=0; i. """ -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 dc319ece0bd6 -r c2b29631c1a3 web/test/unittest_request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/test/unittest_request.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/test/unittest_urlpublisher.py --- a/web/test/unittest_urlpublisher.py Mon May 16 16:24:00 2011 +0200 +++ b/web/test/unittest_urlpublisher.py Mon May 16 16:25:33 2011 +0200 @@ -33,8 +33,8 @@ """test suite for QSPreProcessor""" def setup_database(self): - self.create_user(u'ÿsaÿe') req = self.request() + self.create_user(req, u'ÿsaÿe') b = req.create_entity('BlogEntry', title=u'hell\'o', content=u'blabla') c = req.create_entity('Tag', name=u'yo') # take care: Tag's name normalized to lower case self.execute('SET C tags B WHERE C eid %(c)s, B eid %(b)s', {'c':c.eid, 'b':b.eid}) diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/unittest_urlrewrite.py --- a/web/test/unittest_urlrewrite.py Mon May 16 16:24:00 2011 +0200 +++ b/web/test/unittest_urlrewrite.py Mon May 16 16:25:33 2011 +0200 @@ -103,9 +103,10 @@ class RgxActionRewriteTC(CubicWebTC): def setup_database(self): - self.p1 = self.create_user(u'user1') + req = self.request() + self.p1 = self.create_user(req, u'user1') self.p1.set_attributes(firstname=u'joe', surname=u'Dalton') - self.p2 = self.create_user(u'user2') + self.p2 = self.create_user(req, u'user2') self.p2.set_attributes(firstname=u'jack', surname=u'Dalton') def test_rgx_action_with_transforms(self): diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/unittest_views_basecontrollers.py --- a/web/test/unittest_views_basecontrollers.py Mon May 16 16:24:00 2011 +0200 +++ b/web/test/unittest_views_basecontrollers.py Mon May 16 16:25:33 2011 +0200 @@ -93,9 +93,9 @@ self.assertEqual([g.eid for g in e.in_group], groupeids) def test_user_can_change_its_password(self): - user = self.create_user('user') + req = self.request() + user = self.create_user(req, 'user') cnx = self.login('user') - req = self.request() eid = u(user.eid) req.form = { 'eid': eid, '__maineid' : eid, @@ -160,8 +160,8 @@ self.assertEqual(email.address, 'dima@logilab.fr') def test_edit_multiple_linked(self): - peid = u(self.create_user('adim').eid) req = self.request() + peid = u(self.create_user(req, 'adim').eid) req.form = {'eid': [peid, 'Y'], '__maineid': peid, '__type:'+peid: u'CWUser', @@ -450,7 +450,8 @@ def test_nonregr_rollback_on_validation_error(self): - p = self.create_user("doe") + req = self.request() + p = self.create_user(req, "doe") # do not try to skip 'primary_email' for this test old_skips = p.__class__.skip_copy_for p.__class__.skip_copy_for = () @@ -497,7 +498,7 @@ class ReportBugControllerTC(CubicWebTC): - def test_usable_by_guets(self): + def test_usable_by_guest(self): self.login('anon') self.assertRaises(NoSelectableObject, self.vreg['controllers'].select, 'reportbug', self.request()) @@ -506,7 +507,7 @@ class SendMailControllerTC(CubicWebTC): - def test_not_usable_by_guets(self): + def test_not_usable_by_guest(self): self.assertRaises(NoSelectableObject, self.vreg['controllers'].select, 'sendmail', self.request()) self.vreg['controllers'].select('sendmail', @@ -529,7 +530,7 @@ req = self.request() self.pytag = req.create_entity('Tag', name=u'python') self.cubicwebtag = req.create_entity('Tag', name=u'cubicweb') - self.john = self.create_user(u'John') + self.john = self.create_user(req, u'John') ## tests ################################################################## diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/unittest_views_baseviews.py --- a/web/test/unittest_views_baseviews.py Mon May 16 16:24:00 2011 +0200 +++ b/web/test/unittest_views_baseviews.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Mon May 16 16:24:00 2011 +0200 +++ b/web/test/unittest_viewselector.py Mon May 16 16:25:33 2011 +0200 @@ -180,7 +180,7 @@ self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset2) def test_propertiesform_jdoe(self): - self.create_user('jdoe') + self.create_user(self.request(), 'jdoe') self.login('jdoe') req1 = self.request() req2 = self.request() diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/windmill/test_connexion.py --- a/web/test/windmill/test_connexion.py Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -from cubicweb.devtools import DEFAULT_SOURCES -LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].values() - -# Generated by the windmill services transformer -from windmill.authoring import WindmillTestClient - -def test_connect(): - client = WindmillTestClient(__name__) - - client.open(url=u'/') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertJS(js=u"$('#loginForm').is(':visible')") - client.type(text=LOGIN, id=u'__login') - client.type(text=PASSWORD, id=u'__password') - - client.execJS(js=u"$('#loginForm').submit()") - client.waits.forPageLoad(timeout=u'20000') - client.waits.sleep(milliseconds=u'5000') - client.asserts.assertJS(js=u'$(\'.message\').text() == "welcome %s !"' % LOGIN) - client.open(url=u'/logout') - client.waits.forPageLoad(timeout=u'20000') - client.open(url=u'/') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertJS(js=u"$('#loginForm').is(':visible')") - - -def test_wrong_connect(): - client = WindmillTestClient(__name__) - - client.open(url=u'/') - # XXX windmill wants to use its proxy internally on 403 :-( - #client.asserts.assertJS(js=u"$('#loginForm').is(':visible')") - #client.type(text=LOGIN, id=u'__login') - #client.type(text=u'novalidpassword', id=u'__password') - #client.click(value=u'log in') - client.open(url=u'/?__login=user&__password=nopassword') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertTextIn(validator=u'authentication failure', id=u'loginBox') - client.open(url=u'/') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertJS(js=u"$('#loginForm').is(':visible')") diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/windmill/test_creation.py --- a/web/test/windmill/test_creation.py Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -from cubicweb.devtools import DEFAULT_SOURCES -LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].values() - -# Generated by the windmill services transformer -from windmill.authoring import WindmillTestClient - -def test_creation(): - client = WindmillTestClient(__name__) - - client.open(url=u'/') - client.waits.forPageLoad(timeout=u'8000') - client.type(text=LOGIN, id=u'__login') - client.type(text=PASSWORD, id=u'__password') - client.click(value=u'log in') - client.waits.forPageLoad(timeout=u'20000') - - # pre-condition - client.open(url=u'/cwuser/myuser') - client.asserts.assertJS(js=u'$(\'#contentmain h1\').text() == "this resource does not exist"') - client.open(url=u'/?rql=Any U WHERE U is CWUser, U login "myuser"') - client.asserts.assertJS(js=u'$(\'.searchMessage strong\').text() == "No result matching query"') - - client.open(url=u'/manage') - client.open(url=u'/add/CWUser') - client.type(text=u'myuser', id=u'login-subject:A') - client.type(text=u'myuser', id=u'upassword-subject:A') - client.type(text=u'myuser', name=u'upassword-subject-confirm:A') - client.type(text=u'myuser', id=u'firstname-subject:A') - client.select(option=u'managers', id=u'from_in_group-subject:A') - client.click(id=u'cwinoutadd') - client.waits.forPageLoad(timeout=u'20000') - client.click(id=u'adduse_email:Alink') - client.waits.forPageLoad(timeout=u'20000') - client.type(text=u'myuser@logilab.fr', id=u'address-subject:B') - client.waits.forPageLoad(timeout=u'20000') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.waits.sleep(milliseconds=u'5000') - client.asserts.assertJS(js=u'$(\'.message\').text() == "entity created"') - client.open(url=u'/?rql=Any U WHERE U is CWUser, U login "myuser"') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertJS(js=u'$(\'#contentmain h1\').text() == "myuser"') - client.waits.forPageLoad(timeout=u'8000') - client.open(url=u'/cwuser/myuser?vid=sameetypelist') - client.waits.forPageLoad(timeout=u'8000') - client.asserts.assertJS(js=u'$(\'#contentmain a\').text() == "myuser"') - client.open(url=u'/cwuser/myuser?vid=text') - client.waits.forPageLoad(timeout=u'8000') - client.asserts.assertJS(js=u'$(\'#contentmain\').text() == "\\nmyuser"') - client.open(url=u'/cwuser/myuser?vid=deleteconf') - client.waits.forElement(timeout=u'8000', value=u'button_delete') - client.click(value=u'button_delete') - client.waits.forPageLoad(timeout=u'8000') - client.open(url=u'/cwuser/myuser') - client.asserts.assertJS(js=u'$(\'#contentmain h1\').text() == "this resource does not exist"') - client.open(url=u'/?rql=Any U WHERE U is CWUser, U login "myuser"') - client.asserts.assertJS(js=u'$(\'.searchMessage strong\').text() == "No result matching query"') - diff -r dc319ece0bd6 -r c2b29631c1a3 web/test/windmill/test_edit_relation.py --- a/web/test/windmill/test_edit_relation.py Mon May 16 16:24:00 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -from cubicweb.devtools import DEFAULT_SOURCES -LOGIN, PASSWORD = DEFAULT_SOURCES['admin'].values() - -# Generated by the windmill services transformer -from windmill.authoring import WindmillTestClient - - -def test_edit_relation(): - client = WindmillTestClient(__name__) - - client.open(url=u'/logout') - client.open(url=u'/') - client.asserts.assertJS(js=u"$('#loginForm').is(':visible')") - client.type(text=LOGIN, id=u'__login') - client.type(text=PASSWORD, id=u'__password') - client.execJS(js=u"$('#loginForm').submit()") - client.waits.forPageLoad(timeout=u'20000') - client.open(url=u'/add/Folder') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(timeout=u'8000', id=u'name-subject:A') - client.click(id=u'name-subject:A') - client.type(text=u'folder1', id=u'name-subject:A') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(link=u'add add Folder filed_under Folder object', timeout=u'8000') - client.click(link=u'add add Folder filed_under Folder object') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(timeout=u'8000', id=u'name-subject:A') - client.click(id=u'name-subject:A') - client.type(text=u'subfolder1', id=u'name-subject:A') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(link=u'more actions', timeout=u'8000') - client.click(link=u'more actions') - client.click(link=u'copy') - client.waits.forPageLoad(timeout=u'20000') - client.type(text=u'folder2', id=u'name-subject:A') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(link=u'modify', timeout=u'8000') - client.click(link=u'modify') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(timeout=u'8000', id=u'footer') - client.click(link=u'x') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(link=u'add add Folder filed_under Folder object', timeout=u'8000') - client.click(link=u'add add Folder filed_under Folder object') - client.waits.forPageLoad(timeout=u'20000') - client.type(text=u'subfolder2', id=u'name-subject:A') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(link=u'subfolder2', timeout=u'8000') - client.click(link=u'subfolder2') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(link=u'modify', timeout=u'8000') - client.click(link=u'modify') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(timeout=u'8000', id=u'footer') - client.click(link=u'x') - client.select(xpath=u'//select', index=u'1') - #client.execJQuery(jquery=u'("select").trigger(\'change\')') # BUGGY freeze UI.. - client.execJS(js=u'$("select").trigger(\'change\')') - client.waits.sleep(milliseconds=u'2000') - client.select(jquery=u'(\'select:contains("Search")\')[0]', option=u'Search for folder') - client.waits.forPageLoad(timeout=u'20000') - client.click(link=u'folder1') - client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(timeout=u'8000', value=u'button_ok') - client.click(value=u'button_ok') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertText(xpath=u'//h1', validator=u'subfolder2') - client.waits.forElement(link=u'folder_plural', timeout=u'8000') - client.click(link=u'folder_plural') - client.waits.forPageLoad(timeout=u'20000') - client.asserts.assertText(jquery=u"('#contentmain div a')[0]", validator=u'folder1') - client.asserts.assertText(jquery=u"('#contentmain div a')[1]", validator=u'folder2') - client.asserts.assertText(jquery=u"('#contentmain div a')[2]", validator=u'subfolder1') - client.asserts.assertText(jquery=u"('#contentmain div a')[3]", validator=u'subfolder2') - client.click(link=u'subfolder2') - client.click(link=u'modify') - client.click(link=u'folder1') diff -r dc319ece0bd6 -r c2b29631c1a3 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/basecontrollers.py Mon May 16 16:25:33 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. @@ -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 dc319ece0bd6 -r c2b29631c1a3 web/views/baseviews.py --- a/web/views/baseviews.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/baseviews.py Mon May 16 16:25:33 2011 +0200 @@ -201,8 +201,15 @@ self.w(u'%s' % self._cw.format_date(entity.creation_date)) if entity.creator: - self.w(u' %s ' % _('by')) + if entity.creation_date: + self.w(u' %s ' % _('by')) + else: + self.w(u' %s ' % _('created_by')) self.w(u'%s' % entity.creator.name()) + meta = entity.cw_metainformation() + if meta['source']['uri'] != 'system': + self.w(u' (%s' % _('cw_source')) + self.w(u' %s)' % meta['source']['uri']) self.w(u'
              ') diff -r dc319ece0bd6 -r c2b29631c1a3 web/views/cwsources.py --- a/web/views/cwsources.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/cwsources.py Mon May 16 16:25:33 2011 +0200 @@ -126,6 +126,8 @@ else: # CWAttribute/CWRelation self.srelations.setdefault(cwerschema.rtype.name, []).append( (cwerschema.stype.name, cwerschema.otype.name) ) + self.sentities.add(cwerschema.stype.name) + self.sentities.add(cwerschema.otype.name) def check(self): self.init() @@ -154,14 +156,15 @@ warning(_('relation %(rtype)s with %(etype)s as %(role)s is ' 'supported but no target type supported') % {'rtype': rschema, 'role': role, 'etype': etype}) - for rtype in self.srelations: - rschema = self.schema[rtype] - for subj, obj in rschema.rdefs: - if subj in self.sentities and obj in self.sentities: - break - else: - error(_('relation %s is supported but none if its definitions ' - 'matches supported entities') % rtype) + for rtype, rdefs in self.srelations.iteritems(): + if rdefs is None: + rschema = self.schema[rtype] + for subj, obj in rschema.rdefs: + if subj in self.sentities and obj in self.sentities: + break + else: + error(_('relation %s is supported but none of its definitions ' + 'matches supported entities') % rtype) self.custom_check() def custom_check(self): diff -r dc319ece0bd6 -r c2b29631c1a3 web/views/cwuser.py --- a/web/views/cwuser.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/cwuser.py Mon May 16 16:25:33 2011 +0200 @@ -24,6 +24,7 @@ from logilab.mtconverter import xml_escape +from cubicweb import tags from cubicweb.schema import display_name from cubicweb.selectors import one_line_rset, is_instance, match_user_groups from cubicweb.view import EntityView, StartupView @@ -32,16 +33,8 @@ _pvs = uicfg.primaryview_section _pvs.tag_attribute(('CWUser', 'login'), 'hidden') -_pvs.tag_attribute(('CWGroup', 'name'), 'hidden') -_pvs.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations') -_pvs.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations') -_pvs.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations') -_pvs.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations') -_pvs.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations') -_pvs.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations') _affk = uicfg.autoform_field_kwargs - _affk.tag_subject_of(('CWUser', 'in_group', 'CWGroup'), {'widget': formwidgets.InOutWidget}) @@ -99,6 +92,11 @@ # group views ################################################################## +_pvs.tag_attribute(('CWGroup', 'name'), 'hidden') +_pvs.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations') +_pvs.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations') +_pvs.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations') +_pvs.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations') _pvs.tag_object_of(('CWUser', 'in_group', 'CWGroup'), 'hidden') _pvs.tag_object_of(('*', 'require_group', 'CWGroup'), 'hidden') @@ -170,7 +168,11 @@ class CWUserManagementView(StartupView): __regid__ = 'cw.user-management' - rql = ('Any U, F, S, U, L ORDERBY L WHERE U is CWUser, U login L, U firstname F, U surname S') + rql = ('Any U,USN,F,S,U,UAA,UDS, L,UAA,UDSN ORDERBY L WHERE U is CWUser, ' + 'U login L, U firstname F, U surname S, ' + 'U in_state US, US name USN, ' + 'U primary_email UA?, UA address UAA, ' + 'U cw_source UDS, US name UDSN') title = _('users and groups management') def call(self, **kwargs): @@ -180,7 +182,7 @@ if eschema.has_perm(self._cw, 'add'): self.w(u'%s' % ( self._cw.build_url('add/%s' % eschema), - self._cw._('add a %s' % etype).capitalize())) + self._cw.__('New %s' % etype).capitalize())) self.w(u'
              ') self.wview('cw.user-table', self._cw.execute(self.rql)) @@ -191,10 +193,15 @@ def call(self, **kwargs): headers = (display_name(self._cw, 'CWUser', 'plural'), + display_name(self._cw, 'in_state'), self._cw._('firstname'), self._cw._('surname'), - display_name(self._cw, 'CWGroup', 'plural')) + display_name(self._cw, 'CWGroup', 'plural'), + display_name(self._cw, 'primary_email'), + display_name(self._cw, 'CWSource')) super(CWUserTable, self).call( - paginate=True, cellvids={3: 'cw.user-table.group-cell'}, + paginate=True, displayfilter=True, + cellvids={0: 'cw.user.login', + 4: 'cw.user-table.group-cell'}, headers=headers, **kwargs) @@ -205,3 +212,11 @@ def cell_call(self, row, col, **kwargs): entity = self.cw_rset.get_entity(row, col) self.w(entity.view('reledit', rtype='in_group', role='subject')) + +class CWUserLoginCell(EntityView): + __regid__ = 'cw.user.login' + __select__ = is_instance('CWUser') + + def cell_call(self, row, col, **kwargs): + entity = self.cw_rset.get_entity(row, col) + self.w(tags.a(entity.login, href=entity.absolute_url())) diff -r dc319ece0bd6 -r c2b29631c1a3 web/views/owl.py --- a/web/views/owl.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/owl.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/views/plots.py --- a/web/views/plots.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/plots.py Mon May 16 16:25:33 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 dc319ece0bd6 -r c2b29631c1a3 web/views/reledit.py --- a/web/views/reledit.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/reledit.py Mon May 16 16:25:33 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'