# HG changeset patch # User Sylvain Thénault # Date 1311178907 -7200 # Node ID cc3987eb793c2908795767f24da289d0e6be879f # Parent dc319ece0bd62f841e712df8133b38981b07d4de# Parent 6397a9051f65f08ccd71c6e3fc6ad612755c8533 oldstable is now 3.12 diff -r dc319ece0bd6 -r cc3987eb793c .hgtags --- a/.hgtags Mon May 16 16:24:00 2011 +0200 +++ b/.hgtags Wed Jul 20 18:21:47 2011 +0200 @@ -188,5 +188,25 @@ 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 +ae33197db1f08d42c9df49563f7b15246f8c8e58 cubicweb-version-3.12.5 +6dfe78a0797ccc34962510f8c2a57f63d65ce41e cubicweb-debian-version-3.12.5-1 +a18dac758150fe9c1f9e4958d898717c32a8f679 cubicweb-version-3.12.6 +105767487c7075dbcce36474f1af0485985cbf2c cubicweb-debian-version-3.12.6-1 +628fe57ce746c1dac87fb1b078b2026057df894e cubicweb-version-3.12.7 +a07517985136bbbfa6610c428a1b42cd04cd530b cubicweb-debian-version-3.12.7-1 +50122a47ce4fb2ecbf3cf20ed2777f4276c93609 cubicweb-version-3.12.8 +cf49ed55685a810d8d73585330ad1a57cc76260d cubicweb-debian-version-3.12.8-1 +cb2990aaa63cbfe593bcf3afdbb9071e4c76815a cubicweb-version-3.12.9 +92464e39134c70e4ddbe6cd78a6e3338a3b88b05 cubicweb-debian-version-3.12.9-1 diff -r dc319ece0bd6 -r cc3987eb793c README --- a/README Mon May 16 16:24:00 2011 +0200 +++ b/README Wed Jul 20 18:21:47 2011 +0200 @@ -25,12 +25,12 @@ cubicweb-ctl start -D myblog sensible-browser http://localhost:8080/ -Details at http://www.cubicweb.org/doc/en/intro/tutorial/blog-in-five-minutes +Details at http://docs.cubicweb.org/tutorials/base/blog-in-five-minutes Documentation ------------- -Look in the doc/ subdirectory or read http://www.cubicweb.org/doc/en/ +Look in the doc/ subdirectory or read http://docs.cubicweb.org/ diff -r dc319ece0bd6 -r cc3987eb793c __pkginfo__.py --- a/__pkginfo__.py Mon May 16 16:24:00 2011 +0200 +++ b/__pkginfo__.py Wed Jul 20 18:21:47 2011 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 11, 3) +numversion = (3, 12, 9) 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 cc3987eb793c cwconfig.py --- a/cwconfig.py Mon May 16 16:24:00 2011 +0200 +++ b/cwconfig.py Wed Jul 20 18:21:47 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 cc3987eb793c cwctl.py --- a/cwctl.py Mon May 16 16:24:00 2011 +0200 +++ b/cwctl.py Wed Jul 20 18:21:47 2011 +0200 @@ -152,16 +152,17 @@ print '*'*72 if not ASK.confirm('%s instance %r ?' % (self.name, appid)): continue - status = max(status, self.run_arg(appid)) + try: + status = max(status, self.run_arg(appid)) + except (KeyboardInterrupt, SystemExit): + print >> sys.stderr, '%s aborted' % self.name + return 2 # specific error code sys.exit(status) def run_arg(self, appid): cmdmeth = getattr(self, '%s_instance' % self.name) try: status = cmdmeth(appid) - except (KeyboardInterrupt, SystemExit): - print >> sys.stderr, '%s aborted' % self.name - return 2 # specific error code except (ExecutionError, ConfigurationError), ex: print >> sys.stderr, 'instance %s not %s: %s' % ( appid, self.actionverb, ex) @@ -757,18 +758,16 @@ applcubicwebversion = vcconf.get('cubicweb') if cubicwebversion > applcubicwebversion: toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion)) - if not self.config.fs_only and not toupgrade: - print '-> no data migration needed for instance %s.' % appid - self.i18nupgrade(config) - mih.shutdown() - return - for cube, fromversion, toversion in toupgrade: - print '-> migration needed from %s to %s for %s' % (fromversion, toversion, cube) # only stop once we're sure we have something to do if not (CWDEV or self.config.nostartstop): StopInstanceCommand(self.logger).stop_instance(appid) # run cubicweb/componants migration scripts - mih.migrate(vcconf, reversed(toupgrade), self.config) + if self.config.fs_only or toupgrade: + for cube, fromversion, toversion in toupgrade: + print '-> migration needed from %s to %s for %s' % (fromversion, toversion, cube) + mih.migrate(vcconf, reversed(toupgrade), self.config) + else: + print '-> no data migration needed for instance %s.' % appid # rewrite main configuration file mih.rewrite_configuration() mih.shutdown() @@ -905,6 +904,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 cc3987eb793c cwvreg.py --- a/cwvreg.py Mon May 16 16:24:00 2011 +0200 +++ b/cwvreg.py Wed Jul 20 18:21:47 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 @@ -742,7 +756,7 @@ sitewide=False): """register a given property""" properties = self['propertydefs'] - assert type in YAMS_TO_PY + assert type in YAMS_TO_PY, 'unknown type %s' % type properties[key] = {'type': type, 'vocabulary': vocabulary, 'default': default, 'help': help, 'sitewide': sitewide} @@ -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 cc3987eb793c dataimport.py --- a/dataimport.py Mon May 16 16:24:00 2011 +0200 +++ b/dataimport.py Wed Jul 20 18:21:47 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 cc3987eb793c dbapi.py --- a/dbapi.py Mon May 16 16:24:00 2011 +0200 +++ b/dbapi.py Wed Jul 20 18:21:47 2011 +0200 @@ -30,6 +30,7 @@ from itertools import count from warnings import warn from os.path import join +from uuid import uuid4 from logilab.common.logging_ext import set_log_methods from logilab.common.decorators import monkeypatch @@ -240,13 +241,14 @@ self.cnx = cnx self.data = {} self.login = login + self.mtime = time() # dbapi session identifier is the same as the first connection # identifier, but may later differ in case of auto-reconnection as done # by the web authentication manager (in cw.web.views.authentication) if cnx is not None: self.sessionid = cnx.sessionid else: - self.sessionid = None + self.sessionid = uuid4().hex @property def anonymous_session(self): diff -r dc319ece0bd6 -r cc3987eb793c debian/changelog --- a/debian/changelog Mon May 16 16:24:00 2011 +0200 +++ b/debian/changelog Wed Jul 20 18:21:47 2011 +0200 @@ -1,3 +1,63 @@ +cubicweb (3.12.9-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Tue, 12 Jul 2011 11:30:10 +0200 + +cubicweb (3.12.8-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Mon, 27 Jun 2011 13:58:58 +0200 + +cubicweb (3.12.7-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Tue, 21 Jun 2011 14:47:14 +0200 + +cubicweb (3.12.6-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Thu, 09 Jun 2011 16:09:50 +0200 + +cubicweb (3.12.5-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Mon, 23 May 2011 10:27:42 +0200 + +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 +66,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 cc3987eb793c debian/control --- a/debian/control Mon May 16 16:24:00 2011 +0200 +++ b/debian/control Wed Jul 20 18:21:47 2011 +0200 @@ -8,6 +8,8 @@ Aurélien Campéas , Nicolas Chauvat Build-Depends: debhelper (>= 7), python (>= 2.5), python-central (>= 0.5) +# for the documentation: +# python-sphinx, python-logilab-common, python-unittest2, Standards-Version: 3.9.1 Homepage: http://www.cubicweb.org XS-Python-Version: >= 2.5, << 2.7 @@ -33,7 +35,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 +99,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 @@ -133,6 +135,7 @@ Package: cubicweb-documentation +Architecture: all Recommends: doc-base Description: documentation for the CubicWeb framework CubicWeb is a semantic web application framework. diff -r dc319ece0bd6 -r cc3987eb793c debian/rules --- a/debian/rules Mon May 16 16:24:00 2011 +0200 +++ b/debian/rules Wed Jul 20 18:21:47 2011 +0200 @@ -15,6 +15,9 @@ # cd doc && make # FIXME cleanup and use sphinx-build as build-depends ? NO_SETUPTOOLS=1 python setup.py build + # XXX uncomment this and associated build-depends in control + #when necessary sphinx version is in all built distribution + #PYTHONPATH=$(CURDIR)/.. $(MAKE) -C doc/book/en all touch build-stamp clean: diff -r dc319ece0bd6 -r cc3987eb793c devtools/__init__.py --- a/devtools/__init__.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/__init__.py Wed Jul 20 18:21:47 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() @@ -527,11 +527,19 @@ return get_db_helper('postgres') @property - @cached def dbcnx(self): - from cubicweb.server.serverctl import _db_sys_cnx - return _db_sys_cnx(self.system_source, 'CREATE DATABASE and / or USER', - interactive=False) + try: + return self._cnx + except AttributeError: + from cubicweb.server.serverctl import _db_sys_cnx + try: + self._cnx = _db_sys_cnx( + self.system_source, 'CREATE DATABASE and / or USER', + interactive=False) + return self._cnx + except Exception: + self._cnx = None + raise @property @cached @@ -569,17 +577,19 @@ cnx.close() init_repository(self.config, interactive=False) except: - self.dbcnx.rollback() + if self.dbcnx is not None: + self.dbcnx.rollback() print >> sys.stderr, 'building', self.dbname, 'failed' #self._drop(self.dbname) raise def helper_clear_cache(self): - self.dbcnx.commit() - self.dbcnx.close() - clear_cache(self, 'dbcnx') + if self.dbcnx is not None: + self.dbcnx.commit() + self.dbcnx.close() + del self._cnx + clear_cache(self, 'cursor') clear_cache(self, 'helper') - clear_cache(self, 'cursor') def __del__(self): self.helper_clear_cache() @@ -600,15 +610,18 @@ def _backup_database(self, db_id): """Actual backup the current database. - return a value to be stored in db_cache to allow restoration""" + return a value to be stored in db_cache to allow restoration + """ from cubicweb.server.serverctl import createdb orig_name = self.system_source['db-name'] try: backup_name = self._backup_name(db_id) self._drop(backup_name) self.system_source['db-name'] = backup_name + self._repo.turn_repo_off() createdb(self.helper, self.system_source, self.dbcnx, self.cursor, template=orig_name) self.dbcnx.commit() + self._repo.turn_repo_on() return backup_name finally: self.system_source['db-name'] = orig_name diff -r dc319ece0bd6 -r cc3987eb793c devtools/fake.py --- a/devtools/fake.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/fake.py Wed Jul 20 18:21:47 2011 +0200 @@ -58,7 +58,7 @@ if not (args or 'vreg' in kwargs): kwargs['vreg'] = CubicWebVRegistry(FakeConfig(), initlog=False) kwargs['https'] = False - self._url = kwargs.pop('url', 'view?rql=Blop&vid=blop') + self._url = kwargs.pop('url', None) or 'view?rql=Blop&vid=blop' super(FakeRequest, self).__init__(*args, **kwargs) self._session_data = {} self._headers_in = Headers() diff -r dc319ece0bd6 -r cc3987eb793c devtools/fill.py --- a/devtools/fill.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/fill.py Wed Jul 20 18:21:47 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 cc3987eb793c devtools/httptest.py --- a/devtools/httptest.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/httptest.py Wed Jul 20 18:21:47 2011 +0200 @@ -145,7 +145,7 @@ if passwd is None: passwd = user self.login(user) - response = self.web_get("?__login=%s&__password=%s" % + response = self.web_get("login?__login=%s&__password=%s" % (user, passwd)) assert response.status == httplib.SEE_OTHER, response.status self._ident_cookie = response.getheader('Set-Cookie') diff -r dc319ece0bd6 -r cc3987eb793c devtools/qunit.py --- a/devtools/qunit.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/qunit.py Wed Jul 20 18:21:47 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 cc3987eb793c devtools/repotest.py --- a/devtools/repotest.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/repotest.py Wed Jul 20 18:21:47 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 cc3987eb793c devtools/testlib.py --- a/devtools/testlib.py Mon May 16 16:24:00 2011 +0200 +++ b/devtools/testlib.py Wed Jul 20 18:21:47 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 ####################################################### @@ -581,9 +611,9 @@ return publisher requestcls = fake.FakeRequest - def request(self, rollbackfirst=False, **kwargs): + def request(self, rollbackfirst=False, url=None, **kwargs): """return a web ui request""" - req = self.requestcls(self.vreg, form=kwargs) + req = self.requestcls(self.vreg, url=url, form=kwargs) if rollbackfirst: self.websession.cnx.rollback() req.set_session(self.websession) @@ -672,7 +702,7 @@ def init_authentication(self, authmode, anonuser=None): self.set_auth_mode(authmode, anonuser) - req = self.request() + req = self.request(url='login') origsession = req.session req.session = req.cnx = None del req.execute # get back to class implementation @@ -696,10 +726,11 @@ 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.assertEqual(len(self.open_sessions), nbsessions) + self.assertIsInstance(req.cnx, (dbapi._NeedAuthAccessMock, NoneType)) + # + 1 since we should still have session without connection set + self.assertEqual(len(self.open_sessions), nbsessions + 1) clear_cache(req, 'get_authorization') # content validation ####################################################### diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/admin/config.rst --- a/doc/book/en/admin/config.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/admin/config.rst Wed Jul 20 18:21:47 2011 +0200 @@ -190,6 +190,21 @@ db-encoding=utf8 +You need to change the default settings on the database by running:: + + ALTER DATABASE SET READ_COMMITTED_SNAPSHOT ON; + +The ALTER DATABASE command above requires some permissions that your +user may not have. In that case you will have to ask your local DBA to +run the query for you. + +You can check that the setting is correct by running the following +query which must return '1':: + + SELECT is_read_committed_snapshot_on + FROM sys.databases WHERE name=''; + + .. _SQLiteConfiguration: @@ -209,6 +224,8 @@ Pyro configuration ------------------ +Pyro name server +~~~~~~~~~~~~~~~~ If you want to use Pyro to access your instance remotely, or to have multi-source or distributed configuration, it is required to have a Pyro name server running on your network. By default it is detected by a broadcast request, but you can @@ -216,9 +233,13 @@ To do so, you need to : +* be sure to have installed it (see :ref:`InstallDependencies`) + * launch the pyro name server with `pyro-nsd start` before starting cubicweb * under debian, edit the file :file:`/etc/default/pyro-nsd` so that the name server pyro will be launched automatically when the machine fire up +Note that you can use the pyro server without a running pyro nameserver. +Refer to `pyro-ns-host` server configuration option for details. diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/admin/index.rst --- a/doc/book/en/admin/index.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/admin/index.rst Wed Jul 20 18:21:47 2011 +0200 @@ -14,6 +14,8 @@ :numbered: setup + setup-windows + config cubicweb-ctl create-instance instance-config diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 2011 +0200 @@ -185,12 +185,6 @@ recommended also for other attribute types). By default it expects to generate HTML, so it deals with rich text formating, xml escaping... -How do I translate an msg id defined (and translated) in another cube ? ------------------------------------------------------------------------ - -You should put these translations in the `i18n/static-messages.pot` -file of your own cube. - How to update a database after a schema modification ? ------------------------------------------------------ @@ -380,11 +374,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 cc3987eb793c doc/book/en/annexes/rql/debugging.rst --- a/doc/book/en/annexes/rql/debugging.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/annexes/rql/debugging.rst Wed Jul 20 18:21:47 2011 +0200 @@ -8,26 +8,15 @@ Available levels ~~~~~~~~~~~~~~~~ -:DBG_NONE: - no debug information (current mode) - -:DBG_RQL: - rql execution information - -:DBG_SQL: - executed sql +Server debugging flags. They may be combined using binary operators. -:DBG_REPO: - repository events - -:DBG_MS: - multi-sources - -:DBG_MORE: - more verbosity - -:DBG_ALL: - all level enabled +.. autodata:: cubicweb.server.DBG_NONE +.. autodata:: cubicweb.server.DBG_RQL +.. autodata:: cubicweb.server.DBG_SQL +.. autodata:: cubicweb.server.DBG_REPO +.. autodata:: cubicweb.server.DBG_MS +.. autodata:: cubicweb.server.DBG_MORE +.. autodata:: cubicweb.server.DBG_ALL Enable verbose output @@ -40,6 +29,8 @@ from cubicweb import server server.set_debug(server.DBG_RQL|server.DBG_SQL|server.DBG_ALL) +.. autofunction:: cubicweb.server.set_debug + Detect largest RQL queries ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -50,7 +41,5 @@ API ~~~ -.. autofunction:: cubicweb.server.set_debug - .. autoclass:: cubicweb.server.debugged diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/annexes/rql/intro.rst --- a/doc/book/en/annexes/rql/intro.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/annexes/rql/intro.rst Wed Jul 20 18:21:47 2011 +0200 @@ -159,3 +159,4 @@ .. _Datalog: http://en.wikipedia.org/wiki/Datalog .. _intensional: http://en.wikipedia.org/wiki/Intensional_definition .. _extensional: http://en.wikipedia.org/wiki/Extension_(predicate_logic) + diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/annexes/rql/language.rst --- a/doc/book/en/annexes/rql/language.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/annexes/rql/language.rst Wed Jul 20 18:21:47 2011 +0200 @@ -152,6 +152,10 @@ - Aggregate Functions: COUNT, MIN, MAX, AVG, SUM, GROUP_CONCAT +.. note:: + Aggregate functions will return None if there is no result row. + + Having ``````` diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 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 +.. autofunction:: cubicweb.selectors.on_fire_transition .. autoclass:: cubicweb.selectors.implements diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/controllers.rst --- a/doc/book/en/devweb/controllers.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devweb/controllers.rst Wed Jul 20 18:21:47 2011 +0200 @@ -16,11 +16,11 @@ `Browsing`: * the View controller is associated with most browsing actions within a - CubicWeb application: it always instantiates a :ref:`the_main_template` and - lets the ResultSet/Views dispatch system build up the whole content; it - handles :exc:`ObjectNotFound` and :exc:`NoSelectableObject` errors that may - bubble up to its entry point, in an end-user-friendly way (but other - programming errors will slip through) + CubicWeb application: it always instantiates a + :ref:`the_main_template_layout` and lets the ResultSet/Views dispatch system + build up the whole content; it handles :exc:`ObjectNotFound` and + :exc:`NoSelectableObject` errors that may bubble up to its entry point, in an + end-user-friendly way (but other programming errors will slip through) * the JSon controller (same module) provides services for Ajax calls, typically using JSON as a serialization format for input, and diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/httpcaching.rst --- a/doc/book/en/devweb/httpcaching.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devweb/httpcaching.rst Wed Jul 20 18:21:47 2011 +0200 @@ -1,3 +1,21 @@ HTTP cache management ---------------------- -XXX feedme \ No newline at end of file +===================== + +.. automodule:: cubicweb.web.httpcache + +Cache policies +-------------- +.. autoclass:: cubicweb.web.httpcache.NoHTTPCacheManager +.. autoclass:: cubicweb.web.httpcache.MaxAgeHTTPCacheManager +.. autoclass:: cubicweb.web.httpcache.EtagHTTPCacheManager +.. autoclass:: cubicweb.web.httpcache.EntityHTTPCacheManager + +Exception +--------- +.. autoexception:: cubicweb.web.httpcache.NoEtag + +Helper functions +---------------- +.. autofunction:: cubicweb.web.httpcache.set_http_cache_headers + +.. NOT YET AVAILABLE IN STABLE autofunction:: cubicweb.web.httpcache.lastmodified diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/index.rst --- a/doc/book/en/devweb/index.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devweb/index.rst Wed Jul 20 18:21:47 2011 +0200 @@ -17,5 +17,6 @@ edition/index facets internationalization -.. property + property httpcaching + resource diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/resource.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/devweb/resource.rst Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,16 @@ +Locate resources +---------------- + +.. automethod:: cubicweb.web.webconfig.WebConfiguration.locate_resource +.. automethod:: cubicweb.web.webconfig.WebConfiguration.locate_doc_file +.. automethod:: cubicweb.web.webconfig.WebConfiguration.locate_all_files + +Static files handling +--------------------- + +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_directory +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_exists +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_open +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_add +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_del + diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/views/idownloadable.rst --- a/doc/book/en/devweb/views/idownloadable.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devweb/views/idownloadable.rst Wed Jul 20 18:21:47 2011 +0200 @@ -1,5 +1,23 @@ -The 'download' view -------------------- +The 'download' views +==================== + +.. automodule:: cubicweb.web.views.idownloadable + +Components +---------- + +.. autoclass:: cubicweb.web.views.idownloadable.DownloadBox -(:mod:`cubicweb.web.views.idownloadable`) +Download views +-------------- +.. autoclass:: cubicweb.web.views.idownloadable.DownloadView +.. autoclass:: cubicweb.web.views.idownloadable.DownloadLinkView +.. autoclass:: cubicweb.web.views.idownloadable.IDownloadablePrimaryView +.. autoclass:: cubicweb.web.views.idownloadable.IDownloadableLineView + +Embedded views +-------------- + +.. autoclass:: cubicweb.web.views.idownloadable.ImageView +.. autoclass:: cubicweb.web.views.idownloadable.EHTMLView diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/views/index.rst --- a/doc/book/en/devweb/views/index.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devweb/views/index.rst Wed Jul 20 18:21:47 2011 +0200 @@ -25,7 +25,7 @@ urlpublish breadcrumbs -.. wdoc + idownloadable + wdoc .. embedding -.. idownloadable diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/devweb/views/wdoc.rst --- a/doc/book/en/devweb/views/wdoc.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/devweb/views/wdoc.rst Wed Jul 20 18:21:47 2011 +0200 @@ -1,9 +1,17 @@ .. -*- coding: utf-8 -*- Online documentation system ---------------------------- +=========================== + +.. automodule:: cubicweb.web.views.wdoc -(:mod:`cubicweb.web.views.wdoc`) +Help views +---------- +.. autoclass:: cubicweb.web.views.wdoc.InlineHelpView +.. autoclass:: cubicweb.web.views.wdoc.ChangeLogView -XXX describe the on-line documentation system - +Actions +------- +.. autoclass:: cubicweb.web.views.wdoc.HelpAction +.. autoclass:: cubicweb.web.views.wdoc.ChangeLogAction +.. autoclass:: cubicweb.web.views.wdoc.AboutAction diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/tutorials/advanced/part03_bfss.rst --- a/doc/book/en/tutorials/advanced/part03_bfss.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/tutorials/advanced/part03_bfss.rst Wed Jul 20 18:21:47 2011 +0200 @@ -32,7 +32,7 @@ makedirs(bfssdir) print 'created', bfssdir storage = storages.BytesFileSystemStorage(bfssdir) - set_attribute_storage(self.repo, 'File', 'data', storage) + storages.set_attribute_storage(self.repo, 'File', 'data', storage) .. Note:: diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/tutorials/advanced/part05_ui-advanced.rst --- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Wed Jul 20 18:21:47 2011 +0200 @@ -3,6 +3,8 @@ We'll now see how to benefit from features introduced in 3.9 and 3.10 releases of CubicWeb +.. _uiprops: + Step 1: tired of the default look? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -r dc319ece0bd6 -r cc3987eb793c doc/book/en/tutorials/tools/windmill.rst --- a/doc/book/en/tutorials/tools/windmill.rst Mon May 16 16:24:00 2011 +0200 +++ b/doc/book/en/tutorials/tools/windmill.rst Wed Jul 20 18:21:47 2011 +0200 @@ -23,23 +23,21 @@ environment, take a look to the `virtualenv `_ project as well):: - pip install windmill - curl -O http://github.com/windmill/windmill/tarball/master + $ pip install windmill + $ curl -O http://github.com/windmill/windmill/tarball/master However, the Windmill project doesn't release frequently. Our recommandation is -to used the last snapshot of the Git repository: - -.. sourcecode:: bash +to used the last snapshot of the Git repository:: - git clone git://github.com/windmill/windmill.git HEAD - cd windmill - python setup.py develop + $ git clone git://github.com/windmill/windmill.git HEAD + $ cd windmill + $ python setup.py develop Install instructions are `available `_. Be sure to have the windmill module in your PYTHONPATH afterwards:: - python -c "import windmill" + $ python -c "import windmill" X dummy ------- @@ -60,9 +58,9 @@ *From: http://www.x.org/wiki/XorgTesting* -Then, you can run the X server with the following command : +Then, you can run the X server with the following command :: - /usr/bin/X11/Xvfb :1 -ac -screen 0 1280x1024x8 -fbdir /tmp + $ /usr/bin/X11/Xvfb :1 -ac -screen 0 1280x1024x8 -fbdir /tmp Windmill usage @@ -82,13 +80,15 @@ If you are using firefox as client, consider the "firebug" option. -If you have a running instance, you can refine the test by the *loadtest* windmill option: +If you have a running instance, you can refine the test by the *loadtest* windmill option:: - windmill -m firebug loadtest= + $ windmill -m firebug loadtest= -Or use the internal windmill shell to explore available commands: +Or use the internal windmill shell to explore available commands:: - windmill -m firebug shell + $ windmill -m firebug shell + +And enter python commands: .. sourcecode:: python @@ -125,7 +125,7 @@ To run your test series:: - % pytest test/test_windmill.py + $ pytest test/test_windmill.py By default, CubicWeb will use **firefox** as the default browser and will try to run test instance server on localhost. In the general case, You've no need @@ -144,6 +144,8 @@ Examples: +.. sourcecode:: python + browser = 'firefox' test_dir = osp.join(__file__, 'windmill') edit_test = False @@ -162,7 +164,7 @@ For instance, CubicWeb framework windmill tests can be manually run by:: - % pytest web/test/test_windmill.py + $ pytest web/test/test_windmill.py Edit your tests --------------- @@ -172,7 +174,7 @@ But if you are using `pytest` as test runner, use the `-i` option directly. The test series will be loaded and you can run assertions step-by-step:: - % pytest -i test/test_windmill.py + $ pytest -i test/test_windmill.py In this case, the `firebug` extension will be loaded automatically for you. diff -r dc319ece0bd6 -r cc3987eb793c doc/tools/pyjsrest.py --- a/doc/tools/pyjsrest.py Mon May 16 16:24:00 2011 +0200 +++ b/doc/tools/pyjsrest.py Wed Jul 20 18:21:47 2011 +0200 @@ -127,8 +127,6 @@ 'cubicweb.htmlhelpers', 'cubicweb.ajax', - 'cubicweb.lazy', - 'cubicweb.tabs', 'cubicweb.ajax.box', 'cubicweb.facets', 'cubicweb.widgets', @@ -153,9 +151,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 cc3987eb793c entities/authobjs.py --- a/entities/authobjs.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/authobjs.py Wed Jul 20 18:21:47 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 cc3987eb793c entities/schemaobjs.py --- a/entities/schemaobjs.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/schemaobjs.py Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c entities/wfobjs.py --- a/entities/wfobjs.py Mon May 16 16:24:00 2011 +0200 +++ b/entities/wfobjs.py Wed Jul 20 18:21:47 2011 +0200 @@ -572,18 +572,31 @@ kwargs['to_state'] = self._cw.entity_from_eid(tseid) return self._cw.create_entity('TrInfo', **kwargs) - def fire_transition(self, tr, comment=None, commentformat=None): - """change the entity's state by firing transition of the given name in - entity's workflow - """ + def _get_transition(self, tr): assert self.current_workflow if isinstance(tr, basestring): _tr = self.current_workflow.transition_by_name(tr) assert _tr is not None, 'not a %s transition: %s' % ( self.__regid__, tr) tr = _tr + return tr + + def fire_transition(self, tr, comment=None, commentformat=None): + """change the entity's state by firing given transition (name or entity) + in entity's workflow + """ + tr = self._get_transition(tr) return self._add_trinfo(comment, commentformat, tr.eid) + def fire_transition_if_possible(self, tr, comment=None, commentformat=None): + """change the entity's state by firing given transition (name or entity) + in entity's workflow if this transition is possible + """ + tr = self._get_transition(tr) + if any(tr_ for tr_ in self.possible_transitions() + if tr_.eid == tr.eid): + self.fire_transition(tr) + def change_state(self, statename, comment=None, commentformat=None, tr=None): """change the entity's state to the given state (name or entity) in entity's workflow. This method should only by used by manager to fix an @@ -595,7 +608,7 @@ stateeid = statename.eid else: if not isinstance(statename, basestring): - warn('[3.5] give a state name', DeprecationWarning) + warn('[3.5] give a state name', DeprecationWarning, stacklevel=2) state = self.current_workflow.state_by_eid(statename) else: state = self.current_workflow.state_by_name(statename) @@ -605,3 +618,20 @@ stateeid = state.eid # XXX try to find matching transition? return self._add_trinfo(comment, commentformat, tr and tr.eid, stateeid) + + def set_initial_state(self, statename): + """set a newly created entity's state to the given state (name or entity) + in entity's workflow. This is useful if you don't want it to be the + workflow's initial state. + """ + assert self.current_workflow + if hasattr(statename, 'eid'): + stateeid = statename.eid + else: + state = self.current_workflow.state_by_name(statename) + if state is None: + raise WorkflowException('not a %s state: %s' % (self.__regid__, + statename)) + stateeid = state.eid + self._cw.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s', + {'x': self.entity.eid, 's': stateeid}) diff -r dc319ece0bd6 -r cc3987eb793c entity.py --- a/entity.py Mon May 16 16:24:00 2011 +0200 +++ b/entity.py Wed Jul 20 18:21:47 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 @@ -62,6 +62,23 @@ return True +def remove_ambiguous_rels(attr_set, subjtypes, schema): + '''remove from `attr_set` the relations of entity types `subjtypes` that have + different entity type sets as target''' + for attr in attr_set.copy(): + rschema = schema.rschema(attr) + if rschema.final: + continue + ttypes = None + for subjtype in subjtypes: + cur_ttypes = rschema.objects(subjtype) + if ttypes is None: + ttypes = cur_ttypes + elif cur_ttypes != ttypes: + attr_set.remove(attr) + break + + class Entity(AppObject): """an entity instance has e_schema automagically set on the class and instances has access to their issuing cursor. @@ -91,7 +108,7 @@ # class attributes that must be set in class definition rest_attr = None fetch_attrs = None - skip_copy_for = ('in_state',) + skip_copy_for = ('in_state',) # XXX turn into a set # class attributes set automatically at registration time e_schema = None @@ -157,6 +174,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 +183,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 +221,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 +233,18 @@ # 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) + remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema) + 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 +288,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 +302,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 +315,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 +754,17 @@ 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) + # XXX we should fetch ambiguous relation objects too but not + # recurse on them in _fetch_restrictions; it is easier to remove + # them completely for now, as it would require an deeper api rewrite + remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema) 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 +788,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 +798,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 +886,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 +982,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 +999,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 cc3987eb793c hooks/integrity.py --- a/hooks/integrity.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/integrity.py Wed Jul 20 18:21:47 2011 +0200 @@ -121,14 +121,15 @@ return session = self._cw eidfrom, eidto = self.eidfrom, self.eidto - pendingrdefs = session.transaction_data.get('pendingrdefs', ()) - if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs: + rdef = session.rtype_eids_rdef(rtype, eidfrom, eidto) + if (rdef.subject, rtype, rdef.object) in session.transaction_data.get('pendingrdefs', ()): return - card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') + card = rdef.cardinality if card[0] in '1+' and not session.deleted_in_transaction(eidfrom): - _CheckSRelationOp.get_instance(self._cw).add_data((eidfrom, rtype)) + _CheckSRelationOp.get_instance(session).add_data((eidfrom, rtype)) if card[1] in '1+' and not session.deleted_in_transaction(eidto): - _CheckORelationOp.get_instance(self._cw).add_data((eidto, rtype)) + _CheckORelationOp.get_instance(session).add_data((eidto, rtype)) + class CheckCardinalityHookAfterAddEntity(IntegrityHook): """check cardinalities are satisfied""" @@ -150,21 +151,6 @@ op = _CheckORelationOp.get_instance(self._cw) op.add_data((eid, rschema.type)) - def before_delete_relation(self): - rtype = self.rtype - if rtype in DONT_CHECK_RTYPES_ON_DEL: - return - session = self._cw - eidfrom, eidto = self.eidfrom, self.eidto - pendingrdefs = session.transaction_data.get('pendingrdefs', ()) - if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs: - return - card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') - if card[0] in '1+' and not session.deleted_in_transaction(eidfrom): - _CheckSRelationOp.get_instance(self._cw).add_data((eidfrom, rtype)) - if card[1] in '1+' and not session.deleted_in_transaction(eidto): - _CheckORelationOp.get_instance(self._cw).add_data((eidto, rtype)) - class _CheckConstraintsOp(hook.DataOperationMixIn, hook.LateOperation): """ check a new relation satisfy its constraints """ @@ -204,8 +190,8 @@ def __call__(self): # XXX get only RQL[Unique]Constraints? - constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto, - 'constraints') + rdef = self._cw.rtype_eids_rdef(self.rtype, self.eidfrom, self.eidto) + constraints = rdef.constraints if constraints: _CheckConstraintsOp.get_instance(self._cw).add_data( (self.eidfrom, self.rtype, self.eidto, constraints)) @@ -355,15 +341,15 @@ def __call__(self): # if the relation is being delete, don't delete composite's components # automatically - pendingrdefs = self._cw.transaction_data.get('pendingrdefs', ()) - if (self._cw.describe(self.eidfrom)[0], self.rtype, - self._cw.describe(self.eidto)[0]) in pendingrdefs: + session = self._cw + rtype = self.rtype + rdef = session.rtype_eids_rdef(rtype, self.eidfrom, self.eidto) + if (rdef.subject, rtype, rdef.object) in session.transaction_data.get('pendingrdefs', ()): return - composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto, - 'composite') + composite = rdef.composite if composite == 'subject': _DelayedDeleteOEntityOp.get_instance(self._cw).add_data( - (self.eidto, self.rtype)) + (self.eidto, rtype)) elif composite == 'object': _DelayedDeleteSEntityOp.get_instance(self._cw).add_data( - (self.eidfrom, self.rtype)) + (self.eidfrom, rtype)) diff -r dc319ece0bd6 -r cc3987eb793c hooks/metadata.py --- a/hooks/metadata.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/metadata.py Wed Jul 20 18:21:47 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): @@ -107,7 +106,7 @@ # skip this special composite relation # XXX (syt) why? return eidfrom, eidto = self.eidfrom, self.eidto - composite = self._cw.schema_rproperty(self.rtype, eidfrom, eidto, 'composite') + composite = self._cw.rtype_eids_rdef(self.rtype, eidfrom, eidto).composite if composite == 'subject': SyncOwnersOp.get_instance(self._cw).add_data( (eidfrom, eidto) ) elif composite == 'object': diff -r dc319ece0bd6 -r cc3987eb793c hooks/syncschema.py --- a/hooks/syncschema.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/syncschema.py Wed Jul 20 18:21:47 2011 +0200 @@ -263,7 +263,7 @@ for rtype in (META_RTYPES - VIRTUAL_RTYPES): try: rschema = schema[rtype] - except: + except KeyError: if rtype == 'cw_source': continue # XXX 3.10 migration raise @@ -574,7 +574,8 @@ elif lastrel: DropRelationTable(session, str(rschema)) # then update the in-memory schema - rschema.del_relation_def(rdef.subject, rdef.object) + if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP: + rschema.del_relation_def(rdef.subject, rdef.object) # if this is the last relation definition of this type, drop associated # relation type if lastrel and not session.deleted_in_transaction(rschema.eid): @@ -585,8 +586,10 @@ # # Note: add_relation_def takes a RelationDefinition, not a # RelationDefinitionSchema, needs to fake it - self.rdef.name = str(self.rdef.rtype) - self.session.vreg.schema.add_relation_def(self.rdef) + rdef = self.rdef + rdef.name = str(rdef.rtype) + if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP: + self.session.vreg.schema.add_relation_def(rdef) @@ -882,7 +885,7 @@ if name in CORE_TYPES: raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')}) # delete every entities of this type - if not name in ETYPE_NAME_MAP: + if name not in ETYPE_NAME_MAP: self._cw.execute('DELETE %s X' % name) MemSchemaCWETypeDel(self._cw, etype=name) DropTable(self._cw, table=SQL_PREFIX + name) @@ -1066,6 +1069,8 @@ return subjtype = entity.stype.name objtype = entity.otype.name + if subjtype in ETYPE_NAME_MAP or objtype in ETYPE_NAME_MAP: + return rschema = self._cw.vreg.schema[entity.rtype.name] # note: do not access schema rdef here, it may be added later by an # operation diff -r dc319ece0bd6 -r cc3987eb793c hooks/syncsources.py --- a/hooks/syncsources.py Mon May 16 16:24:00 2011 +0200 +++ b/hooks/syncsources.py Wed Jul 20 18:21:47 2011 +0200 @@ -132,7 +132,7 @@ if not session.deleted_in_transaction(schemacfg.eid): source.add_schema_config(schemacfg, checkonly=checkonly) elif session.deleted_in_transaction(schemacfg.eid): - source.delete_schema_config(schemacfg, checkonly=checkonly) + source.del_schema_config(schemacfg, checkonly=checkonly) else: source.update_schema_config(schemacfg, checkonly=checkonly) @@ -160,4 +160,4 @@ def __call__(self): SourceMappingChangedOp.get_instance(self._cw).add_data( (self._cw.entity_from_eid(self.eidfrom), - self._cw.entity_from_eid(self.eidto)) ) + self._cw.entity_from_eid(self.eidto).repo_source) ) diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c i18n/de.po --- a/i18n/de.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/de.po Wed Jul 20 18:21:47 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" @@ -1192,6 +1200,9 @@ msgid "anonymous" msgstr "anonym" +msgid "anyrsetview" +msgstr "" + msgid "april" msgstr "April" @@ -2323,6 +2334,9 @@ msgid "entity update" msgstr "Aktualisierung der Entität" +msgid "entityview" +msgstr "" + msgid "error" msgstr "" @@ -2743,6 +2757,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" @@ -3453,7 +3470,7 @@ #, python-format msgid "" -"relation %s is supported but none if its definitions matches supported " +"relation %s is supported but none of its definitions matches supported " "entities" msgstr "" @@ -3748,6 +3765,9 @@ msgid "startup views" msgstr "Start-Ansichten" +msgid "startupview" +msgstr "" + msgid "state" msgstr "Zustand" @@ -4014,7 +4034,7 @@ msgid "tr_count" msgstr "" -msgid "transaction undoed" +msgid "transaction undone" msgstr "Transaktion rückgängig gemacht" #, python-format diff -r dc319ece0bd6 -r cc3987eb793c i18n/en.po --- a/i18n/en.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/en.po Wed Jul 20 18:21:47 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" @@ -1149,6 +1157,9 @@ msgid "anonymous" msgstr "" +msgid "anyrsetview" +msgstr "rset views" + msgid "april" msgstr "" @@ -2267,6 +2278,9 @@ msgid "entity update" msgstr "" +msgid "entityview" +msgstr "entity views" + msgid "error" msgstr "" @@ -2668,6 +2682,9 @@ "is created" msgstr "" +msgid "indifferent" +msgstr "indifferent" + msgid "info" msgstr "" @@ -3305,7 +3322,7 @@ msgstr "" msgid "read_permission" -msgstr "can be read by" +msgstr "read permission" msgctxt "CWAttribute" msgid "read_permission" @@ -3324,11 +3341,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 "" @@ -3363,7 +3380,7 @@ #, python-format msgid "" -"relation %s is supported but none if its definitions matches supported " +"relation %s is supported but none of its definitions matches supported " "entities" msgstr "" @@ -3648,6 +3665,9 @@ msgid "startup views" msgstr "" +msgid "startupview" +msgstr "startup views" + msgid "state" msgstr "" @@ -3909,7 +3929,7 @@ msgid "tr_count" msgstr "transition number" -msgid "transaction undoed" +msgid "transaction undone" msgstr "" #, python-format diff -r dc319ece0bd6 -r cc3987eb793c i18n/es.po --- a/i18n/es.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/es.po Wed Jul 20 18:21:47 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" @@ -1193,16 +1209,19 @@ msgid "anonymous" msgstr "anónimo" +msgid "anyrsetview" +msgstr "" + msgid "april" msgstr "Abril" #, 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 +1234,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 +1347,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 +1361,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 +1379,7 @@ "cardinalidad %(card)s" msgid "cancel" -msgstr "" +msgstr "anular" msgid "cancel select" msgstr "Cancelar la selección" @@ -1391,7 +1412,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 +1496,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 +1537,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 +1643,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 +1828,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 +1865,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 +1888,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 +1975,10 @@ msgstr "Permisos" msgid "cwsource-main" -msgstr "" +msgstr "descripción" msgid "cwsource-mapping" -msgstr "" +msgstr "mapeo" msgid "cwuri" msgstr "Uri Interna" @@ -1966,16 +1987,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 +2087,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 +2333,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" @@ -2353,8 +2376,11 @@ msgid "entity update" msgstr "Actualización de la Entidad" +msgid "entityview" +msgstr "" + msgid "error" -msgstr "" +msgstr "error" msgid "error while embedding page" msgstr "Error durante la inclusión de la página" @@ -2418,10 +2444,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 +2600,7 @@ msgstr "Texto indexado" msgid "gc" -msgstr "" +msgstr "fuga de memoria" msgid "generic plot" msgstr "Gráfica Genérica" @@ -2637,10 +2663,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 +2799,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 +2836,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 +2943,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 +3011,7 @@ msgstr "Usuario" msgid "login / password" -msgstr "" +msgstr "usuario / contraseña" msgid "login or email" msgstr "Usuario o dirección de correo" @@ -2999,7 +3030,7 @@ msgstr "Informaciones Generales" msgid "main_tab" -msgstr "" +msgstr "descripción" msgid "mainvars" msgstr "Variables principales" @@ -3030,11 +3061,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 +3093,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 +3105,7 @@ msgstr "Lunes" msgid "month" -msgstr "" +msgstr "mes" msgid "more actions" msgstr "Más acciones" @@ -3121,7 +3152,7 @@ msgctxt "CWSource" msgid "name" -msgstr "" +msgstr "nombre" msgctxt "State" msgid "name" @@ -3150,7 +3181,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 +3298,7 @@ msgctxt "CWSourceSchemaConfig" msgid "options" -msgstr "" +msgstr "opciones" msgid "order" msgstr "Orden" @@ -3308,14 +3339,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 +3429,7 @@ msgstr "Dirección principal de correo electrónico de" msgid "profile" -msgstr "" +msgstr "perfil" msgid "progress" msgstr "Progreso" @@ -3414,7 +3447,7 @@ msgstr "Permisos" msgid "rdf" -msgstr "" +msgstr "rdf" msgid "read" msgstr "Lectura" @@ -3447,6 +3480,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 +3504,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 " +"relation %s is supported but none of 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 +3548,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 +3674,7 @@ msgstr "Seguridad" msgid "see more" -msgstr "" +msgstr "ver más" msgid "see them all" msgstr "Ver todos" @@ -3728,7 +3769,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 +3785,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,11 +3810,14 @@ #, python-format msgid "specifying %s is mandatory" -msgstr "" +msgstr "especificar %s es obligatorio" msgid "startup views" msgstr "Vistas de inicio" +msgid "startupview" +msgstr "" + msgid "state" msgstr "Estado" @@ -3899,7 +3945,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 +3979,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 +3992,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 +4069,7 @@ msgstr "Transición hacia este Estado" msgid "today" -msgstr "" +msgstr "hoy" msgid "todo_by" msgstr "Asignada a" @@ -4031,13 +4078,13 @@ msgstr "Cambiar valor" msgid "tr_count" -msgstr "" +msgstr "n° de transición" msgctxt "TrInfo" msgid "tr_count" -msgstr "" - -msgid "transaction undoed" +msgstr "n° de transición" + +msgid "transaction undone" msgstr "Transacciones Anuladas" #, python-format @@ -4090,7 +4137,7 @@ msgctxt "CWSource" msgid "type" -msgstr "" +msgstr "tipo" msgctxt "Transition" msgid "type" @@ -4104,7 +4151,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 +4206,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 +4272,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 +4344,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 +4424,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 +4529,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 cc3987eb793c i18n/fr.po --- a/i18n/fr.po Mon May 16 16:24:00 2011 +0200 +++ b/i18n/fr.po Wed Jul 20 18:21:47 2011 +0200 @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: cubicweb 2.46.0\n" -"PO-Revision-Date: 2011-01-03 14:35+0100\n" +"PO-Revision-Date: 2011-06-23 10:23+0200\n" "Last-Translator: Logilab Team \n" "Language-Team: fr \n" "Language: \n" @@ -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" @@ -1199,6 +1207,9 @@ msgid "anonymous" msgstr "anonyme" +msgid "anyrsetview" +msgstr "vues \"tous les rset\"" + msgid "april" msgstr "avril" @@ -1934,10 +1945,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\"" @@ -2363,6 +2374,9 @@ msgid "entity update" msgstr "mise à jour d'entité" +msgid "entityview" +msgstr "vues d'entité" + msgid "error" msgstr "erreur" @@ -2783,6 +2797,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" @@ -3500,7 +3517,7 @@ #, python-format msgid "" -"relation %s is supported but none if its definitions matches supported " +"relation %s is supported but none of its definitions matches supported " "entities" msgstr "" "la relation %s est supportée mais aucune de ses définitions ne correspondent " @@ -3799,6 +3816,9 @@ msgid "startup views" msgstr "vues de départ" +msgid "startupview" +msgstr "vues de départ" + msgid "state" msgstr "état" @@ -4065,8 +4085,8 @@ msgid "tr_count" msgstr "n° de transition" -msgid "transaction undoed" -msgstr "transaction annulées" +msgid "transaction undone" +msgstr "transaction annulée" #, python-format msgid "transition %(tr)s isn't allowed from %(st)s" @@ -4535,6 +4555,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 cc3987eb793c migration.py --- a/migration.py Mon May 16 16:24:00 2011 +0200 +++ b/migration.py Wed Jul 20 18:21:47 2011 +0200 @@ -492,6 +492,8 @@ self.warnings.append( 'cube %s depends on %s but constraint badly ' 'formatted: %s' % (cube, name, constraint)) + else: + self.reverse_dependencies[name].add( (None, None, cube) ) # check consistency for cube, versions in sorted(self.reverse_dependencies.items()): oper, version, source = None, None, None @@ -507,6 +509,8 @@ if version_strictly_lower(version, ver): version = ver source = src + elif op == None: + continue else: print 'unable to handle this case', oper, version, op, ver # "solve" constraint satisfaction problem @@ -517,5 +521,7 @@ if oper in ('>=','='): if lower_strict: self.errors.append( ('update', cube, version, source) ) + elif oper is None: + pass # no constraint on version else: print 'unknown operator', oper diff -r dc319ece0bd6 -r cc3987eb793c misc/migration/3.10.0_Any.py --- a/misc/migration/3.10.0_Any.py Mon May 16 16:24:00 2011 +0200 +++ b/misc/migration/3.10.0_Any.py Wed Jul 20 18:21:47 2011 +0200 @@ -32,7 +32,7 @@ # rename cwprops for boxes/contentnavigation for x in rql('Any X,XK WHERE X pkey XK, ' - 'X pkey ~= "boxes.%s" OR ' - 'X pkey ~= "contentnavigation.%s"').entities(): + 'X pkey ~= "boxes.%" OR ' + 'X pkey ~= "contentnavigation.%"').entities(): x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1]) diff -r dc319ece0bd6 -r cc3987eb793c misc/migration/3.10.9_Any.py --- a/misc/migration/3.10.9_Any.py Mon May 16 16:24:00 2011 +0200 +++ b/misc/migration/3.10.9_Any.py Wed Jul 20 18:21:47 2011 +0200 @@ -11,7 +11,7 @@ from logilab.common.shellutils import progress from cubicweb.server.session import hooks_control rset = rql('Any X, XC WHERE X cwuri XC, X cwuri ~= "%/eid/%"') - title = "%i entites to fix" % len(rset) + title = "%i entities to fix" % len(rset) nbops = rset.rowcount enabled = interactive_mode with progress(title=title, nbops=nbops, size=30, enabled=enabled) as pb: diff -r dc319ece0bd6 -r cc3987eb793c misc/migration/3.12.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.12.0_Any.py Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,4 @@ +if schema['TZDatetime'].eid is None: + add_entity_type('TZDatetime') +if schema['TZTime'].eid is None: + add_entity_type('TZTime') diff -r dc319ece0bd6 -r cc3987eb793c misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Mon May 16 16:24:00 2011 +0200 +++ b/misc/migration/bootstrapmigration_repository.py Wed Jul 20 18:21:47 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. @@ -94,7 +94,7 @@ drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression') sync_schema_props_perms('read_permission', syncperms=False) # fix read_permission cardinality -if applcubicwebversion < (3, 9, 6) and cubicwebversion >= (3, 9, 6): +if applcubicwebversion < (3, 9, 6) and cubicwebversion >= (3, 9, 6) and not 'CWUniqueTogetherConstraint' in schema: add_entity_type('CWUniqueTogetherConstraint') if not ('CWUniqueTogetherConstraint', 'CWRType') in schema['relations'].rdefs: diff -r dc319ece0bd6 -r cc3987eb793c misc/scripts/chpasswd.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/scripts/chpasswd.py Wed Jul 20 18:21:47 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 cc3987eb793c req.py --- a/req.py Mon May 16 16:24:00 2011 +0200 +++ b/req.py Wed Jul 20 18:21:47 2011 +0200 @@ -88,7 +88,7 @@ rset = ResultSet([('A',)]*size, '%s X' % etype, description=[(etype,)]*size) def get_entity(row, col=0, etype=etype, req=self, rset=rset): - return req.vreg.etype_class(etype)(req, rset, row, col) + return req.vreg['etypes'].etype_class(etype)(req, rset, row, col) rset.get_entity = get_entity rset.req = self return rset diff -r dc319ece0bd6 -r cc3987eb793c rqlrewrite.py --- a/rqlrewrite.py Mon May 16 16:24:00 2011 +0200 +++ b/rqlrewrite.py Wed Jul 20 18:21:47 2011 +0200 @@ -20,12 +20,16 @@ This is used for instance for read security checking in the repository. """ +from __future__ import with_statement __docformat__ = "restructuredtext en" from rql import nodes as n, stmts, TypeResolverException from rql.utils import common_parent + from yams import BadSchemaDefinition + +from logilab.common import tempattr from logilab.common.graph import has_path from cubicweb import Unauthorized, typed_eid @@ -156,7 +160,6 @@ self.exists_snippet = {} self.pending_keys = [] self.existingvars = existingvars - self._insert_scope = None # we have to annotate the rqlst before inserting snippets, even though # we'll have to redo it latter self.annotate(select) @@ -193,6 +196,7 @@ self.varmap = varmap self.revvarmap = {} self.varinfos = [] + self._insert_scope = None for i, (selectvar, snippetvar) in enumerate(varmap): assert snippetvar in 'SOX' self.revvarmap[snippetvar] = (selectvar, i) @@ -229,7 +233,7 @@ except Unsupported: continue inserted = True - if new is not None: + if new is not None and self._insert_scope is None: self.exists_snippet[rqlexpr] = new parent = parent or new else: @@ -263,11 +267,12 @@ insert_scope = common_parent(scope, insert_scope) else: insert_scope = self._insert_scope - if any(vi.get('stinfo', {}).get('optrelations') for vi in self.varinfos): + if self._insert_scope is None and any(vi.get('stinfo', {}).get('optrelations') + for vi in self.varinfos): assert parent is None self._insert_scope = self.snippet_subquery(varmap, new) self.insert_pending() - self._insert_scope = None + #self._insert_scope = None return if not isinstance(new, (n.Exists, n.Not)): new = n.Exists(new) @@ -283,15 +288,14 @@ except Unsupported: # some solutions have been lost, can't apply this rql expr if parent is None: - self.select.remove_node(new, undefine=True) + self.current_statement().remove_node(new, undefine=True) else: parent.parent.replace(or_, or_.children[0]) self._cleanup_inserted(new) raise else: - self._insert_scope = new - self.insert_pending() - self._insert_scope = None + with tempattr(self, '_insert_scope', new): + self.insert_pending() return new self.insert_pending() @@ -303,6 +307,7 @@ recomputed, we have to insert snippet defined for of entity types taken by X """ + stmt = self.current_statement() while self.pending_keys: key, action = self.pending_keys.pop() try: @@ -313,12 +318,12 @@ except KeyError: # variable isn't used anywhere else, we can't insert security raise Unauthorized() - ptypes = self.select.defined_vars[varname].stinfo['possibletypes'] + ptypes = stmt.defined_vars[varname].stinfo['possibletypes'] if len(ptypes) > 1: # XXX dunno how to handle this self.session.error( 'cant check security of %s, ambigous type for %s in %s', - self.select, varname, key[0]) # key[0] == the rql expression + stmt, varname, key[0]) # key[0] == the rql expression raise Unauthorized() etype = iter(ptypes).next() eschema = self.schema.eschema(etype) @@ -462,6 +467,7 @@ original query, return that relation node """ rschema = self.schema.rschema(sniprel.r_type) + stmt = self.current_statement() for vi in self.varinfos: try: if target == 'object': @@ -477,6 +483,9 @@ except KeyError: # may be raised by vi['xhs_rels'][sniprel.r_type] return None + # don't share if relation's statement is not the current statement + if orel.stmt is not stmt: + return None # can't share neged relation or relations with different outer join if (orel.neged(strict=True) or sniprel.neged(strict=True) or (orel.optional and orel.optional != sniprel.optional)): @@ -493,23 +502,25 @@ def _use_orig_term(self, snippet_varname, term): key = (self.current_expr, self.varmap, snippet_varname) if key in self.rewritten: - insertedvar = self.select.defined_vars.pop(self.rewritten[key]) + stmt = self.current_statement() + insertedvar = stmt.defined_vars.pop(self.rewritten[key]) for inserted_vref in insertedvar.references(): - inserted_vref.parent.replace(inserted_vref, term.copy(self.select)) + inserted_vref.parent.replace(inserted_vref, term.copy(stmt)) self.rewritten[key] = term.name def _get_varname_or_term(self, vname): + stmt = self.current_statement() if vname == 'U': + stmt = self.select if self.u_varname is None: - select = self.select - self.u_varname = select.allocate_varname() + self.u_varname = stmt.allocate_varname() # generate an identifier for the substitution - argname = select.allocate_varname() + argname = stmt.allocate_varname() while argname in self.kwargs: - argname = select.allocate_varname() + argname = stmt.allocate_varname() # insert "U eid %(u)s" - select.add_constant_restriction( - select.get_variable(self.u_varname), + stmt.add_constant_restriction( + stmt.get_variable(self.u_varname), 'eid', unicode(argname), 'Substitute') self.kwargs[argname] = self.session.user.eid return self.u_varname @@ -517,7 +528,7 @@ try: return self.rewritten[key] except KeyError: - self.rewritten[key] = newvname = self.select.allocate_varname() + self.rewritten[key] = newvname = stmt.allocate_varname() return newvname # visitor methods ########################################################## @@ -625,14 +636,20 @@ def visit_variableref(self, node): """get the sql name for a variable reference""" + stmt = self.current_statement() if node.name in self.revvarmap: selectvar, index = self.revvarmap[node.name] vi = self.varinfos[index] if vi.get('const') is not None: return n.Constant(vi['const'], 'Int') # XXX gae - return n.VariableRef(self.select.get_variable(selectvar)) + return n.VariableRef(stmt.get_variable(selectvar)) vname_or_term = self._get_varname_or_term(node.name) if isinstance(vname_or_term, basestring): - return n.VariableRef(self.select.get_variable(vname_or_term)) + return n.VariableRef(stmt.get_variable(vname_or_term)) # shared term - return vname_or_term.copy(self.select) + return vname_or_term.copy(stmt) + + def current_statement(self): + if self._insert_scope is None: + return self.select + return self._insert_scope.stmt diff -r dc319ece0bd6 -r cc3987eb793c rset.py --- a/rset.py Mon May 16 16:24:00 2011 +0200 +++ b/rset.py Wed Jul 20 18:21:47 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. @@ -79,7 +79,7 @@ rows = rows[:10] + ['...'] if len(rows) > 1: # add a line break before first entity if more that one. - pattern = '' + pattern = '' else: pattern = '' @@ -515,17 +515,15 @@ @cached def syntax_tree(self): - """get the syntax tree for the source query. - - :rtype: rql.stmts.Statement - :return: the RQL syntax tree of the originating query + """return the syntax tree (:class:`rql.stmts.Union`) for the originating + query. You can expect it to have solutions computed but it won't be + annotated (you usually don't need that for simple introspection). """ if self._rqlst: rqlst = self._rqlst.copy() # to avoid transport overhead when pyro is used, the schema has been # unset from the syntax tree rqlst.schema = self.req.vreg.schema - self.req.vreg.rqlhelper.annotate(rqlst) else: rqlst = self.req.vreg.parse(self.req, self.rql, self.args) return rqlst @@ -673,8 +671,12 @@ root = rootselect.parent selectmain = select.selection[selectidx] for i, term in enumerate(rootselect.selection): - rootvar = _get_variable(term) - if rootvar is None: + try: + # don't use _get_variable here: if the term isn't a variable + # (function...), we don't want it to be used as an entity attribute + # or relation's value (XXX beside MAX/MIN trick?) + rootvar = term.variable + except AttributeError: continue if rootvar.name == rootmainvar.name: continue diff -r dc319ece0bd6 -r cc3987eb793c schema.py --- a/schema.py Mon May 16 16:24:00 2011 +0200 +++ b/schema.py Wed Jul 20 18:21:47 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): @@ -406,11 +378,27 @@ def add_subject_relation(self, rschema): """register the relation schema as possible subject relation""" super(CubicWebEntitySchema, self).add_subject_relation(rschema) - self._update_has_text() + if rschema.final: + if self.rdef(rschema).get('fulltextindexed'): + self._update_has_text() + elif rschema.fulltext_container: + self._update_has_text() + + def add_object_relation(self, rschema): + """register the relation schema as possible object relation""" + super(CubicWebEntitySchema, self).add_object_relation(rschema) + if rschema.fulltext_container: + self._update_has_text() def del_subject_relation(self, rtype): super(CubicWebEntitySchema, self).del_subject_relation(rtype) - self._update_has_text(True) + if 'has_text' in self.subjrels: + self._update_has_text(deletion=True) + + def del_object_relation(self, rtype): + super(CubicWebEntitySchema, self).del_object_relation(rtype) + if 'has_text' in self.subjrels: + self._update_has_text(deletion=True) def _update_has_text(self, deletion=False): may_need_has_text, has_has_text = False, False @@ -640,175 +628,58 @@ 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 noparen1 in expr.split('('): + for noparen2 in noparen1.split(')'): + for word in noparen2.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 +703,8 @@ def __setstate__(self, state): self.__init__(*state) + # permission rql expression specific stuff ################################# + @cached def transform_has_permission(self): found = None @@ -942,12 +815,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 +895,156 @@ 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} + if 'U' in self.rqlst.defined_vars: + expression = 'U eid %(u)s, ' + expression + args['u'] = session.user.eid + 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 cc3987eb793c selectors.py --- a/selectors.py Mon May 16 16:24:00 2011 +0200 +++ b/selectors.py Wed Jul 20 18:21:47 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,49 @@ ','.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 +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`. - Especially useful to match passed transition to enable notifications when - your workflow allows several transition to the same states. + 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 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...). - 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 + 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 ######################################################## @@ -1317,6 +1345,8 @@ @lltrace def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): + if not getattr(req, 'cnx', True): # default to True for repo session instances + return 0 user = req.user if user is None: return int('guests' in self.expected) @@ -1504,23 +1534,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 +1557,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 cc3987eb793c server/__init__.py --- a/server/__init__.py Mon May 16 16:24:00 2011 +0200 +++ b/server/__init__.py Wed Jul 20 18:21:47 2011 +0200 @@ -18,7 +18,7 @@ """Server subcube of cubicweb : defines objects used only on the server (repository) side -This module contains functions to initialize a new repository. +The server module contains functions to initialize a new repository. """ from __future__ import with_statement @@ -36,17 +36,32 @@ from cubicweb import CW_SOFTWARE_ROOT +class ShuttingDown(BaseException): + """raised when trying to access some resources while the repository is + shutting down. Inherit from BaseException so that `except Exception` won't + catch it. + """ + # server-side debugging ######################################################### # server debugging flags. They may be combined using binary operators. -DBG_NONE = 0 # no debug information -DBG_RQL = 1 # rql execution information -DBG_SQL = 2 # executed sql -DBG_REPO = 4 # repository events -DBG_MS = 8 # multi-sources -DBG_MORE = 16 # more verbosity -DBG_ALL = 1 + 2 + 4 + 8 + 16 -# current debug mode + +#:no debug information +DBG_NONE = 0 #: no debug information +#: rql execution information +DBG_RQL = 1 +#: executed sql +DBG_SQL = 2 +#: repository events +DBG_REPO = 4 +#: multi-sources +DBG_MS = 8 +#: more verbosity +DBG_MORE = 16 +#: all level enabled +DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_MORE + +#: current debug mode DEBUG = 0 def set_debug(debugmode): diff -r dc319ece0bd6 -r cc3987eb793c server/checkintegrity.py --- a/server/checkintegrity.py Mon May 16 16:24:00 2011 +0200 +++ b/server/checkintegrity.py Wed Jul 20 18:21:47 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. @@ -30,7 +30,7 @@ from logilab.common.shellutils import ProgressBar -from cubicweb.schema import PURE_VIRTUAL_RTYPES +from cubicweb.schema import PURE_VIRTUAL_RTYPES, VIRTUAL_RTYPES from cubicweb.server.sqlutils import SQL_PREFIX from cubicweb.server.session import security_enabled diff -r dc319ece0bd6 -r cc3987eb793c server/hook.py --- a/server/hook.py Mon May 16 16:24:00 2011 +0200 +++ b/server/hook.py Wed Jul 20 18:21:47 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): @@ -841,6 +852,13 @@ def _build_container(self): return self.containercls() + def union(self, data): + """only when container is a set""" + assert not self._processed, """Trying to add data to a closed operation. +Iterating over operation data closed it and should be reserved to precommit / +postcommit method of the operation.""" + self._container |= data + def add_data(self, data): assert not self._processed, """Trying to add data to a closed operation. Iterating over operation data closed it and should be reserved to precommit / diff -r dc319ece0bd6 -r cc3987eb793c server/migractions.py --- a/server/migractions.py Mon May 16 16:24:00 2011 +0200 +++ b/server/migractions.py Wed Jul 20 18:21:47 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, @@ -723,9 +737,15 @@ `attrname` is a string giving the name of the attribute to drop """ - rschema = self.repo.schema.rschema(attrname) - attrtype = rschema.objects(etype)[0] - self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit) + try: + rschema = self.repo.schema.rschema(attrname) + attrtype = rschema.objects(etype)[0] + except KeyError: + print 'warning: attribute %s %s is not known, skip deletion' % ( + etype, attrname) + else: + self.cmd_drop_relation_definition(etype, attrname, attrtype, + commit=commit) def cmd_rename_attribute(self, etype, oldname, newname, commit=True): """rename an existing attribute of the given entity type @@ -757,9 +777,10 @@ targeted type is known """ instschema = self.repo.schema - assert not etype in instschema, \ - '%s already defined in the instance schema' % etype eschema = self.fs_schema.eschema(etype) + if etype in instschema and not (eschema.final and eschema.eid is None): + print 'warning: %s already known, skip addition' % etype + return confirm = self.verbosity >= 2 groupmap = self.group_mapping() cstrtypemap = self.cstrtype_mapping() @@ -930,23 +951,19 @@ # triggered by schema synchronization hooks. session = self.session for rdeftype in ('CWRelation', 'CWAttribute'): - thispending = set() - for eid, in self.sqlexec('SELECT cw_eid FROM cw_%s ' - 'WHERE cw_from_entity=%%(eid)s OR ' - ' cw_to_entity=%%(eid)s' % rdeftype, - {'eid': oldeid}, ask_confirm=False): - # we should add deleted eids into pending eids else we may - # get some validation error on commit since integrity hooks - # may think some required relation is missing... This also ensure - # repository caches are properly cleanup - hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(eid) - # and don't forget to remove record from system tables - self.repo.system_source.delete_info( - session, session.entity_from_eid(eid, rdeftype), - 'system', None) - thispending.add(eid) - self.sqlexec('DELETE FROM cw_%s ' - 'WHERE cw_from_entity=%%(eid)s OR ' + thispending = set( (eid for eid, in self.sqlexec( + 'SELECT cw_eid FROM cw_%s WHERE cw_from_entity=%%(eid)s OR ' + ' cw_to_entity=%%(eid)s' % rdeftype, + {'eid': oldeid}, ask_confirm=False)) ) + # we should add deleted eids into pending eids else we may + # get some validation error on commit since integrity hooks + # may think some required relation is missing... This also ensure + # repository caches are properly cleanup + hook.CleanupDeletedEidsCacheOp.get_instance(session).union(thispending) + # and don't forget to remove record from system tables + entities = [session.entity_from_eid(eid, rdeftype) for eid in thispending] + self.repo.system_source.delete_info_multi(session, entities, 'system') + self.sqlexec('DELETE FROM cw_%s WHERE cw_from_entity=%%(eid)s OR ' 'cw_to_entity=%%(eid)s' % rdeftype, {'eid': oldeid}, ask_confirm=False) # now we have to manually cleanup relations pointing to deleted @@ -991,6 +1008,10 @@ """ reposchema = self.repo.schema + if rtype in reposchema: + print 'warning: relation type %s is already known, skip addition' % ( + rtype) + return rschema = self.fs_schema.rschema(rtype) execute = self._cw.execute # register the relation into CWRType and insert necessary relation @@ -1057,6 +1078,10 @@ rschema = self.fs_schema.rschema(rtype) if not rtype in self.repo.schema: self.cmd_add_relation_type(rtype, addrdef=False, commit=True) + if (subjtype, objtype) in self.repo.schema.rschema(rtype).rdefs: + print 'warning: relation %s %s %s is already known, skip addition' % ( + subjtype, rtype, objtype) + return execute = self._cw.execute rdef = self._get_rdef(rschema, subjtype, objtype) ss.execschemarql(execute, rdef, @@ -1127,6 +1152,10 @@ syncprops=syncprops) else: for etype in self.repo.schema.entities(): + if etype.eid is None: + # not yet added final etype (thing to BigInt defined in + # yams though 3.13 migration not done yet) + continue self._synchronize_eschema(etype, syncrdefs=syncrdefs, syncprops=syncprops, syncperms=syncperms) if commit: @@ -1248,6 +1277,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)): @@ -1404,7 +1439,7 @@ return self.cmd_create_entity(etype, *args, **kwargs).eid @contextmanager - def cmd_dropped_constraints(self, etype, attrname, cstrtype, + def cmd_dropped_constraints(self, etype, attrname, cstrtype=None, droprequired=False): """context manager to drop constraints temporarily on fs_schema @@ -1424,8 +1459,9 @@ rdef = self.fs_schema.eschema(etype).rdef(attrname) original_constraints = rdef.constraints # remove constraints - rdef.constraints = [cstr for cstr in original_constraints - if not (cstrtype and isinstance(cstr, cstrtype))] + if cstrtype: + rdef.constraints = [cstr for cstr in original_constraints + if not (cstrtype and isinstance(cstr, cstrtype))] if droprequired: original_cardinality = rdef.cardinality rdef.cardinality = '?' + rdef.cardinality[1] @@ -1495,13 +1531,13 @@ rschema = self.repo.schema.rschema(attr) oldtype = rschema.objects(etype)[0] rdefeid = rschema.rproperty(etype, oldtype, 'eid') - sql = ("UPDATE CWAttribute " - "SET to_entity=(SELECT eid FROM CWEType WHERE name='%s')" - "WHERE eid=%s") % (newtype, rdefeid) + sql = ("UPDATE cw_CWAttribute " + "SET cw_to_entity=(SELECT cw_eid FROM cw_CWEType WHERE cw_name='%s')" + "WHERE cw_eid=%s") % (newtype, rdefeid) self.sqlexec(sql, ask_confirm=False) dbhelper = self.repo.system_source.dbhelper sqltype = dbhelper.TYPE_MAPPING[newtype] - sql = 'ALTER TABLE %s ALTER COLUMN %s TYPE %s' % (etype, attr, sqltype) + sql = 'ALTER TABLE cw_%s ALTER COLUMN cw_%s TYPE %s' % (etype, attr, sqltype) self.sqlexec(sql, ask_confirm=False) if commit: self.commit() diff -r dc319ece0bd6 -r cc3987eb793c server/msplanner.py --- a/server/msplanner.py Mon May 16 16:24:00 2011 +0200 +++ b/server/msplanner.py Wed Jul 20 18:21:47 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 cc3987eb793c server/mssteps.py --- a/server/mssteps.py Mon May 16 16:24:00 2011 +0200 +++ b/server/mssteps.py Wed Jul 20 18:21:47 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. @@ -22,6 +22,7 @@ * each step has is own members (this is not necessarily bad, but a bit messy for now) """ +from __future__ import with_statement __docformat__ = "restructuredtext en" @@ -32,25 +33,30 @@ AGGR_TRANSFORMS = {'COUNT':'SUM', 'MIN':'MIN', 'MAX':'MAX', 'SUM': 'SUM'} -def remove_clauses(union, keepgroup): - clauses = [] - for select in union.children: - if keepgroup: - having, orderby = select.having, select.orderby - select.having, select.orderby = (), () - clauses.append( (having, orderby) ) - else: - groupby, having, orderby = select.groupby, select.having, select.orderby - select.groupby, select.having, select.orderby = (), (), () - clauses.append( (groupby, having, orderby) ) - return clauses +class remove_and_restore_clauses(object): + def __init__(self, union, keepgroup): + self.union = union + self.keepgroup = keepgroup + self.clauses = None -def restore_clauses(union, keepgroup, clauses): - for i, select in enumerate(union.children): - if keepgroup: - select.having, select.orderby = clauses[i] - else: - select.groupby, select.having, select.orderby = clauses[i] + def __enter__(self): + self.clauses = clauses = [] + for select in self.union.children: + if self.keepgroup: + having, orderby = select.having, select.orderby + select.having, select.orderby = (), () + clauses.append( (having, orderby) ) + else: + groupby, having, orderby = select.groupby, select.having, select.orderby + select.groupby, select.having, select.orderby = (), (), () + clauses.append( (groupby, having, orderby) ) + + def __exit__(self, exctype, exc, traceback): + for i, select in enumerate(self.union.children): + if self.keepgroup: + select.having, select.orderby = self.clauses[i] + else: + select.groupby, select.having, select.orderby = self.clauses[i] class FetchStep(OneFetchStep): @@ -94,29 +100,24 @@ plan = self.plan plan.create_temp_table(self.table) union = self.union - # XXX 2.5 use "with" - clauses = remove_clauses(union, self.keepgroup) - for source in self.sources: - source.flying_insert(self.table, plan.session, union, plan.args, - self.inputmap) - restore_clauses(union, self.keepgroup, clauses) + with remove_and_restore_clauses(union, self.keepgroup): + for source in self.sources: + source.flying_insert(self.table, plan.session, union, plan.args, + self.inputmap) def mytest_repr(self): """return a representation of this step suitable for test""" - clauses = remove_clauses(self.union, self.keepgroup) - try: - inputmap = varmap_test_repr(self.inputmap, self.plan.tablesinorder) - outputmap = varmap_test_repr(self.outputmap, self.plan.tablesinorder) - except AttributeError: - inputmap = self.inputmap - outputmap = self.outputmap - try: + with remove_and_restore_clauses(self.union, self.keepgroup): + try: + inputmap = varmap_test_repr(self.inputmap, self.plan.tablesinorder) + outputmap = varmap_test_repr(self.outputmap, self.plan.tablesinorder) + except AttributeError: + inputmap = self.inputmap + outputmap = self.outputmap return (self.__class__.__name__, - sorted((r.as_string(kwargs=self.plan.args), r.solutions) - for r in self.union.children), - sorted(self.sources), inputmap, outputmap) - finally: - restore_clauses(self.union, self.keepgroup, clauses) + sorted((r.as_string(kwargs=self.plan.args), r.solutions) + for r in self.union.children), + sorted(self.sources), inputmap, outputmap) class AggrStep(LimitOffsetMixIn, Step): diff -r dc319ece0bd6 -r cc3987eb793c server/querier.py --- a/server/querier.py Mon May 16 16:24:00 2011 +0200 +++ b/server/querier.py Wed Jul 20 18:21:47 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 cc3987eb793c server/repository.py --- a/server/repository.py Mon May 16 16:24:00 2011 +0200 +++ b/server/repository.py Wed Jul 20 18:21:47 2011 +0200 @@ -34,6 +34,7 @@ import sys import threading import Queue +from warnings import warn from itertools import chain from os.path import join from datetime import datetime @@ -54,18 +55,23 @@ BadConnectionId, Unauthorized, ValidationError, RepositoryError, UniqueTogetherError, typed_eid, onevent) from cubicweb import cwvreg, schema, server -from cubicweb.server import utils, hook, pool, querier, sources +from cubicweb.server import ShuttingDown, utils, hook, pool, querier, sources from cubicweb.server.session import Session, InternalSession, InternalManager, \ 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,13 +80,9 @@ 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 - for attr, value in relations: - session.update_rel_cache_add(entity.eid, attr, value) - del_existing_rel_if_needed(session, entity.eid, attr, value) def del_existing_rel_if_needed(session, eidfrom, rtype, eidto): """delete existing relation when adding a new one if card is 1 or ? @@ -91,14 +93,11 @@ this kind of behaviour has to be done in the repository so we don't have hooks order hazardness """ - # skip that for internal session or if integrity explicitly disabled - # - # XXX we should imo rely on the orm to first fetch existing entity if any - # then delete it. - if session.is_internal_session \ - or not session.is_hook_category_activated('activeintegrity'): + # skip that if integrity explicitly disabled + if not session.is_hook_category_activated('activeintegrity'): return - card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') + rdef = session.rtype_eids_rdef(rtype, eidfrom, eidto) + card = rdef.cardinality # one may be tented to check for neweids but this may cause more than one # relation even with '1?' cardinality if thoses relations are added in the # same transaction where the entity is being created. This never occurs from @@ -110,7 +109,7 @@ # * inlined relations will be implicitly deleted for the subject entity # * we don't want read permissions to be applied but we want delete # permission to be checked - if card[0] in '1?' and not session.repo.schema.rschema(rtype).inlined: + if card[0] in '1?': with security_enabled(session, read=False): session.execute('DELETE X %s Y WHERE X eid %%(x)s, ' 'NOT Y eid %%(y)s' % rtype, @@ -178,8 +177,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 @@ -232,6 +231,7 @@ self.sources_by_eid = {} if self.config.quick_start \ or not 'CWSource' in self.schema: # # 3.10 migration + self.system_source.init_creating() return session = self.internal_session() try: @@ -742,7 +742,7 @@ args[0] = ex.entity ex.args = tuple(args) raise - except: + except Exception: # FIXME: check error to catch internal errors self.exception('unexpected error while executing %s with %s', rqlstring, args) raise @@ -940,7 +940,7 @@ checkshuttingdown=True): """return the user associated to the given session identifier""" if checkshuttingdown and self.shutting_down: - raise Exception('Repository is shutting down') + raise ShuttingDown('Repository is shutting down') try: session = self._sessions[sessionid] except KeyError: @@ -1092,17 +1092,6 @@ hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid) self._delete_info(session, entity, sourceuri, extid, scleanup) - def delete_info_multi(self, session, entities, sourceuri, extids, scleanup=None): - """same as delete_info but accepts a list of entities and - extids with the same etype and belonging to the same source - """ - # mark eid as being deleted in session info and setup cache update - # operation - op = hook.CleanupDeletedEidsCacheOp.get_instance(session) - for entity in entities: - op.add_data(entity.eid) - self._delete_info_multi(session, entities, sourceuri, extids, scleanup) - def _delete_info(self, session, entity, sourceuri, extid, scleanup=None): """delete system information on deletion of an entity: * delete all remaining relations from/to this entity @@ -1131,19 +1120,18 @@ try: session.execute(rql, {'x': eid, 'seid': scleanup}, build_descr=False) - except: + except Exception: self.exception('error while cascading delete for entity %s ' 'from %s. RQL: %s', entity, sourceuri, rql) - self.system_source.delete_info(session, entity, sourceuri, extid) + self.system_source.delete_info_multi(session, [entity], sourceuri) - def _delete_info_multi(self, session, entities, sourceuri, extids, scleanup=None): + def _delete_info_multi(self, session, entities, sourceuri, scleanup=None): """same as _delete_info but accepts a list of entities with the same etype and belinging to the same source. """ pendingrtypes = session.transaction_data.get('pendingrtypes', ()) # delete remaining relations: if user can delete the entity, he can # delete all its relations without security checking - assert entities and len(entities) == len(extids) with security_enabled(session, read=False, write=False): eids = [_e.eid for _e in entities] in_eids = ','.join((str(eid) for eid in eids)) @@ -1162,10 +1150,10 @@ rql += ', NOT (Y cw_source S, S eid %(seid)s)' try: session.execute(rql, {'seid': scleanup}, build_descr=False) - except: + except Exception: self.exception('error while cascading delete for entity %s ' 'from %s. RQL: %s', entities, sourceuri, rql) - self.system_source.delete_info_multi(session, entities, sourceuri, extids) + self.system_source.delete_info_multi(session, entities, sourceuri) def locate_relation_source(self, session, subject, rtype, object): subjsource = self.source_from_eid(subject, session) @@ -1229,16 +1217,24 @@ if server.DEBUG & server.DBG_REPO: print 'ADD entity', self, entity.__regid__, entity.eid, edited relations = [] + prefill_entity_caches(entity, relations) if source.should_call_hooks: self.hm.call_hooks('before_add_entity', session, entity=entity) + activintegrity = session.is_hook_category_activated('activeintegrity') for attr in edited.iterkeys(): rschema = eschema.subjrels[attr] if not rschema.final: # inlined relation - relations.append((attr, edited[attr])) + value = edited[attr] + relations.append((attr, value)) + session.update_rel_cache_add(entity.eid, attr, value) + rdef = session.rtype_eids_rdef(attr, entity.eid, value) + if rdef.cardinality[1] in '1?' and activintegrity: + with security_enabled(session, read=False): + session.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr, + {'x': entity.eid, 'y': value}) edited.set_defaults() if session.is_hook_category_activated('integrity'): edited.check(creation=True) - prefill_entity_caches(entity, relations) try: source.add_entity(session, entity) except UniqueTogetherError, exc: @@ -1340,6 +1336,16 @@ def glob_delete_entities(self, session, eids): """delete a list of entities and all related entities from the repository""" + # mark eids as being deleted in session info and setup cache update + # operation (register pending eids before actual deletion to avoid + # multiple call to glob_delete_entities) + op = hook.CleanupDeletedEidsCacheOp.get_instance(session) + if not isinstance(eids, (set, frozenset)): + warn('[3.13] eids should be given as a set', DeprecationWarning, + stacklevel=2) + eids = frozenset(eids) + eids = eids - op._container + op._container |= eids data_by_etype_source = {} # values are ([list of eids], # [list of extid], # [list of entities]) @@ -1351,43 +1357,95 @@ for eid in eids: etype, sourceuri, extid = self.type_and_source_from_eid(eid, session) + # XXX should cache entity's cw_metainformation entity = session.entity_from_eid(eid, etype) - _key = (etype, sourceuri) - if _key not in data_by_etype_source: - data_by_etype_source[_key] = ([eid], [extid], [entity]) - else: - _data = data_by_etype_source[_key] - _data[0].append(eid) - _data[1].append(extid) - _data[2].append(entity) - for (etype, sourceuri), (eids, extids, entities) in data_by_etype_source.iteritems(): + try: + data_by_etype_source[(etype, sourceuri)].append(entity) + except KeyError: + data_by_etype_source[(etype, sourceuri)] = [entity] + for (etype, sourceuri), entities in data_by_etype_source.iteritems(): if server.DEBUG & server.DBG_REPO: - print 'DELETE entities', etype, eids - #print 'DELETE entities', etype, len(eids) + print 'DELETE entities', etype, [entity.eid for entity in entities] source = self.sources_by_uri[sourceuri] if source.should_call_hooks: self.hm.call_hooks('before_delete_entity', session, entities=entities) - self._delete_info_multi(session, entities, sourceuri, extids) # xxx + self._delete_info_multi(session, entities, sourceuri) source.delete_entities(session, entities) if source.should_call_hooks: self.hm.call_hooks('after_delete_entity', session, entities=entities) - # don't clear cache here this is done in a hook on commit + # don't clear cache here, it is done in a hook on commit 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 = {} + subjects_by_types = {} + objects_by_types = {} + activintegrity = session.is_hook_category_activated('activeintegrity') + for rtype, eids_subj_obj in relations.iteritems(): + if server.DEBUG & server.DBG_REPO: + for subjeid, objeid in relations: + print 'ADD relation', subjeid, rtype, objeid + for subjeid, objeid in eids_subj_obj: + source = self.locate_relation_source(session, subjeid, rtype, objeid) + 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((subjeid, objeid)) + else: + relations_by_rtype[rtype] = [(subjeid, objeid)] + if not activintegrity: + continue + # take care to relation of cardinality '?1', as all eids will + # be inserted later, we've remove duplicated eids since they + # won't be catched by `del_existing_rel_if_needed` + rdef = session.rtype_eids_rdef(rtype, subjeid, objeid) + card = rdef.cardinality + if card[0] in '?1': + with security_enabled(session, read=False): + session.execute('DELETE X %s Y WHERE X eid %%(x)s, ' + 'NOT Y eid %%(y)s' % rtype, + {'x': subjeid, 'y': objeid}) + subjects = subjects_by_types.setdefault(rdef, {}) + if subjeid in subjects: + del relations_by_rtype[rtype][subjects[subjeid]] + subjects[subjeid] = len(relations_by_rtype[rtype]) - 1 + continue + subjects[subjeid] = len(relations_by_rtype[rtype]) - 1 + if card[1] in '?1': + with security_enabled(session, read=False): + session.execute('DELETE X %s Y WHERE Y eid %%(y)s, ' + 'NOT X eid %%(x)s' % rtype, + {'x': subjeid, 'y': objeid}) + objects = objects_by_types.setdefault(rdef, {}) + if objeid in objects: + del relations_by_rtype[rtype][objects[objeid]] + objects[objeid] = len(relations_by_rtype[rtype]) + continue + objects[objeid] = len(relations_by_rtype[rtype]) + for source, relations_by_rtype in sources.iteritems(): + if source.should_call_hooks: + for rtype, source_relations in relations_by_rtype.iteritems(): + 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 subjeid, objeid in source_relations: + session.update_rel_cache_add(subjeid, rtype, objeid, 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 cc3987eb793c server/rqlannotation.py --- a/server/rqlannotation.py Mon May 16 16:24:00 2011 +0200 +++ b/server/rqlannotation.py Wed Jul 20 18:21:47 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: @@ -265,9 +271,17 @@ return has_text_query def is_ambiguous(self, var): - # ignore has_text relation - if len([rel for rel in var.stinfo['relations'] - if rel.scope is var.scope and rel.r_type == 'has_text']) == 1: + # ignore has_text relation when we know it will be used as principal. + # This is expected by the rql2sql generator which will use the `entities` + # table to filter out by type if necessary, This optimisation is very + # interesting in multi-sources cases, as it may avoid a costly query + # on sources to get all entities of a given type to achieve this, while + # we have all the necessary information. + root = var.stmt.root # Union node + # rel.scope -> Select or Exists node, so add .parent to get Union from + # Select node + rels = [rel for rel in var.stinfo['relations'] if rel.scope.parent is root] + if len(rels) == 1 and rels[0].r_type == 'has_text': return False try: data = var.stmt._deamb_data diff -r dc319ece0bd6 -r cc3987eb793c server/schemaserial.py --- a/server/schemaserial.py Mon May 16 16:24:00 2011 +0200 +++ b/server/schemaserial.py Wed Jul 20 18:21:47 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 cc3987eb793c server/serverctl.py --- a/server/serverctl.py Mon May 16 16:24:00 2011 +0200 +++ b/server/serverctl.py Wed Jul 20 18:21:47 2011 +0200 @@ -355,7 +355,7 @@ print '-> user %s created.' % user if dbname in helper.list_databases(cursor): if automatic or ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname): - cursor.execute('DROP DATABASE %s' % dbname) + cursor.execute('DROP DATABASE "%s"' % dbname) else: print ('you may want to run "cubicweb-ctl db-init ' '--drop %s" manually to continue.' % config.appid) @@ -560,6 +560,15 @@ """ name = 'reset-admin-pwd' arguments = '' + options = ( + ('password', + {'short': 'p', 'type' : 'string', 'metavar' : '', + 'default' : None, + 'help': 'Use this password instead of prompt for one.\n' + '/!\ THIS IS AN INSECURE PRACTICE /!\ \n' + 'the password will appear in shell history'} + ), + ) def run(self, args): """run the command with its specific arguments""" @@ -586,15 +595,18 @@ print " fix your sources file before running this command" cnx.close() sys.exit(1) - # ask for a new password - _, passwd = manager_userpasswd(adminlogin, confirm=True, - passwdmsg='new password for %s' % adminlogin) + if self.config.password is None: + # ask for a new password + msg = 'new password for %s' % adminlogin + _, pwd = manager_userpasswd(adminlogin, confirm=True, passwdmsg=msg) + else: + pwd = self.config.password try: cursor.execute("UPDATE cw_CWUser SET cw_upassword=%(p)s WHERE cw_login=%(l)s", - {'p': dbhelper.binary_value(crypt_password(passwd)), 'l': adminlogin}) + {'p': dbhelper.binary_value(crypt_password(pwd)), 'l': adminlogin}) sconfig = Configuration(options=USER_OPTIONS) sconfig['login'] = adminlogin - sconfig['password'] = passwd + sconfig['password'] = pwd sourcescfg['admin'] = sconfig config.write_sources_file(sourcescfg) except Exception, ex: @@ -691,19 +703,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 +790,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 +804,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 +832,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 +891,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 +908,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 cc3987eb793c server/session.py --- a/server/session.py Mon May 16 16:24:00 2011 +0200 +++ b/server/session.py Wed Jul 20 18:21:47 2011 +0200 @@ -33,12 +33,15 @@ 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 import ShuttingDown from cubicweb.server.edition import EditedEntity + ETYPE_PYOBJ_MAP[Binary] = 'Bytes' NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy() @@ -58,6 +61,46 @@ 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 + +@objectify_selector +def repairing(cls, req, **kwargs): + """repository side only selector returning 1 if the session is not a regular + user session but an internal session + """ + return req.vreg.config.repairing + + +class transaction(object): + """context manager to enter a transaction for a session: when exiting the + `with` block on exception, call `session.rollback()`, else call + `session.commit()` on normal exit + """ + def __init__(self, session, free_cnxset=True): + self.session = session + self.free_cnxset = free_cnxset + + def __enter__(self): + pass + + def __exit__(self, exctype, exc, traceback): + if exctype: + self.session.rollback(free_cnxset=self.free_cnxset) + else: + self.session.commit(free_cnxset=self.free_cnxset) + class hooks_control(object): """context manager to control activated hooks categories. @@ -166,11 +209,22 @@ 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)>' % ( self.cnxtype, unicode(self.user.login), self.id, id(self)) + def transaction(self, free_cnxset=True): + """return context manager to enter a transaction for the session: when + exiting the `with` block on exception, call `session.rollback()`, else + call `session.commit()` on normal exit. + + The `free_cnxset` will be given to rollback/commit methods to indicate + wether the connections set should be freed or not. + """ + return transaction(self, free_cnxset) + def set_tx_data(self, txid=None): if txid is None: txid = threading.currentThread().getName() @@ -213,14 +267,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. @@ -358,17 +432,20 @@ """ return eid in self.transaction_data.get('neweids', ()) - def schema_rproperty(self, rtype, eidfrom, eidto, rprop): - rschema = self.repo.schema[rtype] - subjtype = self.describe(eidfrom)[0] - objtype = self.describe(eidto)[0] - rdef = rschema.rdef(subjtype, objtype) - return rdef.get(rprop) + def rtype_eids_rdef(self, rtype, eidfrom, eidto): + # use type_and_source_from_eid instead of type_from_eid for optimization + # (avoid two extra methods call) + subjtype = self.repo.type_and_source_from_eid(eidfrom, self)[0] + objtype = self.repo.type_and_source_from_eid(eidto, self)[0] + return self.vreg.schema.rschema(rtype).rdefs[(subjtype, objtype)] # security control ######################################################### DEFAULT_SECURITY = object() # evaluated to true by design + def security_enabled(self, read=False, write=False): + return security_enabled(self, read=read, write=write) + @property def read_security(self): """return a boolean telling if read security is activated or not""" @@ -454,6 +531,11 @@ HOOKS_ALLOW_ALL = object() HOOKS_DENY_ALL = object() + def allow_all_hooks_but(self, *categories): + return hooks_control(self, self.HOOKS_ALLOW_ALL, *categories) + def deny_all_hooks_but(self, *categories): + return hooks_control(self, self.HOOKS_DENY_ALL, *categories) + @property def hooks_mode(self): return getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL) @@ -582,21 +664,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 +917,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(): @@ -987,6 +1071,11 @@ # deprecated ############################################################### + @deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)') + def schema_rproperty(self, rtype, eidfrom, eidto, rprop): + return getattr(self.rtype_eids_rdef(rtype, eidfrom, eidto), rprop) + + @deprecated("[3.7] execute is now unsafe by default in hooks/operation. You" " can also control security with the security_enabled context " "manager") @@ -1056,7 +1145,7 @@ """connections pool, set according to transaction mode for each query""" if self.repo.shutting_down: self.reset_pool(True) - raise Exception('repository is shutting down') + raise ShuttingDown('repository is shutting down') return getattr(self._threaddata, 'pool', None) diff -r dc319ece0bd6 -r cc3987eb793c server/sources/__init__.py --- a/server/sources/__init__.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/__init__.py Wed Jul 20 18:21:47 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() @@ -455,19 +462,12 @@ """mark entity as being modified, fulltext reindex if needed""" raise NotImplementedError() - def delete_info(self, session, entity, uri, extid): - """delete system information on deletion of an entity by transfering - record from the entities table to the deleted_entities table + def delete_info_multi(self, session, entities, uri): + """delete system information on deletion of a list of entities with the + same etype and belinging to the same source """ raise NotImplementedError() - def delete_info_multi(self, session, entities, uri, extids): - """ame as delete_info but accepts a list of entities with - the same etype and belinging to the same source. - """ - for entity, extid in itertools.izip(entities, extids): - self.delete_info(session, entity, uri, extid) - def modified_entities(self, session, etypes, mtime): """return a 2-uple: * list of (etype, eid) of entities of the given types which have been diff -r dc319ece0bd6 -r cc3987eb793c server/sources/datafeed.py --- a/server/sources/datafeed.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/datafeed.py Wed Jul 20 18:21:47 2011 +0200 @@ -57,7 +57,8 @@ ) def __init__(self, repo, source_config, eid=None): AbstractSource.__init__(self, repo, source_config, eid) - self.update_config(None, self.check_conf_dict(eid, source_config)) + self.update_config(None, self.check_conf_dict(eid, source_config, + fail_if_unknown=False)) def check_config(self, source_entity): """check configuration of source entity""" @@ -118,9 +119,9 @@ def fresh(self): if self.latest_retrieval is None: return False - return datetime.now() < (self.latest_retrieval + self.synchro_interval) + return datetime.utcnow() < (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 +136,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 @@ -148,7 +151,7 @@ for etype, eids in byetype.iteritems(): session.execute('DELETE %s X WHERE X eid IN (%s)' % (etype, ','.join(eids))) - self.latest_retrieval = datetime.now() + self.latest_retrieval = datetime.utcnow() session.execute('SET X latest_retrieval %(date)s WHERE X eid %(x)s', {'x': self.eid, 'date': self.latest_retrieval}) return parser.stats diff -r dc319ece0bd6 -r cc3987eb793c server/sources/native.py --- a/server/sources/native.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/native.py Wed Jul 20 18:21:47 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 @@ -157,6 +165,15 @@ class UndoException(Exception): """something went wrong during undoing""" + def __unicode__(self): + """Called by the unicode builtin; should return a Unicode object + + Type of UndoException message must be `unicode` by design in CubicWeb. + + .. warning:: + This method is not available in python2.5""" + assert isinstance(self.message, unicode) + return self.message def _undo_check_relation_target(tentity, rdef, role): """check linked entity has not been redirected for this relation""" @@ -354,24 +371,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 +665,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""" @@ -690,7 +747,7 @@ session.pool.connection(self.uri).rollback() if self.repo.config.mode != 'test': self.critical('transaction has been rollbacked') - except: + except Exception, ex: pass if ex.__class__.__name__ == 'IntegrityError': # need string comparison because of various backends @@ -922,31 +979,13 @@ attrs = {'eid': entity.eid, 'mtime': datetime.now()} self.doexec(session, self.sqlgen.update('entities', attrs, ['eid']), attrs) - def delete_info(self, session, entity, uri, extid): - """delete system information on deletion of an entity: + def delete_info_multi(self, session, entities, uri): + """delete system information on deletion of a list of entities with the + same etype and belinging to the same source + * update the fti - * remove record from the entities table - * transfer it to the deleted_entities table if the entity's type is - multi-sources - """ - self.fti_unindex_entities(session, [entity]) - attrs = {'eid': entity.eid} - self.doexec(session, self.sqlgen.delete('entities', attrs), attrs) - if not entity.__regid__ in self.multisources_etypes: - return - if extid is not None: - assert isinstance(extid, str), type(extid) - extid = b64encode(extid) - attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid, - 'source': uri, 'dtime': datetime.now()} - self.doexec(session, self.sqlgen.insert('deleted_entities', attrs), attrs) - - def delete_info_multi(self, session, entities, uri, extids): - """delete system information on deletion of an entity: - * update the fti - * remove record from the entities table - * transfer it to the deleted_entities table if the entity's type is - multi-sources + * remove record from the `entities` table + * transfer it to the `deleted_entities` """ self.fti_unindex_entities(session, entities) attrs = {'eid': '(%s)' % ','.join([str(_e.eid) for _e in entities])} @@ -955,7 +994,8 @@ return attrs = {'type': entities[0].__regid__, 'source': uri, 'dtime': datetime.now()} - for entity, extid in itertools.izip(entities, extids): + for entity in entities: + extid = entity.cw_metainformation()['extid'] if extid is not None: assert isinstance(extid, str), type(extid) extid = b64encode(extid) @@ -1232,7 +1272,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, @@ -1272,7 +1312,7 @@ sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs) self.doexec(session, sql, attrs) # remove record from entities (will update fti if needed) - self.delete_info(session, entity, self.uri, None) + self.delete_info_multi(session, [entity], self.uri) self.repo.hm.call_hooks('after_delete_entity', session, entity=entity) return () @@ -1544,3 +1584,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 cc3987eb793c server/sources/pyrorql.py --- a/server/sources/pyrorql.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/pyrorql.py Wed Jul 20 18:21:47 2011 +0200 @@ -234,6 +234,7 @@ etype, dexturi, dextid = cnx.describe(extid) if dexturi == 'system' or not ( dexturi in self.repo.sources_by_uri or self._skip_externals): + assert etype in self.support_entities, etype return self.repo.extid2eid(self, str(extid), etype, session), True if dexturi in self.repo.sources_by_uri: source = self.repo.sources_by_uri[dexturi] @@ -312,9 +313,8 @@ def get_connection(self): try: return self._get_connection() - except (ConnectionError, PyroError): - self.critical("can't get connection to source %s", self.uri, - exc_info=1) + except (ConnectionError, PyroError), ex: + self.critical("can't get connection to source %s: %s", self.uri, ex) return ConnectionWrapper() def check_connection(self, cnx): diff -r dc319ece0bd6 -r cc3987eb793c server/sources/rql2sql.py --- a/server/sources/rql2sql.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/rql2sql.py Wed Jul 20 18:21:47 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 @@ -70,7 +72,9 @@ stack.append(self.source_execute) FunctionDescr.update_cb_stack = default_update_cb_stack -LENGTH = SQL_FUNCTIONS_REGISTRY.get_function('LENGTH') +get_func_descr = SQL_FUNCTIONS_REGISTRY.get_function + +LENGTH = get_func_descr('LENGTH') def length_source_execute(source, session, value): return len(value.getvalue()) LENGTH.source_execute = length_source_execute @@ -82,6 +86,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 +96,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 +121,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 @@ -245,47 +247,56 @@ table + '.eid_from') return switchedsql.replace('__eid_from__', table + '.eid_to') -def sort_term_selection(sorts, selectedidx, rqlst, groups): +def sort_term_selection(sorts, rqlst, groups): # XXX beurk if isinstance(rqlst, list): def append(term): rqlst.append(term) + selectionidx = set(str(term) for term in rqlst) else: def append(term): rqlst.selection.append(term.copy(rqlst)) + selectionidx = set(str(term) for term in rqlst.selection) + for sortterm in sorts: term = sortterm.term - if not isinstance(term, Constant) and not str(term) in selectedidx: - selectedidx.append(str(term)) + if not isinstance(term, Constant) and not str(term) in selectionidx: + selectionidx.add(str(term)) append(term) if groups: for vref in term.iget_nodes(VariableRef): if not vref in groups: groups.append(vref) -def fix_selection_and_group(rqlst, selectedidx, needwrap, selectsortterms, +def fix_selection_and_group(rqlst, needwrap, selectsortterms, sorts, groups, having): if selectsortterms and sorts: - sort_term_selection(sorts, selectedidx, rqlst, not needwrap and groups) + sort_term_selection(sorts, rqlst, not needwrap and groups) + groupvrefs = [vref for term in groups for vref in term.iget_nodes(VariableRef)] if sorts and groups: # when a query is grouped, ensure sort terms are grouped as well for sortterm in sorts: term = sortterm.term - if not isinstance(term, Constant): + if not (isinstance(term, Constant) or \ + (isinstance(term, Function) and + get_func_descr(term.name).aggregat)): for vref in term.iget_nodes(VariableRef): - if not vref in groups: + if not vref in groupvrefs: groups.append(vref) - if needwrap: + groupvrefs.append(vref) + if needwrap and (groups or having): + selectedidx = set(vref.name for term in rqlst.selection + for vref in term.get_nodes(VariableRef)) if groups: - for vref in groups: - if not vref.name in selectedidx: - selectedidx.append(vref.name) + for vref in groupvrefs: + if vref.name not in selectedidx: + selectedidx.add(vref.name) rqlst.selection.append(vref) if having: for term in having: for vref in term.iget_nodes(VariableRef): - if not vref.name in selectedidx: - selectedidx.append(vref.name) + if vref.name not in selectedidx: + selectedidx.add(vref.name) rqlst.selection.append(vref) def iter_mapped_var_sels(stmt, variable): @@ -308,12 +319,12 @@ break if not isinstance(node, Function): raise QueryError() - func = SQL_FUNCTIONS_REGISTRY.get_function(node.name) - if func.source_execute is None: + funcd = get_func_descr(node.name) + if funcd.source_execute is None: raise QueryError('%s can not be called on mapped attribute' % node.name) state.source_cb_funcs.add(node) - func.update_cb_stack(stack) + funcd.update_cb_stack(stack) # IGenerator implementation for RQL->SQL ####################################### @@ -588,16 +599,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 ################################################### @@ -802,23 +813,16 @@ # treat subqueries self._subqueries_sql(select, state) # generate sql for this select node - selectidx = [str(term) for term in select.selection] if needwrap: outerselection = origselection[:] if sorts and selectsortterms: - outerselectidx = [str(term) for term in outerselection] if distinct: - sort_term_selection(sorts, outerselectidx, - outerselection, groups) - else: - outerselectidx = selectidx[:] - fix_selection_and_group(select, selectidx, needwrap, - selectsortterms, sorts, groups, having) + sort_term_selection(sorts, outerselection, groups) + fix_selection_and_group(select, needwrap, selectsortterms, + sorts, groups, having) if needwrap: - fselectidx = outerselectidx fneedwrap = len(outerselection) != len(origselection) else: - fselectidx = selectidx fneedwrap = len(select.selection) != len(origselection) if fneedwrap: needalias = True @@ -850,8 +854,12 @@ # sort if sorts: sqlsortterms = [] + if needwrap: + selectidx = [str(term) for term in outerselection] + else: + selectidx = [str(term) for term in select.selection] for sortterm in sorts: - _term = self._sortterm_sql(sortterm, fselectidx) + _term = self._sortterm_sql(sortterm, selectidx) if _term is not None: sqlsortterms.append(_term) if sqlsortterms: @@ -1243,6 +1251,8 @@ return condition self._state.add_outer_join_condition(leftalias, condition) return + if leftalias is None: + leftalias = leftvar._q_sql.split('.', 1)[0] self._state.replace_tables_by_outer_join( leftalias, rightalias, outertype, '%s=%s' % (lhssql, rhs.accept(self))) return '' @@ -1254,11 +1264,16 @@ unification (eg X attr1 A, Y attr2 A). In case of selection, nothing to do here. """ - for var in rhs_vars: + for vref in rhs_vars: + var = vref.variable if var.name in self._varmap: # ensure table is added - self._var_info(var.variable) - principal = var.variable.stinfo.get('principal') + self._var_info(var) + if isinstance(var, ColumnAlias): + # force sql generation whatever the computed principal + principal = 1 + else: + principal = var.stinfo.get('principal') if principal is not None and principal is not relation: # we have to generate unification expression lhssql = self._inlined_var_sql(relation.children[0].variable, @@ -1424,6 +1439,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 cc3987eb793c server/sources/storages.py --- a/server/sources/storages.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sources/storages.py Wed Jul 20 18:21:47 2011 +0200 @@ -122,7 +122,7 @@ fpath = source.binary_to_str(value) try: return Binary(file(fpath, 'rb').read()) - except OSError, ex: + except EnvironmentError, ex: source.critical("can't open %s: %s", value, ex) return None @@ -173,13 +173,15 @@ entity.cw_edited.edited_attribute(attr, Binary(fpath)) # Mark the old file as useless so the file will be removed at # commit. - DeleteFileOp.get_instance(entity._cw).add_data(oldpath) + if oldpath is not None: + DeleteFileOp.get_instance(entity._cw).add_data(oldpath) return binary def entity_deleted(self, entity, attr): """an entity using this storage for attr has been deleted""" fpath = self.current_fs_path(entity, attr) - DeleteFileOp.get_instance(entity._cw).add_data(fpath) + if fpath is not None: + DeleteFileOp.get_instance(entity._cw).add_data(fpath) def new_fs_path(self, entity, attr): # We try to get some hint about how to name the file using attribute's @@ -199,13 +201,16 @@ return fspath def current_fs_path(self, entity, attr): + """return the current fs_path of the tribute. + + Return None is the attr is not stored yet.""" sysource = entity._cw.pool.source('system') cu = sysource.doexec(entity._cw, 'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % ( attr, entity.__regid__, entity.eid)) rawvalue = cu.fetchone()[0] if rawvalue is None: # no previous value - return self.new_fs_path(entity, attr) + return None return sysource._process_value(rawvalue, cu.description[0], binarywrap=str) diff -r dc319ece0bd6 -r cc3987eb793c server/sqlutils.py --- a/server/sqlutils.py Mon May 16 16:24:00 2011 +0200 +++ b/server/sqlutils.py Wed Jul 20 18:21:47 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 cc3987eb793c server/ssplanner.py --- a/server/ssplanner.py Mon May 16 16:24:00 2011 +0200 +++ b/server/ssplanner.py Wed Jul 20 18:21:47 2011 +0200 @@ -28,7 +28,6 @@ from cubicweb.schema import VIRTUAL_RTYPES from cubicweb.rqlrewrite import add_types_restriction from cubicweb.server.session import security_enabled -from cubicweb.server.hook import CleanupDeletedEidsCacheOp from cubicweb.server.edition import EditedEntity READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity')) @@ -521,13 +520,7 @@ if results: todelete = frozenset(typed_eid(eid) for eid, in results) session = self.plan.session - # mark eids as being deleted in session info and setup cache update - # operation (register pending eids before actual deletion to avoid - # multiple call to glob_delete_entities) - op = CleanupDeletedEidsCacheOp.get_instance(session) - actual = todelete - op._container - op._container |= actual - session.repo.glob_delete_entities(session, actual) + session.repo.glob_delete_entities(session, todelete) return results class DeleteRelationsStep(Step): @@ -559,6 +552,7 @@ session = self.plan.session repo = session.repo edefs = {} + relations = {} # insert relations if self.children: result = self.execute_child() @@ -578,9 +572,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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 cc3987eb793c server/test/data/sources_postgres --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/data/sources_postgres Wed Jul 20 18:21:47 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 cc3987eb793c 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c server/test/unittest_postgres.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/unittest_postgres.py Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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] @@ -1416,6 +1435,22 @@ def test_nonregr_final_norestr(self): self.assertRaises(BadRQLQuery, self.execute, 'Date X') + def test_nonregr_eid_cmp(self): + peid1 = self.execute("INSERT Personne X: X nom 'bidule'")[0][0] + peid2 = self.execute("INSERT Personne X: X nom 'bidule'")[0][0] + rset = self.execute('Any X,Y WHERE X is Personne, Y is Personne, X nom XD, Y nom XD, X eid Z, Y eid > Z') + self.assertEqual(rset.rows, [[peid1, peid2]]) + rset = self.execute('Any X,Y WHERE X nom XD, Y nom XD, X eid Z, Y eid > Z') + self.assertEqual(rset.rows, [[peid1, peid2]]) + + def test_nonregr_has_text_ambiguity_1(self): + peid = self.execute("INSERT CWUser X: X login 'bidule', X upassword 'bidule', X in_group G WHERE G name 'users'")[0][0] + aeid = self.execute("INSERT Affaire X: X ref 'bidule'")[0][0] + self.commit() + rset = self.execute('Any X WHERE X is CWUser, X has_text "bidule"') + self.assertEqual(rset.rows, [[peid]]) + rset = self.execute('Any X WHERE X is CWUser, X has_text "bidule", X in_state S, S name SN') + self.assertEqual(rset.rows, [[peid]]) if __name__ == '__main__': unittest_main() diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 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,183 @@ 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) + + def test_optional_relation_reset_1(self): + req = self.request() + p1 = req.create_entity('Personne', nom=u'Vincent') + p2 = req.create_entity('Personne', nom=u'Florent') + w = req.create_entity('Affaire', ref=u'wc') + w.set_relations(todo_by=[p1,p2]) + w.clear_all_caches() + self.commit() + self.assertEqual(len(w.todo_by), 1) + self.assertEqual(w.todo_by[0].eid, p2.eid) + + def test_optional_relation_reset_2(self): + req = self.request() + p1 = req.create_entity('Personne', nom=u'Vincent') + p2 = req.create_entity('Personne', nom=u'Florent') + w = req.create_entity('Affaire', ref=u'wc') + w.set_relations(todo_by=p1) + self.commit() + w.set_relations(todo_by=p2) + w.clear_all_caches() + self.commit() + self.assertEqual(len(w.todo_by), 1) + self.assertEqual(w.todo_by[0].eid, p2.eid) + + if __name__ == '__main__': unittest_main() diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 2011 +0200 @@ -210,6 +210,12 @@ FROM cw_Personne AS _P LIMIT 20 OFFSET 10'''), + ("Any P ORDERBY N LIMIT 1 WHERE P is Personne, P travaille S, S eid %(eid)s, P nom N, P nom %(text)s", + '''SELECT _P.cw_eid +FROM cw_Personne AS _P, travaille_relation AS rel_travaille0 +WHERE rel_travaille0.eid_from=_P.cw_eid AND rel_travaille0.eid_to=12345 AND _P.cw_nom=hip hop momo +ORDER BY _P.cw_nom +LIMIT 1'''), ] @@ -536,6 +542,14 @@ '''SELECT _X.cw_eid FROM cw_CWEType AS _X WHERE _X.cw_description!=parent AND _X.cw_description!=_X.cw_name'''), + + ('DISTINCT Any X, SUM(C) GROUPBY X ORDERBY SUM(C) DESC WHERE H todo_by X, H duration C', + '''SELECT DISTINCT rel_todo_by0.eid_to, SUM(_H.cw_duration) +FROM cw_Affaire AS _H, todo_by_relation AS rel_todo_by0 +WHERE rel_todo_by0.eid_from=_H.cw_eid +GROUP BY rel_todo_by0.eid_to +ORDER BY 2 DESC'''), + ] ADVANCED_WITH_GROUP_CONCAT = [ @@ -631,7 +645,12 @@ ("Personne X,Y where X nom NX, Y nom NX, X eid XE, not Y eid XE", '''SELECT _X.cw_eid, _Y.cw_eid FROM cw_Personne AS _X, cw_Personne AS _Y -WHERE _Y.cw_nom=_X.cw_nom AND NOT (_Y.cw_eid=_X.cw_eid)''') +WHERE _Y.cw_nom=_X.cw_nom AND NOT (_Y.cw_eid=_X.cw_eid)'''), + + ('Any X,Y WHERE X is Personne, Y is Personne, X nom XD, Y nom XD, X eid Z, Y eid > Z', + '''SELECT _X.cw_eid, _Y.cw_eid +FROM cw_Personne AS _X, cw_Personne AS _Y +WHERE _Y.cw_nom=_X.cw_nom AND _Y.cw_eid>_X.cw_eid'''), ] @@ -835,9 +854,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 +928,38 @@ ('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'''), + + ('Any CASE, CALIBCFG, CFG ' + 'WHERE CASE eid 1, CFG ecrit_par CASE, CALIBCFG? ecrit_par CASE', + '''SELECT _CFG.cw_ecrit_par, _CALIBCFG.cw_eid, _CFG.cw_eid +FROM cw_Note AS _CFG LEFT OUTER JOIN cw_Note AS _CALIBCFG ON (_CALIBCFG.cw_ecrit_par=_CFG.cw_ecrit_par) +WHERE _CFG.cw_ecrit_par=1'''), ] VIRTUAL_VARS = [ @@ -1201,7 +1251,7 @@ def _check(self, rql, sql, varmap=None, args=None): if args is None: - args = {'text': 'hip hop momo'} + args = {'text': 'hip hop momo', 'eid': 12345} try: union = self._prepare(rql) r, nargs, cbs = self.o.generate(union, args, @@ -1225,9 +1275,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 +1293,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 +1304,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''', {}) @@ -1411,6 +1465,12 @@ WHERE ((EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE rel_owned_by0.eid_from=_X.cw_eid AND rel_owned_by0.eid_to=1)) OR (((EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by2, cw_Affaire AS _D LEFT OUTER JOIN concerne_relation AS rel_concerne1 ON (rel_concerne1.eid_from=_D.cw_eid) LEFT OUTER JOIN cw_Note AS _B ON (rel_concerne1.eid_to=_B.cw_eid) WHERE rel_owned_by2.eid_from=_B.cw_eid AND rel_owned_by2.eid_to=1 AND _X.cw_eid=_D.cw_eid)) OR (EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by4, cw_Affaire AS _F LEFT OUTER JOIN concerne_relation AS rel_concerne3 ON (rel_concerne3.eid_from=_F.cw_eid) LEFT OUTER JOIN cw_Societe AS _E ON (rel_concerne3.eid_to=_E.cw_eid) WHERE rel_owned_by4.eid_from=_E.cw_eid AND rel_owned_by4.eid_to=1 AND _X.cw_eid=_F.cw_eid))))))) AS _T0, cw_CWEType AS _ET, is_relation AS rel_is0 WHERE rel_is0.eid_from=_T0.C0 AND rel_is0.eid_to=_ET.cw_eid GROUP BY _ET.cw_name'''), + + ('Any A WHERE A ordernum O, A is CWAttribute WITH O BEING (Any MAX(O) WHERE A ordernum O, A is CWAttribute)', + '''SELECT _A.cw_eid +FROM (SELECT MAX(_A.cw_ordernum) AS C0 +FROM cw_CWAttribute AS _A) AS _T0, cw_CWAttribute AS _A +WHERE _A.cw_ordernum=_T0.C0'''), )): yield t @@ -1576,7 +1636,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 +1646,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', @@ -1595,6 +1655,16 @@ WHERE rel_owned_by0.eid_from=_G.cw_eid AND rel_owned_by0.eid_to=1122 GROUP BY _G.cw_eid''') + def test_groupby_orderby_insertion_dont_modify_intention(self): + self._check('Any YEAR(XECT)*100+MONTH(XECT), COUNT(X),SUM(XCE),AVG(XSCT-XECT) ' + 'GROUPBY YEAR(XECT),MONTH(XECT) ORDERBY 1 ' + 'WHERE X creation_date XSCT, X modification_date XECT, ' + 'X ordernum XCE, X is CWAttribute', + '''SELECT ((CAST(EXTRACT(YEAR from _X.cw_modification_date) AS INTEGER) * 100) + CAST(EXTRACT(MONTH from _X.cw_modification_date) AS INTEGER)), COUNT(_X.cw_eid), SUM(_X.cw_ordernum), AVG((_X.cw_creation_date - _X.cw_modification_date)) +FROM cw_CWAttribute AS _X +GROUP BY CAST(EXTRACT(YEAR from _X.cw_modification_date) AS INTEGER),CAST(EXTRACT(MONTH from _X.cw_modification_date) AS INTEGER) +ORDER BY 1'''), + class SqlServer2005SQLGeneratorTC(PostgresSQLGeneratorTC): backend = 'sqlserver2005' @@ -1697,10 +1767,61 @@ FROM cw_EmailAddress AS _O WHERE NOT (EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email0 WHERE rel_use_email0.eid_from=1 AND rel_use_email0.eid_to=_O.cw_eid)) AND EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email1 WHERE rel_use_email1.eid_to=_O.cw_eid AND EXISTS(SELECT 1 FROM cw_CWGroup AS _D WHERE rel_use_email1.eid_from=2 AND NOT (EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group2 WHERE rel_in_group2.eid_from=2 AND rel_in_group2.eid_to=_D.cw_eid)) AND _D.cw_name=guests)) ORDER BY 4 DESC'''), + + ("Any P ORDERBY N LIMIT 1 WHERE P is Personne, P travaille S, S eid %(eid)s, P nom N, P nom %(text)s", + '''WITH orderedrows AS ( +SELECT +_L01 +, ROW_NUMBER() OVER (ORDER BY _L01) AS __RowNumber +FROM ( +SELECT _P.cw_eid AS _L01 FROM cw_Personne AS _P, travaille_relation AS rel_travaille0 +WHERE rel_travaille0.eid_from=_P.cw_eid AND rel_travaille0.eid_to=12345 AND _P.cw_nom=hip hop momo +) AS _SQ1 ) +SELECT +_L01 +FROM orderedrows WHERE +__RowNumber <= 1'''), + + ("Any P ORDERBY N LIMIT 1 WHERE P is Personne, P nom N", + '''WITH orderedrows AS ( +SELECT +_L01 +, ROW_NUMBER() OVER (ORDER BY _L01) AS __RowNumber +FROM ( +SELECT _P.cw_eid AS _L01 FROM cw_Personne AS _P +) AS _SQ1 ) +SELECT +_L01 +FROM orderedrows WHERE +__RowNumber <= 1 +'''), + + ("Any PN, N, P ORDERBY N LIMIT 1 WHERE P is Personne, P nom N, P prenom PN", + '''WITH orderedrows AS ( +SELECT +_L01, _L02, _L03 +, ROW_NUMBER() OVER (ORDER BY _L02) AS __RowNumber +FROM ( +SELECT _P.cw_prenom AS _L01, _P.cw_nom AS _L02, _P.cw_eid AS _L03 FROM cw_Personne AS _P +) AS _SQ1 ) +SELECT +_L01, _L02, _L03 +FROM orderedrows WHERE +__RowNumber <= 1 +'''), ] for t in self._parse(WITH_LIMIT):# + ADVANCED_WITH_LIMIT_OR_ORDERBY): yield t + def test_groupby_orderby_insertion_dont_modify_intention(self): + self._check('Any YEAR(XECT)*100+MONTH(XECT), COUNT(X),SUM(XCE),AVG(XSCT-XECT) ' + 'GROUPBY YEAR(XECT),MONTH(XECT) ORDERBY 1 ' + 'WHERE X creation_date XSCT, X modification_date XECT, ' + 'X ordernum XCE, X is CWAttribute', + '''SELECT ((YEAR(_X.cw_modification_date) * 100) + MONTH(_X.cw_modification_date)), COUNT(_X.cw_eid), SUM(_X.cw_ordernum), AVG((_X.cw_creation_date - _X.cw_modification_date)) +FROM cw_CWAttribute AS _X +GROUP BY YEAR(_X.cw_modification_date),MONTH(_X.cw_modification_date) +ORDER BY 1'''), class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC): @@ -1832,6 +1953,16 @@ FROM cw_CWUser AS _X WHERE ((YEAR(_X.cw_creation_date)=2010) OR (_X.cw_creation_date IS NULL))''') + def test_groupby_orderby_insertion_dont_modify_intention(self): + self._check('Any YEAR(XECT)*100+MONTH(XECT), COUNT(X),SUM(XCE),AVG(XSCT-XECT) ' + 'GROUPBY YEAR(XECT),MONTH(XECT) ORDERBY 1 ' + 'WHERE X creation_date XSCT, X modification_date XECT, ' + 'X ordernum XCE, X is CWAttribute', + '''SELECT ((YEAR(_X.cw_modification_date) * 100) + MONTH(_X.cw_modification_date)), COUNT(_X.cw_eid), SUM(_X.cw_ordernum), AVG((_X.cw_creation_date - _X.cw_modification_date)) +FROM cw_CWAttribute AS _X +GROUP BY YEAR(_X.cw_modification_date),MONTH(_X.cw_modification_date) +ORDER BY 1'''), + class MySQLGenerator(PostgresSQLGeneratorTC): @@ -1928,6 +2059,16 @@ FROM (SELECT 1) AS _T WHERE NOT (EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group0))''') + def test_groupby_orderby_insertion_dont_modify_intention(self): + self._check('Any YEAR(XECT)*100+MONTH(XECT), COUNT(X),SUM(XCE),AVG(XSCT-XECT) ' + 'GROUPBY YEAR(XECT),MONTH(XECT) ORDERBY 1 ' + 'WHERE X creation_date XSCT, X modification_date XECT, ' + 'X ordernum XCE, X is CWAttribute', + '''SELECT ((EXTRACT(YEAR from _X.cw_modification_date) * 100) + EXTRACT(MONTH from _X.cw_modification_date)), COUNT(_X.cw_eid), SUM(_X.cw_ordernum), AVG((_X.cw_creation_date - _X.cw_modification_date)) +FROM cw_CWAttribute AS _X +GROUP BY EXTRACT(YEAR from _X.cw_modification_date),EXTRACT(MONTH from _X.cw_modification_date) +ORDER BY 1'''), + class removeUnsusedSolutionsTC(TestCase): def test_invariant_not_varying(self): diff -r dc319ece0bd6 -r cc3987eb793c server/test/unittest_rqlannotation.py --- a/server/test/unittest_rqlannotation.py Mon May 16 16:24:00 2011 +0200 +++ b/server/test/unittest_rqlannotation.py Wed Jul 20 18:21:47 2011 +0200 @@ -18,13 +18,16 @@ # with CubicWeb. If not, see . """unit tests for modules cubicweb.server.rqlannotation""" -from cubicweb.devtools import init_test_database +from cubicweb.devtools import TestServerConfiguration, get_test_db_handler from cubicweb.devtools.repotest import BaseQuerierTC def setUpModule(*args): + handler = get_test_db_handler(TestServerConfiguration( + 'data2', apphome=SQLGenAnnotatorTC.datadir)) + handler.build_db_cache() global repo, cnx - repo, cnx = init_test_database(apphome=SQLGenAnnotatorTC.datadir) + repo, cnx = handler.get_repo_and_cnx() def tearDownModule(*args): global repo, cnx @@ -330,6 +333,13 @@ self.assertEqual(rqlst.defined_vars['N']._q_invariant, False) self.assertEqual(rqlst.defined_vars['F']._q_invariant, True) + def test_nonregr_ambiguity_2(self): + rqlst = self._prepare('Any S,SN WHERE X has_text "tot", X in_state S, S name SN, X is CWUser') + # X use has_text but should not be invariant as ambiguous, and has_text + # may not be its principal + self.assertEqual(rqlst.defined_vars['X']._q_invariant, False) + self.assertEqual(rqlst.defined_vars['S']._q_invariant, False) + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 2011 +0200 @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # @@ -21,11 +22,15 @@ from cubicweb.devtools.testlib import CubicWebTC from cubicweb.transaction import * +from cubicweb.server.sources.native import UndoException + + 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() @@ -167,7 +172,7 @@ ['CWUser']) self.assertEqual([et.name for et in toto.is_instance_of], ['CWUser']) - # undoing shouldn't be visble in undoable transaction, and the undoed + # undoing shouldn't be visble in undoable transaction, and the undone # transaction should be removed txs = self.cnx.undoable_transactions() self.assertEqual(len(txs), 2) @@ -246,7 +251,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', @@ -282,6 +288,15 @@ # test implicit 'replacement' of an inlined relation + +class UndoExceptionInUnicode(CubicWebTC): + + # problem occurs in string manipulation for python < 2.6 + def test___unicode__method(self): + u = UndoException(u"voilà") + self.assertIsInstance(unicode(u), unicode) + + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r dc319ece0bd6 -r cc3987eb793c server/utils.py --- a/server/utils.py Mon May 16 16:24:00 2011 +0200 +++ b/server/utils.py Wed Jul 20 18:21:47 2011 +0200 @@ -128,14 +128,18 @@ (interval, func_name(func))) self.interval = interval def auto_restart_func(self=self, func=func, args=args): + restart = True try: func(*args) - except: + except Exception: logger = logging.getLogger('cubicweb.repository') logger.exception('Unhandled exception in LoopTask %s', self.name) raise + except BaseException: + restart = False finally: - self.start() + if restart: + self.start() self.func = auto_restart_func self.name = func_name(func) diff -r dc319ece0bd6 -r cc3987eb793c sobjects/parsers.py --- a/sobjects/parsers.py Mon May 16 16:24:00 2011 +0200 +++ b/sobjects/parsers.py Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c test/data/rewrite/schema.py --- a/test/data/rewrite/schema.py Mon May 16 16:24:00 2011 +0200 +++ b/test/data/rewrite/schema.py Wed Jul 20 18:21:47 2011 +0200 @@ -27,8 +27,8 @@ 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')), } ref = String(fulltextindexed=True, indexed=True, maxsize=16) - documented_by = SubjectRelation('Card') - concerne = SubjectRelation(('Societe', 'Note')) + documented_by = SubjectRelation('Card', cardinality='1*') + concerne = SubjectRelation(('Societe', 'Note'), cardinality='1*') class Societe(EntityType): diff -r dc319ece0bd6 -r cc3987eb793c test/data/schema.py --- a/test/data/schema.py Mon May 16 16:24:00 2011 +0200 +++ b/test/data/schema.py Wed Jul 20 18:21:47 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,13 @@ # # 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,24 +29,49 @@ 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() evaluee = SubjectRelation('Note') + fournit = SubjectRelation(('Service', 'Produit'), cardinality='1*') + + +class Service(EntityType): + fabrique_par = SubjectRelation('Personne', cardinality='1*') + + +class Produit(EntityType): + fabrique_par = SubjectRelation('Usine', cardinality='1*') + + +class Usine(EntityType): + lieu = String(required=True) + class Note(EntityType): type = String() ecrit_par = SubjectRelation('Personne') + class SubNote(Note): __specializes_schema__ = True description = String() + class tags(RelationDefinition): subject = 'Tag' object = ('Personne', 'Note') + class evaluee(RelationDefinition): subject = 'CWUser' object = 'Note' @@ -54,5 +79,3 @@ class StateFull(WorkflowableEntityType): name = String() - - diff -r dc319ece0bd6 -r cc3987eb793c test/unittest_entity.py --- a/test/unittest_entity.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_entity.py Wed Jul 20 18:21:47 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') @@ -215,7 +250,7 @@ 'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, ' 'X modification_date AB') - def test_related_rql_ambigous_cant_use_fetch_order(self): + def test_related_rql_ambiguous_cant_use_fetch_order(self): tag = self.vreg['etypes'].etype_class('Tag')(self.request()) for ttype in self.schema['tags'].objects(): self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',) @@ -223,56 +258,106 @@ 'Any X,AA ORDERBY AA DESC ' 'WHERE E eid %(x)s, E tags X, X modification_date AA') + def test_related_rql_cant_fetch_ambiguous_rtype(self): + soc_etype = self.vreg['etypes'].etype_class('Societe') + soc = soc_etype(self.request()) + soc_etype.fetch_attrs = ('fournit',) + self.vreg['etypes'].etype_class('Service').fetch_attrs = ('fabrique_par',) + self.vreg['etypes'].etype_class('Produit').fetch_attrs = ('fabrique_par',) + self.vreg['etypes'].etype_class('Usine').fetch_attrs = ('lieu',) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = ('nom',) + # XXX should be improved: we could fetch fabrique_par object too + self.assertEqual(soc.cw_related_rql('fournit', 'subject'), + 'Any X WHERE E eid %(x)s, E fournit X') + def test_unrelated_rql_security_1_manager(self): 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 +387,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 +597,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 cc3987eb793c test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_rqlrewrite.py Wed Jul 20 18:21:47 2011 +0200 @@ -33,7 +33,8 @@ config.bootstrap_cubes() schema = config.load_schema() from yams.buildobjs import RelationDefinition - schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', object='State', cardinality='1*')) + schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', + object='State', cardinality='1*')) rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid', 'has_text': 'fti'}) @@ -78,12 +79,22 @@ return rewriter.rewritten def test_vrefs(node): - vrefmap = {} + vrefmaps = {} + selects = [] for vref in node.iget_nodes(nodes.VariableRef): - vrefmap.setdefault(vref.name, set()).add(vref) - for var in node.defined_vars.itervalues(): - assert not (var.stinfo['references'] ^ vrefmap[var.name]) - assert (var.stinfo['references']) + stmt = vref.stmt + try: + vrefmaps[stmt].setdefault(vref.name, set()).add(vref) + except KeyError: + vrefmaps[stmt] = {vref.name: set( (vref,) )} + selects.append(stmt) + assert node in selects + for stmt in selects: + for var in stmt.defined_vars.itervalues(): + assert var.stinfo['references'] + vrefmap = vrefmaps[stmt] + assert not (var.stinfo['references'] ^ vrefmap[var.name]), (node.as_string(), var, var.stinfo['references'], vrefmap[var.name]) + class RQLRewriteTC(TestCase): """a faire: @@ -95,10 +106,10 @@ """ def test_base_var(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' + constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Card C') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), u"Any C WHERE C is Card, B eid %(D)s, " "EXISTS(C in_state A, B in_group E, F require_state A, " @@ -130,27 +141,31 @@ "E in_state D, D name 'subscribed'), D is State, E is CWUser)") def test_simplified_rqlst(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' + constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any 2') # this is the simplified rql st for Any X WHERE X eid 12 - rewrite(rqlst, {('2', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('2', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), u"Any 2 WHERE B eid %(C)s, " "EXISTS(2 in_state A, B in_group D, E require_state A, " "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)") - def test_optional_var_base(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' + def test_optional_var_1(self): + constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any A,C WHERE A documented_by C?') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), "Any A,C WHERE A documented_by C?, A is Affaire " "WITH C BEING " "(Any C WHERE EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', " "G require_group F), D eid %(A)s, C is Card)") + + def test_optional_var_2(self): + constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), "Any A,C,T WHERE A documented_by C?, A is Affaire " "WITH C,T BEING " @@ -158,6 +173,34 @@ "G require_state B, G name 'read', G require_group F), " "D eid %(A)s, C is Card)") + def test_optional_var_3(self): + constraint1 = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + constraint2 = 'X in_state S, S name "public"' + rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') + rewrite(rqlst, {('C', 'X'): (constraint1, constraint2)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any A,C,T WHERE A documented_by C?, A is Affaire " + "WITH C,T BEING (Any C,T WHERE C title T, " + "EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F), " + "D eid %(A)s, C is Card, " + "EXISTS(C in_state E, E name 'public'))") + + def test_optional_var_4(self): + constraint1 = 'A created_by U, X documented_by A' + constraint2 = 'A created_by U, X concerne A' + constraint3 = 'X created_by U' + rqlst = parse('Any X,LA,Y WHERE LA? documented_by X, LA concerne Y') + rewrite(rqlst, {('LA', 'X'): (constraint1, constraint2), + ('X', 'X'): (constraint3,), + ('Y', 'X'): (constraint3,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + u'Any X,LA,Y WHERE LA? documented_by X, LA concerne Y, B eid %(C)s, ' + 'EXISTS(X created_by B), EXISTS(Y created_by B), ' + 'X is Card, Y is IN(Division, Note, Societe) ' + 'WITH LA BEING (Any LA WHERE EXISTS(A created_by B, LA documented_by A), ' + 'B eid %(D)s, LA is Affaire, EXISTS(E created_by B, LA concerne E))') + def test_optional_var_inlined(self): c1 = ('X require_permission P') c2 = ('X inlined_card O, O require_permission P') @@ -353,14 +396,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 cc3987eb793c test/unittest_rset.py --- a/test/unittest_rset.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_rset.py Wed Jul 20 18:21:47 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. @@ -63,6 +63,13 @@ result = list(attr_desc_iterator(parse(rql).children[0], idx, idx)) self.assertEqual(result, relations) + def test_subquery_callfunc(self): + rql = ('Any A,B,C,COUNT(D) GROUPBY A,B,C WITH A,B,C,D BEING ' + '(Any YEAR(CD), MONTH(CD), S, X WHERE X is CWUser, X creation_date CD, X in_state S)') + rqlst = parse(rql) + select, col = rqlst.locate_subquery(2, 'CWUser', None) + result = list(attr_desc_iterator(select, col, 2)) + self.assertEqual(result, []) class ResultSetTC(CubicWebTC): @@ -114,7 +121,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 cc3987eb793c test/unittest_schema.py --- a/test/unittest_schema.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_schema.py Wed Jul 20 18:21:47 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. @@ -26,7 +26,7 @@ from rql import RQLSyntaxError -from yams import BadSchemaDefinition +from yams import ValidationError, BadSchemaDefinition from yams.constraints import SizeConstraint, StaticVocabularyConstraint from yams.buildobjs import RelationDefinition, EntityType, RelationType from yams.reader import PyFileReader @@ -37,6 +37,7 @@ RQLExpression, ERQLExpression, RRQLExpression, normalize_expression, order_eschemas, guess_rrqlexpr_mainvars) from cubicweb.devtools import TestServerConfiguration as TestConfiguration +from cubicweb.devtools.testlib import CubicWebTC DATADIR = join(dirname(__file__), 'data') @@ -166,10 +167,11 @@ 'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig', 'CWUniqueTogetherConstraint', 'CWUser', 'ExternalUri', 'File', 'Float', 'Int', 'Interval', 'Note', - 'Password', 'Personne', + 'Password', 'Personne', 'Produit', 'RQLExpression', - 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint', - 'Tag', 'Time', 'Transition', 'TrInfo', + 'Service', 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint', + 'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo', + 'Usine', 'Workflow', 'WorkflowTransition'] self.assertListEqual(sorted(expected_entities), entities) relations = sorted([str(r) for r in schema.relations()]) @@ -188,14 +190,14 @@ 'ecrit_par', 'eid', 'evaluee', 'expression', 'exprtype', - 'final', 'firstname', 'for_user', + 'fabrique_par', 'final', 'firstname', 'for_user', 'fournit', 'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed', 'has_text', 'identity', 'in_group', 'in_state', 'indexed', 'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of', - 'label', 'last_login_time', 'latest_retrieval', 'login', + 'label', 'last_login_time', 'latest_retrieval', 'lieu', 'login', 'mainvars', 'match_host', 'modification_date', @@ -239,7 +241,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) @@ -314,8 +316,17 @@ 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') + mainvars = guess_rrqlexpr_mainvars(normalize_expression('NOT EXISTS(O team_competition C, C level < 3, C concerns S)')) + self.assertEqual(mainvars, set(['S', 'O'])) + + +class RQLConstraintTC(CubicWebTC): + def test_user_constraint(self): + cstr = RQLConstraint('U identity O') + anoneid = self.execute('Any X WHERE X login "anon"')[0][0] + self.assertRaises(ValidationError, cstr.repo_check, self.session, 1, 'rel', anoneid) + self.assertEqual(cstr.repo_check(self.session, 1, self.session.user.eid), + None) # no validation error, constraint checked if __name__ == '__main__': diff -r dc319ece0bd6 -r cc3987eb793c test/unittest_selectors.py --- a/test/unittest_selectors.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_selectors.py Wed Jul 20 18:21:47 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 cc3987eb793c test/unittest_uilib.py --- a/test/unittest_uilib.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_uilib.py Wed Jul 20 18:21:47 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. @@ -22,14 +22,15 @@ import pkg_resources -from logilab.common.testlib import TestCase, unittest_main + from unittest2 import skipIf +from logilab.common.testlib import DocTest, TestCase, unittest_main + from cubicweb import uilib lxml_version = pkg_resources.get_distribution('lxml').version.split('.') - class UILIBTC(TestCase): def test_remove_tags(self): @@ -185,6 +186,10 @@ self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected) +class DocTest(DocTest): + module = uilib + + if __name__ == '__main__': unittest_main() diff -r dc319ece0bd6 -r cc3987eb793c test/unittest_utils.py --- a/test/unittest_utils.py Mon May 16 16:24:00 2011 +0200 +++ b/test/unittest_utils.py Wed Jul 20 18:21:47 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. @@ -21,7 +21,7 @@ import decimal import datetime -from logilab.common.testlib import TestCase, unittest_main +from logilab.common.testlib import TestCase, DocTest, unittest_main from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList, RepeatList from cubicweb.entity import Entity @@ -156,5 +156,8 @@ self.assertEqual(self.encode(TestCase), 'null') +class DocTest(DocTest): + from cubicweb import utils as module + if __name__ == '__main__': unittest_main() diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/ajax_url0.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajax_url0.html Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,3 @@ +

+

Hello

+
diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/ajax_url1.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajax_url1.html Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,6 @@ +
+
+ +
+

Hello

+
diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/ajax_url2.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajax_url2.html Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,7 @@ +
+
+ + +
+

Hello

+
diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/ajaxresult.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/ajaxresult.json Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,1 @@ +['foo', 'bar'] diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/test_ajax.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_ajax.html Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + +
+

cubicweb.ajax.js functions tests

+

+
    + + diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/test_ajax.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_ajax.js Wed Jul 20 18:21:47 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 cc3987eb793c testfunc/test/jstests/test_htmlhelpers.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_htmlhelpers.html Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + +
    +
    +

    cubicweb.htmlhelpers.js functions tests

    +

    +

    +
      + + diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/test_htmlhelpers.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_htmlhelpers.js Wed Jul 20 18:21:47 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 cc3987eb793c testfunc/test/jstests/test_utils.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_utils.html Wed Jul 20 18:21:47 2011 +0200 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + +
      +
      +

      cw.utils functions tests

      +

      +

      +
        + + diff -r dc319ece0bd6 -r cc3987eb793c testfunc/test/jstests/test_utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/test_utils.js Wed Jul 20 18:21:47 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 cc3987eb793c testfunc/test/jstests/utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testfunc/test/jstests/utils.js Wed Jul 20 18:21:47 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>> l = SizeConstrainedList(2) >>> l.append(1) @@ -142,6 +142,7 @@ >>> l [1, 2] >>> l.append(3) + >>> l [2, 3] """ def __init__(self, maxsize): @@ -364,17 +365,43 @@ self.doctype = u'' # xmldecl and html opening tag self.xmldecl = u'\n' % req.encoding - self.htmltag = u'' % (req.lang, req.lang) + self._namespaces = [('xmlns', 'http://www.w3.org/1999/xhtml'), + ('xmlns:cubicweb','http://www.logilab.org/2008/cubicweb')] + self._htmlattrs = [('xml:lang', req.lang), + ('lang', req.lang)] # keep main_stream's reference on req for easier text/html demoting req.main_stream = self + def add_namespace(self, prefix, uri): + self._namespaces.append( (prefix, uri) ) + + def set_namespaces(self, namespaces): + self._namespaces = namespaces + + def add_htmlattr(self, attrname, attrvalue): + self._htmlattrs.append( (attrname, attrvalue) ) + + def set_htmlattrs(self, attrs): + self._htmlattrs = attrs + + def set_doctype(self, doctype, reset_xmldecl=True): + self.doctype = doctype + if reset_xmldecl: + self.xmldecl = u'' + def write(self, data): """StringIO interface: this method will be assigned to self.w """ self.body.write(data) + @property + def htmltag(self): + attrs = ' '.join('%s="%s"' % (attr, xml_escape(value)) + for attr, value in (self._namespaces + self._htmlattrs)) + if attrs: + return '' % attrs + return '' + def getvalue(self): """writes HTML headers, closes tag and writes HTML body""" return u'%s\n%s\n%s\n%s\n%s\n' % (self.xmldecl, self.doctype, @@ -382,7 +409,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 cc3987eb793c view.py --- a/view.py Mon May 16 16:24:00 2011 +0200 +++ b/view.py Wed Jul 20 18:21:47 2011 +0200 @@ -371,7 +371,7 @@ class EntityView(View): """base class for views applying on an entity (i.e. uniform result set)""" __select__ = non_final_entity() - category = 'entityview' + category = _('entityview') def call(self, **kwargs): if self.cw_rset is None: @@ -392,7 +392,7 @@ """ __select__ = none_rset() - category = 'startupview' + category = _('startupview') def html_headers(self): """return a list of html headers (eg something to be inserted between @@ -436,7 +436,7 @@ """base class for views applying on any non empty result sets""" __select__ = nonempty_rset() - category = 'anyrsetview' + category = _('anyrsetview') def columns_labels(self, mainindex=0, tr=True): if tr: @@ -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 cc3987eb793c web/_exceptions.py --- a/web/_exceptions.py Mon May 16 16:24:00 2011 +0200 +++ b/web/_exceptions.py Wed Jul 20 18:21:47 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 cc3987eb793c web/application.py --- a/web/application.py Mon May 16 16:24:00 2011 +0200 +++ b/web/application.py Wed Jul 20 18:21:47 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. @@ -71,6 +71,8 @@ total += 1 try: last_usage_time = session.cnx.check() + except AttributeError: + last_usage_time = session.mtime except BadConnectionId: self.close_session(session) closed += 1 @@ -204,17 +206,36 @@ except InvalidSession: # try to open a new session, so we get an anonymous session if # allowed - try: - session = self.open_session(req) - except AuthenticationError: - req.remove_cookie(cookie, sessioncookie) - raise + session = self.open_session(req) + else: + if not session.cnx: + # session exists but is not bound to a connection. We should + # try to authenticate + loginsucceed = False + try: + if self.open_session(req, allow_no_cnx=False): + loginsucceed = True + except Redirect: + # may be raised in open_session (by postlogin mechanism) + # on successful connection + loginsucceed = True + raise + except AuthenticationError: + # authentication failed, continue to use this session + req.set_session(session) + finally: + if loginsucceed: + # session should be replaced by new session created + # in open_session + self.session_manager.close_session(session) def get_session(self, req, sessionid): - return self.session_manager.get_session(req, sessionid) + session = self.session_manager.get_session(req, sessionid) + session.mtime = time() + return session - def open_session(self, req): - session = self.session_manager.open_session(req) + def open_session(self, req, allow_no_cnx=True): + session = self.session_manager.open_session(req, allow_no_cnx=allow_no_cnx) cookie = req.get_cookie() sessioncookie = self.session_cookie(req) cookie[sessioncookie] = session.sessionid @@ -279,10 +300,7 @@ sessions (i.e. a new connection may be created or an already existing one may be reused """ - try: - self.session_handler.set_session(req) - except AuthenticationError: - req.set_session(DBAPISession(None)) + self.session_handler.set_session(req) # publish methods ######################################################### @@ -365,11 +383,12 @@ # redirect is raised by edit controller when everything went fine, # so try to commit try: - txuuid = req.cnx.commit() - if txuuid is not None: - msg = u'[%s]' %( - req.build_url('undo', txuuid=txuuid), req._('undo')) - req.append_to_redirect_message(msg) + if req.cnx: + txuuid = req.cnx.commit() + if txuuid is not None: + msg = u'[%s]' %( + req.build_url('undo', txuuid=txuuid), req._('undo')) + req.append_to_redirect_message(msg) except ValidationError, ex: self.validation_error_handler(req, ex) except Unauthorized, ex: diff -r dc319ece0bd6 -r cc3987eb793c web/component.py --- a/web/component.py Mon May 16 16:24:00 2011 +0200 +++ b/web/component.py Wed Jul 20 18:21:47 2011 +0200 @@ -410,7 +410,6 @@ params.pop('view', None) params.pop('entity', None) form = params.pop('formparams', {}) - form['pageid'] = self._cw.pageid if entity.has_eid(): eid = entity.eid else: diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 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. @@ -215,7 +215,7 @@ } /** - * .. function:: loadxhtml(url, form, reqtype='get', mode='replace', cursor=true) + * .. function:: loadxhtml(url, form, reqtype='get', mode='replace', cursor=false) * * build url given by absolute or relative `url` and `form` parameters * (dictionary), fetch it using `reqtype` method, then evaluate the @@ -231,7 +231,9 @@ */ jQuery.fn.loadxhtml = function(url, form, reqtype, mode, cursor) { if (this.size() > 1) { - cw.log('loadxhtml was called with more than one element'); + cw.log('loadxhtml called with more than one element'); + } else if (this.size() < 1) { + cw.log('loadxhtml called without an element'); } var callback = null; if (form && form.callback) { @@ -605,7 +607,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 ******************************************************************/ @@ -700,43 +702,37 @@ } ); -remoteExec = cw.utils.deprecatedFunction( - '[3.9] remoteExec() is deprecated, use loadRemote instead', - function(fname /* ... */) { - setProgressCursor(); - var props = { - fname: fname, - pageid: pageid, - arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) - }; - var result = jQuery.ajax({ - url: JSON_BASE_URL, - data: props, - async: false, - traditional: true - }).responseText; - if (result) { - result = cw.evalJSON(result); - } - resetCursor(); - return result; +function remoteExec(fname /* ... */) { + setProgressCursor(); + var props = { + fname: fname, + pageid: pageid, + arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) + }; + var result = jQuery.ajax({ + url: JSON_BASE_URL, + data: props, + async: false, + traditional: true + }).responseText; + if (result) { + result = cw.evalJSON(result); } -); + resetCursor(); + return result; +} -asyncRemoteExec = cw.utils.deprecatedFunction( - '[3.9] asyncRemoteExec() is deprecated, use loadRemote instead', - function(fname /* ... */) { - setProgressCursor(); - var props = { - fname: fname, - pageid: pageid, - arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) - }; - // XXX we should inline the content of loadRemote here - var deferred = loadRemote(JSON_BASE_URL, props, 'POST'); - deferred = deferred.addErrback(remoteCallFailed); - deferred = deferred.addErrback(resetCursor); - deferred = deferred.addCallback(resetCursor); - return deferred; - } -); +function asyncRemoteExec(fname /* ... */) { + setProgressCursor(); + var props = { + fname: fname, + pageid: pageid, + arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) + }; + // XXX we should inline the content of loadRemote here + var deferred = loadRemote(JSON_BASE_URL, props, 'POST'); + deferred = deferred.addErrback(remoteCallFailed); + deferred = deferred.addErrback(resetCursor); + deferred = deferred.addCallback(resetCursor); + return deferred; +} diff -r dc319ece0bd6 -r cc3987eb793c web/data/cubicweb.calendar.js --- a/web/data/cubicweb.calendar.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.calendar.js Wed Jul 20 18:21:47 2011 +0200 @@ -15,9 +15,9 @@ /** * .. class:: Calendar * - * Calendar (graphical) widget + * Calendar (graphical) widget * - * public methods are : + * public methods are : * * __init__ : * :attr:`containerId`: the DOM node's ID where the calendar will be displayed @@ -74,7 +74,7 @@ /** * .. function:: Calendar._uppercaseFirst(s) * - * utility function (the only use for now is inside the calendar) + * utility function (the only use for now is inside the calendar) */ this._uppercaseFirst = function(s) { return s.charAt(0).toUpperCase(); @@ -83,7 +83,7 @@ /** * .. function:: Calendar._domForRows(rows) * - * accepts the cells data and builds the corresponding TR nodes + * accepts the cells data and builds the corresponding TR nodes * * * `rows`, a list of list of couples (daynum, cssprops) */ @@ -98,7 +98,7 @@ /** * .. function:: Calendar._headdisplay(row) * - * builds the calendar headers + * builds the calendar headers */ this._headdisplay = function(row) { if (_CAL_HEADER) { @@ -224,13 +224,17 @@ this.hide); // connect(inputId, 'onfocus', this, 'hide'); }; -// keep track of each calendar created +/** + * .. data:: Calendar.REGISTRY + * + * keep track of each calendar created + */ Calendar.REGISTRY = {}; /** * .. function:: toggleCalendar(containerId, inputId, year, month) * - * popup / hide calendar associated to `containerId` + * popup / hide calendar associated to `containerId` */ function toggleCalendar(containerId, inputId, year, month) { var cal = Calendar.REGISTRY[containerId]; @@ -251,7 +255,7 @@ /** * .. function:: toggleNextMonth(containerId) * - * ask for next month to calendar displayed in `containerId` + * ask for next month to calendar displayed in `containerId` */ function toggleNextMonth(containerId) { var cal = Calendar.REGISTRY[containerId]; @@ -261,7 +265,7 @@ /** * .. function:: togglePreviousMonth(containerId) * - * ask for previous month to calendar displayed in `containerId` + * ask for previous month to calendar displayed in `containerId` */ function togglePreviousMonth(containerId) { var cal = Calendar.REGISTRY[containerId]; @@ -271,7 +275,7 @@ /** * .. function:: dateSelected(cell, containerId) * - * Callback called when the user clicked on a cell in the popup calendar + * callback called when the user clicked on a cell in the popup calendar */ function dateSelected(cell, containerId) { var cal = Calendar.REGISTRY[containerId]; diff -r dc319ece0bd6 -r cc3987eb793c web/data/cubicweb.css --- a/web/data/cubicweb.css Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.css Wed Jul 20 18:21:47 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 cc3987eb793c web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.edition.js Wed Jul 20 18:21:47 2011 +0200 @@ -583,6 +583,7 @@ * around the corresponding input fields. */ function validateForm(formid, action, onsuccess, onfailure) { + freezeFormButtons(formid); try { var zipped = cw.utils.formContents(formid); var args = ajaxFuncArgs('validate_form', null, action, zipped[0], zipped[1]); diff -r dc319ece0bd6 -r cc3987eb793c web/data/cubicweb.facets.js --- a/web/data/cubicweb.facets.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.facets.js Wed Jul 20 18:21:47 2011 +0200 @@ -133,11 +133,20 @@ // may changes and we must send its value when the callback is // called, not when the page is initialized var facetargs = form.attr('cubicweb:facetargs'); - if (facetargs !== undefined) { + if (facetargs != undefined && !form.attr('cubicweb:initialized')) { + form.attr('cubicweb:initialized', '1'); + var jsfacetargs = cw.evalJSON(form.attr('cubicweb:facetargs')); form.submit(function() { - buildRQL.apply(null, cw.evalJSON(form.attr('cubicweb:facetargs'))); + buildRQL.apply(null, jsfacetargs); return false; }); + var divid = jsfacetargs[0]; + if (jQuery('#'+divid).length) { + var $loadingDiv = $(DIV({id:'facetLoading'}, + facetLoadingMsg)); + $loadingDiv.corner(); + $(jQuery('#'+divid).get(0).parentNode).append($loadingDiv); + } form.find('div.facet').each(function() { var facet = jQuery(this); facet.find('div.facetCheckBox').each(function(i) { @@ -188,7 +197,7 @@ var $img = jQuery(this).find('img'); $img.attr('src', SELECTED_IMG).attr('alt', (_('selected'))); } - buildRQL.apply(null, cw.evalJSON(form.attr('cubicweb:facetargs'))); + buildRQL.apply(null, jsfacetargs); facet.find('.facetBody').animate({ scrollTop: 0 }, @@ -197,7 +206,7 @@ facet.find('select.facetOperator').change(function() { var nbselected = facet.find('div.facetValueSelected').length; if (nbselected >= 2) { - buildRQL.apply(null, cw.evalJSON(form.attr('cubicweb:facetargs'))); + buildRQL.apply(null, jsfacetargs); } }); facet.find('div.facetTitle').click(function() { diff -r dc319ece0bd6 -r cc3987eb793c web/data/cubicweb.flot.js --- a/web/data/cubicweb.flot.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.flot.js Wed Jul 20 18:21:47 2011 +0200 @@ -13,16 +13,25 @@ var previousPoint = null; function onPlotHover(event, pos, item) { + var $fig = $(event.target); if (item) { if (previousPoint != item.datapoint) { previousPoint = item.datapoint; $("#tooltip").remove(); var x = item.datapoint[0].toFixed(2), - y = item.datapoint[1].toFixed(2); - if (item.datapoint.length == 3) { - x = new Date(item.datapoint[2]); - x = x.toLocaleDateString() + ' ' + x.toLocaleTimeString(); + y = item.datapoint[1].toFixed(2); + if ($fig.data('mode') == 'time') { + x = new Date(item.datapoint[0]); + var dateformat = $fig.data('dateformat'); + if (dateformat) { + x = x.strftime(dateformat); + } else { + x = x.toLocaleDateString() + ' ' + x.toLocaleTimeString(); + } } else if (item.datapoint.length == 4) { + // NOTE: this has no chance to work with jquery flot >= 0.6 because + // jquery flot normalizes datapoints and only keeps 2 columns. Either + // use processRawData hook or use the 'dateformat' option. x = new Date(item.datapoint[2]); x = x.strftime(item.datapoint[3]); } diff -r dc319ece0bd6 -r cc3987eb793c 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 cc3987eb793c web/data/cubicweb.timeline-bundle.js --- a/web/data/cubicweb.timeline-bundle.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/cubicweb.timeline-bundle.js Wed Jul 20 18:21:47 2011 +0200 @@ -1,10 +1,15 @@ +/** + * This file contains timeline utilities + * :organization: Logilab + */ + var SimileAjax_urlPrefix = baseuri() + 'data/'; var Timeline_urlPrefix = baseuri() + 'data/'; /* * Simile Ajax API * - * Include this file in your HTML file as follows: + * Include this file in your HTML file as follows:: * * * diff -r dc319ece0bd6 -r cc3987eb793c web/data/excanvas.js --- a/web/data/excanvas.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/excanvas.js Wed Jul 20 18:21:47 2011 +0200 @@ -1,924 +1,30 @@ -// Copyright 2006 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Known Issues: -// -// * Patterns are not implemented. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Non uniform scaling does not correctly scale strokes. -// * Optimize. There is always room for speed improvements. - -// Only add this code if we do not already have a canvas implementation -if (!document.createElement('canvas').getContext) { - -(function() { - - // alias some functions to make (compiled) code shorter - var m = Math; - var mr = m.round; - var ms = m.sin; - var mc = m.cos; - var abs = m.abs; - var sqrt = m.sqrt; - - // this is used for sub pixel precision - var Z = 10; - var Z2 = Z / 2; - - /** - * This funtion is assigned to the elements as element.getContext(). - * @this {HTMLElement} - * @return {CanvasRenderingContext2D_} - */ - function getContext() { - return this.context_ || - (this.context_ = new CanvasRenderingContext2D_(this)); - } - - var slice = Array.prototype.slice; - - /** - * Binds a function to an object. The returned function will always use the - * passed in {@code obj} as {@code this}. - * - * Example: - * - * g = bind(f, obj, a, b) - * g(c, d) // will do f.call(obj, a, b, c, d) - * - * @param {Function} f The function to bind the object to - * @param {Object} obj The object that should act as this when the function - * is called - * @param {*} var_args Rest arguments that will be used as the initial - * arguments when the function is called - * @return {Function} A new function that has bound this - */ - function bind(f, obj, var_args) { - var a = slice.call(arguments, 2); - return function() { - return f.apply(obj, a.concat(slice.call(arguments))); - }; - } - - var G_vmlCanvasManager_ = { - init: function(opt_doc) { - if (/MSIE/.test(navigator.userAgent) && !window.opera) { - var doc = opt_doc || document; - // Create a dummy element so that IE will allow canvas elements to be - // recognized. - doc.createElement('canvas'); - doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); - } - }, - - init_: function(doc) { - // create xmlns - if (!doc.namespaces['g_vml_']) { - doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', - '#default#VML'); - - } - if (!doc.namespaces['g_o_']) { - doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', - '#default#VML'); - } - - // Setup default CSS. Only add one style sheet per document - if (!doc.styleSheets['ex_canvas_']) { - var ss = doc.createStyleSheet(); - ss.owningElement.id = 'ex_canvas_'; - ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + - // default size is 300x150 in Gecko and Opera - 'text-align:left;width:300px;height:150px}' + - 'g_vml_\\:*{behavior:url(#default#VML)}' + - 'g_o_\\:*{behavior:url(#default#VML)}'; - - } - - // find all canvas elements - var els = doc.getElementsByTagName('canvas'); - for (var i = 0; i < els.length; i++) { - this.initElement(els[i]); - } - }, - - /** - * Public initializes a canvas element so that it can be used as canvas - * element from now on. This is called automatically before the page is - * loaded but if you are creating elements using createElement you need to - * make sure this is called on the element. - * @param {HTMLElement} el The canvas element to initialize. - * @return {HTMLElement} the element that was created. - */ - initElement: function(el) { - if (!el.getContext) { - - el.getContext = getContext; - - // Remove fallback content. There is no way to hide text nodes so we - // just remove all childNodes. We could hide all elements and remove - // text nodes but who really cares about the fallback content. - el.innerHTML = ''; - - // do not use inline function because that will leak memory - el.attachEvent('onpropertychange', onPropertyChange); - el.attachEvent('onresize', onResize); - - var attrs = el.attributes; - if (attrs.width && attrs.width.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setWidth_(attrs.width.nodeValue); - el.style.width = attrs.width.nodeValue + 'px'; - } else { - el.width = el.clientWidth; - } - if (attrs.height && attrs.height.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setHeight_(attrs.height.nodeValue); - el.style.height = attrs.height.nodeValue + 'px'; - } else { - el.height = el.clientHeight; - } - //el.getContext().setCoordsize_() - } - return el; - } - }; - - function onPropertyChange(e) { - var el = e.srcElement; - - switch (e.propertyName) { - case 'width': - el.style.width = el.attributes.width.nodeValue + 'px'; - el.getContext().clearRect(); - break; - case 'height': - el.style.height = el.attributes.height.nodeValue + 'px'; - el.getContext().clearRect(); - break; - } - } - - function onResize(e) { - var el = e.srcElement; - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - el.firstChild.style.height = el.clientHeight + 'px'; - } - } - - G_vmlCanvasManager_.init(); - - // precompute "00" to "FF" - var dec2hex = []; - for (var i = 0; i < 16; i++) { - for (var j = 0; j < 16; j++) { - dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); - } - } - - function createMatrixIdentity() { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]; - } - - function matrixMultiply(m1, m2) { - var result = createMatrixIdentity(); - - for (var x = 0; x < 3; x++) { - for (var y = 0; y < 3; y++) { - var sum = 0; - - for (var z = 0; z < 3; z++) { - sum += m1[x][z] * m2[z][y]; - } - - result[x][y] = sum; - } - } - return result; - } - - function copyState(o1, o2) { - o2.fillStyle = o1.fillStyle; - o2.lineCap = o1.lineCap; - o2.lineJoin = o1.lineJoin; - o2.lineWidth = o1.lineWidth; - o2.miterLimit = o1.miterLimit; - o2.shadowBlur = o1.shadowBlur; - o2.shadowColor = o1.shadowColor; - o2.shadowOffsetX = o1.shadowOffsetX; - o2.shadowOffsetY = o1.shadowOffsetY; - o2.strokeStyle = o1.strokeStyle; - o2.globalAlpha = o1.globalAlpha; - o2.arcScaleX_ = o1.arcScaleX_; - o2.arcScaleY_ = o1.arcScaleY_; - o2.lineScale_ = o1.lineScale_; - } - - function processStyle(styleString) { - var str, alpha = 1; - - styleString = String(styleString); - if (styleString.substring(0, 3) == 'rgb') { - var start = styleString.indexOf('(', 3); - var end = styleString.indexOf(')', start + 1); - var guts = styleString.substring(start + 1, end).split(','); - - str = '#'; - for (var i = 0; i < 3; i++) { - str += dec2hex[Number(guts[i])]; - } - - if (guts.length == 4 && styleString.substr(3, 1) == 'a') { - alpha = guts[3]; - } - } else { - str = styleString; - } - - return {color: str, alpha: alpha}; - } - - function processLineCap(lineCap) { - switch (lineCap) { - case 'butt': - return 'flat'; - case 'round': - return 'round'; - case 'square': - default: - return 'square'; - } - } - - /** - * This class implements CanvasRenderingContext2D interface as described by - * the WHATWG. - * @param {HTMLElement} surfaceElement The element that the 2D context should - * be associated with - */ - function CanvasRenderingContext2D_(surfaceElement) { - this.m_ = createMatrixIdentity(); - - this.mStack_ = []; - this.aStack_ = []; - this.currentPath_ = []; - - // Canvas context properties - this.strokeStyle = '#000'; - this.fillStyle = '#000'; - - this.lineWidth = 1; - this.lineJoin = 'miter'; - this.lineCap = 'butt'; - this.miterLimit = Z * 1; - this.globalAlpha = 1; - this.canvas = surfaceElement; - - var el = surfaceElement.ownerDocument.createElement('div'); - el.style.width = surfaceElement.clientWidth + 'px'; - el.style.height = surfaceElement.clientHeight + 'px'; - el.style.overflow = 'hidden'; - el.style.position = 'absolute'; - surfaceElement.appendChild(el); - - this.element_ = el; - this.arcScaleX_ = 1; - this.arcScaleY_ = 1; - this.lineScale_ = 1; - } - - var contextPrototype = CanvasRenderingContext2D_.prototype; - contextPrototype.clearRect = function() { - this.element_.innerHTML = ''; - }; - - contextPrototype.beginPath = function() { - // TODO: Branch current matrix so that save/restore has no effect - // as per safari docs. - this.currentPath_ = []; - }; - - contextPrototype.moveTo = function(aX, aY) { - var p = this.getCoords_(aX, aY); - this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.lineTo = function(aX, aY) { - var p = this.getCoords_(aX, aY); - this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); - - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, - aCP2x, aCP2y, - aX, aY) { - var p = this.getCoords_(aX, aY); - var cp1 = this.getCoords_(aCP1x, aCP1y); - var cp2 = this.getCoords_(aCP2x, aCP2y); - bezierCurveTo(this, cp1, cp2, p); - }; - - // Helper function that takes the already fixed cordinates. - function bezierCurveTo(self, cp1, cp2, p) { - self.currentPath_.push({ - type: 'bezierCurveTo', - cp1x: cp1.x, - cp1y: cp1.y, - cp2x: cp2.x, - cp2y: cp2.y, - x: p.x, - y: p.y - }); - self.currentX_ = p.x; - self.currentY_ = p.y; - } - - contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { - // the following is lifted almost directly from - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes - - var cp = this.getCoords_(aCPx, aCPy); - var p = this.getCoords_(aX, aY); - - var cp1 = { - x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), - y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) - }; - var cp2 = { - x: cp1.x + (p.x - this.currentX_) / 3.0, - y: cp1.y + (p.y - this.currentY_) / 3.0 - }; - - bezierCurveTo(this, cp1, cp2, p); - }; - - contextPrototype.arc = function(aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { - aRadius *= Z; - var arcType = aClockwise ? 'at' : 'wa'; - - var xStart = aX + mc(aStartAngle) * aRadius - Z2; - var yStart = aY + ms(aStartAngle) * aRadius - Z2; - - var xEnd = aX + mc(aEndAngle) * aRadius - Z2; - var yEnd = aY + ms(aEndAngle) * aRadius - Z2; - - // IE won't render arches drawn counter clockwise if xStart == xEnd. - if (xStart == xEnd && !aClockwise) { - xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - - var p = this.getCoords_(aX, aY); - var pStart = this.getCoords_(xStart, yStart); - var pEnd = this.getCoords_(xEnd, yEnd); - - this.currentPath_.push({type: arcType, - x: p.x, - y: p.y, - radius: aRadius, - xStart: pStart.x, - yStart: pStart.y, - xEnd: pEnd.x, - yEnd: pEnd.y}); - - }; - - contextPrototype.rect = function(aX, aY, aWidth, aHeight) { - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - }; - - contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.stroke(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.fill(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { - var gradient = new CanvasGradient_('gradient'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - return gradient; - }; - - contextPrototype.createRadialGradient = function(aX0, aY0, aR0, - aX1, aY1, aR1) { - var gradient = new CanvasGradient_('gradientradial'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.r0_ = aR0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - gradient.r1_ = aR1; - return gradient; - }; - - contextPrototype.drawImage = function(image, var_args) { - var dx, dy, dw, dh, sx, sy, sw, sh; - - // to find the original width we overide the width and height - var oldRuntimeWidth = image.runtimeStyle.width; - var oldRuntimeHeight = image.runtimeStyle.height; - image.runtimeStyle.width = 'auto'; - image.runtimeStyle.height = 'auto'; - - // get the original size - var w = image.width; - var h = image.height; - - // and remove overides - image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; - - if (arguments.length == 3) { - dx = arguments[1]; - dy = arguments[2]; - sx = sy = 0; - sw = dw = w; - sh = dh = h; - } else if (arguments.length == 5) { - dx = arguments[1]; - dy = arguments[2]; - dw = arguments[3]; - dh = arguments[4]; - sx = sy = 0; - sw = w; - sh = h; - } else if (arguments.length == 9) { - sx = arguments[1]; - sy = arguments[2]; - sw = arguments[3]; - sh = arguments[4]; - dx = arguments[5]; - dy = arguments[6]; - dw = arguments[7]; - dh = arguments[8]; - } else { - throw Error('Invalid number of arguments'); - } - - var d = this.getCoords_(dx, dy); - - var w2 = sw / 2; - var h2 = sh / 2; - - var vmlStr = []; - - var W = 10; - var H = 10; - - // For some reason that I've now forgotten, using divs didn't work - vmlStr.push(' ' , - '', - ''); - - this.element_.insertAdjacentHTML('BeforeEnd', - vmlStr.join('')); - }; - - contextPrototype.stroke = function(aFill) { - var lineStr = []; - var lineOpen = false; - var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); - var color = a.color; - var opacity = a.alpha * this.globalAlpha; - - var W = 10; - var H = 10; - - lineStr.push(''); - - if (!aFill) { - var lineWidth = this.lineScale_ * this.lineWidth; - - // VML cannot correctly render a line if the width is less than 1px. - // In that case, we dilute the color to make the line look thinner. - if (lineWidth < 1) { - opacity *= lineWidth; - } - - lineStr.push( - '' - ); - } else if (typeof this.fillStyle == 'object') { - var fillStyle = this.fillStyle; - var angle = 0; - var focus = {x: 0, y: 0}; - - // additional offset - var shift = 0; - // scale factor for offset - var expansion = 1; - - if (fillStyle.type_ == 'gradient') { - var x0 = fillStyle.x0_ / this.arcScaleX_; - var y0 = fillStyle.y0_ / this.arcScaleY_; - var x1 = fillStyle.x1_ / this.arcScaleX_; - var y1 = fillStyle.y1_ / this.arcScaleY_; - var p0 = this.getCoords_(x0, y0); - var p1 = this.getCoords_(x1, y1); - var dx = p1.x - p0.x; - var dy = p1.y - p0.y; - angle = Math.atan2(dx, dy) * 180 / Math.PI; - - // The angle should be a non-negative number. - if (angle < 0) { - angle += 360; - } - - // Very small angles produce an unexpected result because they are - // converted to a scientific notation string. - if (angle < 1e-6) { - angle = 0; - } - } else { - var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); - var width = max.x - min.x; - var height = max.y - min.y; - focus = { - x: (p0.x - min.x) / width, - y: (p0.y - min.y) / height - }; - - width /= this.arcScaleX_ * Z; - height /= this.arcScaleY_ * Z; - var dimension = m.max(width, height); - shift = 2 * fillStyle.r0_ / dimension; - expansion = 2 * fillStyle.r1_ / dimension - shift; - } - - // We need to sort the color stops in ascending order by offset, - // otherwise IE won't interpret it correctly. - var stops = fillStyle.colors_; - stops.sort(function(cs1, cs2) { - return cs1.offset - cs2.offset; - }); - - var length = stops.length; - var color1 = stops[0].color; - var color2 = stops[length - 1].color; - var opacity1 = stops[0].alpha * this.globalAlpha; - var opacity2 = stops[length - 1].alpha * this.globalAlpha; - - var colors = []; - for (var i = 0; i < length; i++) { - var stop = stops[i]; - colors.push(stop.offset * expansion + shift + ' ' + stop.color); - } - - // When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - lineStr.push(''); - } else { - lineStr.push(''); - } - - lineStr.push(''); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - }; - - contextPrototype.fill = function() { - this.stroke(true); - } - - contextPrototype.closePath = function() { - this.currentPath_.push({type: 'close'}); - }; - - /** - * @private - */ - contextPrototype.getCoords_ = function(aX, aY) { - var m = this.m_; - return { - x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, - y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 - } - }; - - contextPrototype.save = function() { - var o = {}; - copyState(this, o); - this.aStack_.push(o); - this.mStack_.push(this.m_); - this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); - }; - - contextPrototype.restore = function() { - copyState(this.aStack_.pop(), this); - this.m_ = this.mStack_.pop(); - }; - - function matrixIsFinite(m) { - for (var j = 0; j < 3; j++) { - for (var k = 0; k < 2; k++) { - if (!isFinite(m[j][k]) || isNaN(m[j][k])) { - return false; - } - } - } - return true; - } - - function setM(ctx, m, updateLineScale) { - if (!matrixIsFinite(m)) { - return; - } - ctx.m_ = m; - - if (updateLineScale) { - // Get the line scale. - // Determinant of this.m_ means how much the area is enlarged by the - // transformation. So its square root can be used as a scale factor - // for width. - var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; - ctx.lineScale_ = sqrt(abs(det)); - } - } - - contextPrototype.translate = function(aX, aY) { - var m1 = [ - [1, 0, 0], - [0, 1, 0], - [aX, aY, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.rotate = function(aRot) { - var c = mc(aRot); - var s = ms(aRot); - - var m1 = [ - [c, s, 0], - [-s, c, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.scale = function(aX, aY) { - this.arcScaleX_ *= aX; - this.arcScaleY_ *= aY; - var m1 = [ - [aX, 0, 0], - [0, aY, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { - var m1 = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { - var m = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, m, true); - }; - - /******** STUBS ********/ - contextPrototype.clip = function() { - // TODO: Implement - }; - - contextPrototype.arcTo = function() { - // TODO: Implement - }; - - contextPrototype.createPattern = function() { - return new CanvasPattern_; - }; - - // Gradient / Pattern Stubs - function CanvasGradient_(aType) { - this.type_ = aType; - this.x0_ = 0; - this.y0_ = 0; - this.r0_ = 0; - this.x1_ = 0; - this.y1_ = 0; - this.r1_ = 0; - this.colors_ = []; - } - - CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { - aColor = processStyle(aColor); - this.colors_.push({offset: aOffset, - color: aColor.color, - alpha: aColor.alpha}); - }; - - function CanvasPattern_() {} - - // set up externs - G_vmlCanvasManager = G_vmlCanvasManager_; - CanvasRenderingContext2D = CanvasRenderingContext2D_; - CanvasGradient = CanvasGradient_; - CanvasPattern = CanvasPattern_; - -})(); - -} // if +/** + * jqPlot + * Pure JavaScript plotting plugin using jQuery + * + * Version: @VERSION + * + * Copyright (c) 2009-2011 Chris Leonello + * jqPlot is currently available for use in all personal or commercial projects + * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL + * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can + * choose the license that best suits your project and use it accordingly. + * + * Although not required, the author would appreciate an email letting him + * know of any substantial use of jqPlot. You can reach the author at: + * chris at jqplot dot com or see http://www.jqplot.com/info.php . + * + * If you are feeling kind and generous, consider supporting the project by + * making a donation at: http://www.jqplot.com/donate.php . + * + * sprintf functions contained in jqplot.sprintf.js by Ash Searle: + * + * version 2007.04.27 + * author Ash Searle + * http://hexmen.com/blog/2007/03/printf-sprintf/ + * http://hexmen.com/js/sprintf.js + * The author (Ash Searle) has placed this code in the public domain: + * "This code is unrestricted: you are free to use it however you like." + * + */ +if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&").replace(/"/g,""")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(al){var aj=[];var Z=false;var m=10;var am=10;aj.push("ak.x){ak.x=j.x}if(ag.y==null||j.yak.y){ak.y=j.y}}}aj.push(' ">');if(!al){w(this,aj)}else{G(this,aj,ag,ak)}aj.push("");this.element_.insertAdjacentHTML("beforeEnd",aj.join(""))};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push('','','');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()}; \ No newline at end of file diff -r dc319ece0bd6 -r cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c web/data/jquery.js --- a/web/data/jquery.js Mon May 16 16:24:00 2011 +0200 +++ b/web/data/jquery.js Wed Jul 20 18:21:47 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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.skipTest('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 cc3987eb793c web/test/unittest_request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/test/unittest_request.py Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c 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 Wed Jul 20 18:21:47 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 cc3987eb793c web/test/unittest_web.py --- a/web/test/unittest_web.py Mon May 16 16:24:00 2011 +0200 +++ b/web/test/unittest_web.py Wed Jul 20 18:21:47 2011 +0200 @@ -38,7 +38,7 @@ self.failUnless(url.endswith('()')) cbname = url.split()[1][:-2] self.assertMultiLineEqual( - 'function %s() { $("#foo").loadxhtml("http://testing.fr/cubicweb/json?%s",null,"get","replace"); }' % (cbname, qs), + 'function %s() { $("#foo").loadxhtml("http://testing.fr/cubicweb/json?%s",{"pageid": "%s"},"get","replace"); }' % (cbname, qs, req.pageid), req.html_headers.post_inlined_scripts[0]) if __name__ == '__main__': diff -r dc319ece0bd6 -r cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c 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 cc3987eb793c web/views/autoform.py --- a/web/views/autoform.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/autoform.py Wed Jul 20 18:21:47 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. diff -r dc319ece0bd6 -r cc3987eb793c web/views/basecomponents.py --- a/web/views/basecomponents.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/basecomponents.py Wed Jul 20 18:21:47 2011 +0200 @@ -29,8 +29,8 @@ from logilab.common.deprecation import class_renamed from rql import parse -from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params, - match_context, configuration_values, +from cubicweb.selectors import (yes, match_form_params, match_context, + multi_etypes_rset, configuration_values, anonymous_user, authenticated_user) from cubicweb.schema import display_name from cubicweb.utils import wrap_on_write @@ -88,6 +88,7 @@ class ApplLogo(HeaderComponent): """build the instance logo, usually displayed in the header""" __regid__ = 'logo' + __select__ = yes() # no need for a cnx order = -1 def render(self, w): @@ -150,7 +151,7 @@ class AnonUserStatusLink(HeaderComponent): __regid__ = 'userstatus' - __select__ = HeaderComponent.__select__ & anonymous_user() + __select__ = anonymous_user() context = _('header-right') order = HeaderComponent.order - 10 @@ -159,7 +160,7 @@ class AuthenticatedUserStatus(AnonUserStatusLink): - __select__ = HeaderComponent.__select__ & authenticated_user() + __select__ = authenticated_user() def render(self, w): # display useractions and siteactions @@ -185,9 +186,18 @@ # don't want user to hide this component using an cwproperty cw_property_defs = {} - def call(self): - msgs = [msg for msg in (self._cw.get_shared_data('sources_error', pop=True), - self._cw.message) if msg] + def call(self, msg=None): + if msg is None: + msgs = [] + if self._cw.cnx: + srcmsg = self._cw.get_shared_data('sources_error', pop=True) + if srcmsg: + msgs.append(srcmsg) + reqmsg = self._cw.message # XXX don't call self._cw.message twice + if reqmsg: + msgs.append(reqmsg) + else: + msgs = [msg] self.w(u'
              \n' % (toggle_action('appMsg'), (msgs and ' ' or 'hidden'))) for msg in msgs: diff -r dc319ece0bd6 -r cc3987eb793c web/views/basecontrollers.py --- a/web/views/basecontrollers.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/basecontrollers.py Wed Jul 20 18:21:47 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. @@ -281,7 +281,7 @@ try: args = [json.loads(arg) for arg in args] except ValueError, exc: - self.exception('error while decoding json arguments for js_%s: %s', + self.exception('error while decoding json arguments for js_%s: %s (err: %s)', fname, args, exc) raise RemoteCallFailed(exc_message(exc, self._cw.encoding)) try: @@ -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 @@ -603,16 +603,14 @@ errors = self._cw.cnx.undo_transaction(txuuid) if not errors: self.redirect() - return self._cw._('some errors occurred:') + self.view('pyvalist', - pyvalue=errors) + raise ValidationError(None, {None: '\n'.join(errors)}) - def redirect(self): + def redirect(self, msg=None): req = self._cw + msg = msg or req._("transaction undone") breadcrumbs = req.session.data.get('breadcrumbs', None) if breadcrumbs is not None and len(breadcrumbs) > 1: - url = req.rebuild_url(breadcrumbs[-2], - __message=req._('transaction undoed')) + url = req.rebuild_url(breadcrumbs[-2], __message=msg) else: - url = req.build_url(__message=req._('transaction undoed')) + url = req.build_url(__message=msg) raise Redirect(url) - diff -r dc319ece0bd6 -r cc3987eb793c web/views/basetemplates.py --- a/web/views/basetemplates.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/basetemplates.py Wed Jul 20 18:21:47 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. diff -r dc319ece0bd6 -r cc3987eb793c web/views/baseviews.py --- a/web/views/baseviews.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/baseviews.py Wed Jul 20 18:21:47 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 cc3987eb793c web/views/boxes.py --- a/web/views/boxes.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/boxes.py Wed Jul 20 18:21:47 2011 +0200 @@ -185,7 +185,7 @@ def render_body(self, w): for category, views in box.sort_by_category(self.views): - menu = htmlwidgets.BoxMenu(category) + menu = htmlwidgets.BoxMenu(self._cw._(category)) for view in views: menu.append(self.action_link(view)) self.append(menu) @@ -208,6 +208,7 @@ raise component.EmptyComponent() self.items = [] + class RsetBox(component.CtxComponent): """helper view class to display an rset in a sidebox""" __select__ = nonempty_rset() & match_kwargs('title', 'vid') diff -r dc319ece0bd6 -r cc3987eb793c web/views/cwsources.py --- a/web/views/cwsources.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/cwsources.py Wed Jul 20 18:21:47 2011 +0200 @@ -27,7 +27,7 @@ from cubicweb.selectors import is_instance, score_entity, match_user_groups from cubicweb.view import EntityView, StartupView from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name -from cubicweb.web import uicfg +from cubicweb.web import uicfg, formwidgets as wdgs from cubicweb.web.views import tabs, actions @@ -35,6 +35,12 @@ _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_schema', '*'), False) _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_for_source', '*'), False) +_afs = uicfg.autoform_section +_afs.tag_attribute(('CWSource', 'synchronizing'), 'main', 'hidden') +_afs.tag_object_of(('*', 'cw_for_source', 'CWSource'), 'main', 'hidden') +_affk = uicfg.autoform_field_kwargs +_affk.tag_attribute(('CWSource', 'parser'), {'widget': wdgs.TextInput}) + # source primary views ######################################################### _pvs = uicfg.primaryview_section @@ -126,6 +132,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 +162,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 cc3987eb793c web/views/cwuser.py --- a/web/views/cwuser.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/cwuser.py Wed Jul 20 18:21:47 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 cc3987eb793c web/views/debug.py --- a/web/views/debug.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/debug.py Wed Jul 20 18:21:47 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. @@ -133,6 +133,9 @@ if sessions: w(u'
                ') for session in sessions: + if not session.cnx: + w(u'
              • %s (NO CNX)
              • ' % session.sessionid) + continue try: last_usage_time = session.cnx.check() except BadConnectionId: diff -r dc319ece0bd6 -r cc3987eb793c web/views/formrenderers.py --- a/web/views/formrenderers.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/formrenderers.py Wed Jul 20 18:21:47 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. @@ -40,7 +40,7 @@ from logilab.common import dictattr from logilab.mtconverter import xml_escape -from cubicweb import tags +from cubicweb import tags, uilib from cubicweb.appobject import AppObject from cubicweb.selectors import is_instance, yes from cubicweb.utils import json_dumps, support_args @@ -112,18 +112,21 @@ data = [] _w = data.append _w(self.open_form(form, values)) - if self.display_progress_div: - _w(u'
                %s
                ' % self._cw._('validating...')) - _w(u'\n
                \n') - self.render_fields(_w, form, values) - self.render_buttons(_w, form) - _w(u'\n
                \n') + self.render_content(_w, form, values) _w(self.close_form(form, values)) errormsg = self.error_message(form) if errormsg: data.insert(0, errormsg) w(''.join(data)) + def render_content(self, w, form, values): + if self.display_progress_div: + w(u'
                %s
                ' % self._cw._('validating...')) + w(u'\n
                \n') + self.render_fields(w, form, values) + self.render_buttons(w, form) + w(u'\n
                \n') + def render_label(self, form, field): if field.label is None: return u'' @@ -179,24 +182,25 @@ return u'
                %s
                ' % errormsg return u'' - def open_form(self, form, values): + def open_form(self, form, values, **attrs): if form.needs_multipart: enctype = u'multipart/form-data' else: enctype = u'application/x-www-form-urlencoded' - tag = (u'' + attrs.setdefault('cubicweb:target', form.cwtarget) + return '' % uilib.sgml_attributes(attrs) def close_form(self, form, values): """seems dumb but important for consistency w/ close form, and necessary diff -r dc319ece0bd6 -r cc3987eb793c web/views/ibreadcrumbs.py --- a/web/views/ibreadcrumbs.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/ibreadcrumbs.py Wed Jul 20 18:21:47 2011 +0200 @@ -195,7 +195,7 @@ def cell_call(self, row, col, **kwargs): entity = self.cw_rset.get_entity(row, col) - desc = xml_escape(uilib.cut(entity.dc_description(), 50)) + desc = uilib.cut(entity.dc_description(), 50) # NOTE remember camember: tags.a autoescapes self.w(tags.a(entity.view('breadcrumbtext'), href=entity.absolute_url(), title=desc)) diff -r dc319ece0bd6 -r cc3987eb793c web/views/idownloadable.py --- a/web/views/idownloadable.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/idownloadable.py Wed Jul 20 18:21:47 2011 +0200 @@ -15,8 +15,10 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Specific views for entities adapting to IDownloadable""" - +""" +Specific views for entities adapting to IDownloadable +===================================================== +""" __docformat__ = "restructuredtext en" _ = unicode @@ -50,6 +52,7 @@ class DownloadBox(component.EntityCtxComponent): + """add download box""" __regid__ = 'download_box' # no download box for images __select__ = (component.EntityCtxComponent.__select__ & adaptable('IDownloadable') & ~has_mimetype('image/')) @@ -71,7 +74,9 @@ class DownloadView(EntityView): - """this view is replacing the deprecated 'download' controller and allow + """download view + + this view is replacing the deprecated 'download' controller and allow downloading of entities providing the necessary interface """ __regid__ = 'download' @@ -199,6 +204,7 @@ class ImageView(AbstractEmbeddedView): + """image embedded view""" __regid__ = 'image' __select__ = has_mimetype('image/') @@ -207,6 +213,7 @@ class EHTMLView(AbstractEmbeddedView): + """html embedded view""" __regid__ = 'ehtml' __select__ = has_mimetype('text/html') diff -r dc319ece0bd6 -r cc3987eb793c web/views/iprogress.py --- a/web/views/iprogress.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/iprogress.py Wed Jul 20 18:21:47 2011 +0200 @@ -177,12 +177,11 @@ @classmethod def overrun(cls, iprogress): - """overrun = done + todo - """ done = iprogress.done or 0 todo = iprogress.todo or 0 - revised_cost = iprogress.revised_cost or 0 - if done + todo > revised_cost: - overrun = done + todo - revised_cost + budget = iprogress.revised_cost or 0 + if done + todo > budget: + overrun = done + todo - budget else: overrun = 0 if overrun < cls.precision: @@ -191,11 +190,10 @@ @classmethod def overrun_percentage(cls, iprogress): - """pourcentage overrun = overrun / budget""" - revised_cost = iprogress.revised_cost or 0 - if revised_cost == 0: + budget = iprogress.revised_cost or 0 + if budget == 0: return 0 - return cls.overrun(iprogress) * 100. / revised_cost + return cls.overrun(iprogress) * 100. / budget def cell_call(self, row, col): self._cw.add_css('cubicweb.iprogress.css') diff -r dc319ece0bd6 -r cc3987eb793c web/views/owl.py --- a/web/views/owl.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/owl.py Wed Jul 20 18:21:47 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 cc3987eb793c web/views/plots.py --- a/web/views/plots.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/plots.py Wed Jul 20 18:21:47 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'): @@ -82,15 +82,17 @@ class FlotPlotWidget(PlotWidget): """PlotRenderer widget using Flot""" onload = u""" -var fig = jQuery("#%(figid)s"); +var fig = jQuery('#%(figid)s'); if (fig.attr('cubicweb:type') != 'prepared-plot') { %(plotdefs)s - jQuery.plot(jQuery("#%(figid)s"), [%(plotdata)s], + jQuery.plot(jQuery('#%(figid)s'), [%(plotdata)s], {points: {show: true}, lines: {show: true}, grid: {hoverable: true}, + /*yaxis : {tickFormatter : suffixFormatter},*/ xaxis: {mode: %(mode)s}}); - jQuery("#%(figid)s").bind("plothover", onPlotHover); + jQuery('#%(figid)s').data({mode: %(mode)s, dateformat: %(dateformat)s}); + jQuery('#%(figid)s').bind('plothover', onPlotHover); fig.attr('cubicweb:type','prepared-plot'); } """ @@ -101,11 +103,8 @@ self.timemode = timemode def dump_plot(self, plot): - # XXX for now, the only way that we have to customize properly - # datetime labels on tooltips is to insert an additional column - # cf. function onPlotHover in cubicweb.flot.js if self.timemode: - plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x, y in plot] + plot = [(datetime2ticks(x), y) for x, y in plot] return json_dumps(plot) def _render(self, req, width=500, height=400): @@ -122,11 +121,14 @@ plotdefs.append('var %s = %s;' % (plotid, self.dump_plot(plot))) # XXX ugly but required in order to not crash my demo plotdata.append("{label: '%s', data: %s}" % (label.replace(u'&', u''), plotid)) + fmt = req.property_value('ui.date-format') # XXX datetime-format + # XXX TODO make plot options customizable req.html_headers.add_onload(self.onload % {'plotdefs': '\n'.join(plotdefs), 'figid': figid, 'plotdata': ','.join(plotdata), - 'mode': self.timemode and "'time'" or 'null'}) + 'mode': self.timemode and "'time'" or 'null', + 'dateformat': '"%s"' % fmt}) class PlotView(baseviews.AnyRsetView): @@ -134,6 +136,7 @@ title = _('generic plot') __select__ = multi_columns_rset() & all_columns_are_numbers() timemode = False + paginable = False def call(self, width=500, height=400): # prepare data @@ -181,6 +184,7 @@ class PieChartView(baseviews.AnyRsetView): __regid__ = 'piechart' pieclass = Pie + paginable = False __select__ = multi_columns_rset() & second_column_is_number() @@ -196,7 +200,7 @@ for rowidx, (_, value) in enumerate(self.cw_rset): if value is not None: vid = self._guess_vid(rowidx) - label = '%s: %s' % (self.view(vid, self.cw_rset, row=rowidx, col=0), + label = '%s: %s' % (self._cw.view(vid, self.cw_rset, row=rowidx, col=0), value) labels.append(label.encode(self._cw.encoding)) values.append(value) diff -r dc319ece0bd6 -r cc3987eb793c web/views/rdf.py --- a/web/views/rdf.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/rdf.py Wed Jul 20 18:21:47 2011 +0200 @@ -59,6 +59,9 @@ self.entity2graph(graph, entity) self.w(graph.serialize().decode('utf-8')) + def entity_call(self, entity): + self.call() + def entity2graph(self, graph, entity): cwuri = URIRef(entity.cwuri) add = graph.add diff -r dc319ece0bd6 -r cc3987eb793c web/views/reledit.py --- a/web/views/reledit.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/reledit.py Wed Jul 20 18:21:47 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'
              ') - w(u'
              ') for tabid, domid, tabkwargs in tabs: w(u'
              ' % domid) tabkwargs.setdefault('tabid', domid) @@ -156,11 +155,12 @@ tabkwargs.setdefault('rset', self.cw_rset) self.lazyview(**tabkwargs) w(u'
              ') + w(u'
          ') # call the setTab() JS function *after* each tab is generated # because the callback binding needs to be done before # XXX make work history: true self._cw.add_onload(u""" - jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s }); + jQuery('#entity-tabs-%(eeid)s').tabs( { selected: %(tabindex)s }); setTab('%(domid)s', '%(cookiename)s'); """ % {'tabindex' : active_tab_idx, 'domid' : active_tab, diff -r dc319ece0bd6 -r cc3987eb793c web/views/workflow.py --- a/web/views/workflow.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/workflow.py Wed Jul 20 18:21:47 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. @@ -30,7 +30,7 @@ from logilab.mtconverter import xml_escape from logilab.common.graph import escape -from cubicweb import Unauthorized, view +from cubicweb import Unauthorized from cubicweb.selectors import (has_related_entities, one_line_rset, relation_possible, match_form_params, score_entity, is_instance, adaptable) @@ -39,7 +39,7 @@ from cubicweb.web import uicfg, stdmsgs, action, component, form, action from cubicweb.web import formfields as ff, formwidgets as fwdgs from cubicweb.web.views import TmpFileViewMixin -from cubicweb.web.views import forms, primary, autoform, ibreadcrumbs +from cubicweb.web.views import forms, primary, ibreadcrumbs from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler @@ -72,11 +72,7 @@ _abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True) _afs = uicfg.autoform_section -_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden') -_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden') -_afs.tag_attribute(('TrInfo', 'tr_count'), 'main', 'hidden') -_afs.tag_object_of(('State', 'allowed_transition', '*'), 'main', 'attributes') - +_affk = uicfg.autoform_field_kwargs # IWorkflowable views ######################################################### @@ -90,7 +86,7 @@ fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] -class ChangeStateFormView(form.FormViewMixIn, view.EntityView): +class ChangeStateFormView(form.FormViewMixIn, EntityView): __regid__ = 'statuschange' title = _('status change') __select__ = (one_line_rset() @@ -114,7 +110,7 @@ def get_form(self, entity, transition, **kwargs): # XXX used to specify both rset/row/col and entity in case implements - # selector (and not implements) is used on custom form + # selector (and not is_instance) is used on custom form form = self._cw.vreg['forms'].select( 'changestate', self._cw, entity=entity, transition=transition, redirect_path=self.redirectpath(entity), **kwargs) @@ -183,6 +179,15 @@ self.entity.view('wfhistory', w=w, title=None) +class InContextWithStateView(EntityView): + """display incontext view for an entity as well as its current state""" + __regid__ = 'incontext-state' + __select__ = adaptable('IWorkflowable') + def entity_call(self, entity): + iwf = entity.cw_adapt_to('IWorkflowable') + self.w(u'%s [%s]' % (entity.view('incontext'), iwf.printable_state)) + + # workflow actions ############################################################# class WorkflowActions(action.Action): @@ -242,7 +247,7 @@ default_tab = 'wf_tab_info' -class CellView(view.EntityView): +class CellView(EntityView): __regid__ = 'cell' __select__ = is_instance('TrInfo') @@ -250,7 +255,7 @@ self.w(self.cw_rset.get_entity(row, col).view('reledit', rtype='comment')) -class StateInContextView(view.EntityView): +class StateInContextView(EntityView): """convenience trick, State's incontext view should not be clickable""" __regid__ = 'incontext' __select__ = is_instance('State') @@ -285,7 +290,7 @@ ) -class TransitionSecurityTextView(view.EntityView): +class TransitionSecurityTextView(EntityView): __regid__ = 'trsecurity' __select__ = is_instance('Transition') @@ -303,7 +308,7 @@ u'
          '.join((e.dc_title() for e in entity.condition)))) -class TransitionAllowedTextView(view.EntityView): +class TransitionAllowedTextView(EntityView): __regid__ = 'trfromstates' __select__ = is_instance('Transition') @@ -315,51 +320,56 @@ # workflow entity types edition ################################################ -_afs = uicfg.autoform_section +def _wf_items_for_relation(req, wfeid, wfrelation, field): + wf = req.entity_from_eid(wfeid) + rschema = req.vreg.schema[field.name] + param = 'toeid' if field.role == 'subject' else 'fromeid' + return sorted((e.view('combobox'), e.eid) + for e in getattr(wf, 'reverse_%s' % wfrelation) + if rschema.has_perm(req, 'add', **{param: e.eid})) + +# TrInfo _afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden') _afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden') -_afs.tag_object_of(('State', 'allowed_transition', '*'), 'main', 'attributes') -_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'main', 'attributes') +_afs.tag_attribute(('TrInfo', 'tr_count'), 'main', 'hidden') + +# BaseTransition +# XXX * allowed_transition BaseTransition +# XXX BaseTransition destination_state * + +def transition_states_vocabulary(form, field): + entity = form.edited_entity + if not entity.has_eid(): + eids = entity.linked_to('transition_of', 'subject') + if not eids: + return [] + return _wf_items_for_relation(form._cw, eids[0], 'state_of', field) + return ff.relvoc_unrelated(entity, field.name, field.role) -def workflow_items_for_relation(req, wfeid, wfrelation, targetrelation): - wf = req.entity_from_eid(wfeid) - rschema = req.vreg.schema[targetrelation] - return sorted((e.view('combobox'), e.eid) - for e in getattr(wf, 'reverse_%s' % wfrelation) - if rschema.has_perm(req, 'add', toeid=e.eid)) +_afs.tag_subject_of(('*', 'destination_state', '*'), 'main', 'attributes') +_affk.tag_subject_of(('*', 'destination_state', '*'), + {'choices': transition_states_vocabulary}) +_afs.tag_object_of(('*', 'allowed_transition', '*'), 'main', 'attributes') +_affk.tag_object_of(('*', 'allowed_transition', '*'), + {'choices': transition_states_vocabulary}) + +# State + +def state_transitions_vocabulary(form, field): + entity = form.edited_entity + if not entity.has_eid(): + eids = entity.linked_to('state_of', 'subject') + if eids: + return _wf_items_for_relation(form._cw, eids[0], 'transition_of', field) + return [] + return ff.relvoc_unrelated(entity, field.name, field.role) + +_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'main', 'attributes') +_affk.tag_subject_of(('State', 'allowed_transition', '*'), + {'choices': state_transitions_vocabulary}) -class TransitionEditionForm(autoform.AutomaticEntityForm): - __select__ = is_instance('Transition') - - def workflow_states_for_relation(self, targetrelation): - eids = self.edited_entity.linked_to('transition_of', 'subject') - if eids: - return workflow_items_for_relation(self._cw, eids[0], 'state_of', - targetrelation) - return [] - - def subject_destination_state_vocabulary(self, rtype, limit=None): - if not self.edited_entity.has_eid(): - return self.workflow_states_for_relation('destination_state') - return self.subject_relation_vocabulary(rtype, limit) - - def object_allowed_transition_vocabulary(self, rtype, limit=None): - if not self.edited_entity.has_eid(): - return self.workflow_states_for_relation('allowed_transition') - return self.object_relation_vocabulary(rtype, limit) - - -class StateEditionForm(autoform.AutomaticEntityForm): - __select__ = is_instance('State') - - def subject_allowed_transition_vocabulary(self, rtype, limit=None): - if not self.edited_entity.has_eid(): - eids = self.edited_entity.linked_to('state_of', 'subject') - if eids: - return workflow_items_for_relation(self._cw, eids[0], 'transition_of', - 'allowed_transition') - return [] +# adaptaters ################################################################### class WorkflowIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter): __select__ = is_instance('Workflow') @@ -434,7 +444,7 @@ return WorkflowDotPropsHandler(self._cw) -class TmpPngView(TmpFileViewMixin, view.EntityView): +class TmpPngView(TmpFileViewMixin, EntityView): __regid__ = 'tmppng' __select__ = match_form_params('tmpfile') content_type = 'image/png' diff -r dc319ece0bd6 -r cc3987eb793c web/views/xmlrss.py --- a/web/views/xmlrss.py Mon May 16 16:24:00 2011 +0200 +++ b/web/views/xmlrss.py Wed Jul 20 18:21:47 2011 +0200 @@ -42,6 +42,8 @@ 'Date': lambda x: x.strftime('%Y-%m-%d'), 'Datetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), 'Time': lambda x: x.strftime('%H:%M:%S'), + 'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ + 'TZTime': lambda x: x.strftime('%H:%M:%S'), 'Interval': lambda x: x.days * 60*60*24 + x.seconds, }