# HG changeset patch # User Sylvain Thénault # Date 1317300424 -7200 # Node ID ad0eeb0f7a8d84ce2ff6901b03a89a40c535dd86 # Parent 6cebeb1f386afdd71420a218b983e964096df21c# Parent 8da3caff3291ae1a5115c7243e43647a445e7ca6 backport stable diff -r 6cebeb1f386a -r ad0eeb0f7a8d .hgtags --- a/.hgtags Thu Sep 29 14:07:37 2011 +0200 +++ b/.hgtags Thu Sep 29 14:47:04 2011 +0200 @@ -227,3 +227,5 @@ 9dfd21fa0a8b9f121a08866ad3e2ebd1dd06790d cubicweb-debian-version-3.12.10-1 17c007ad845abbac82e12146abab32a634657574 cubicweb-version-3.13.6 8a8949ca5351d48c5cf795ccdff06c1d4aab2ce0 cubicweb-debian-version-3.13.6-1 +68e8c81fa96d6bcd21cc17bc9832d388ce05a9eb cubicweb-version-3.13.7 +2f93ce32febe2f82565994fbd454f331f76ca883 cubicweb-debian-version-3.13.7-1 diff -r 6cebeb1f386a -r ad0eeb0f7a8d __init__.py --- a/__init__.py Thu Sep 29 14:07:37 2011 +0200 +++ b/__init__.py Thu Sep 29 14:47:04 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 6cebeb1f386a -r ad0eeb0f7a8d __pkginfo__.py diff -r 6cebeb1f386a -r ad0eeb0f7a8d appobject.py --- a/appobject.py Thu Sep 29 14:07:37 2011 +0200 +++ b/appobject.py Thu Sep 29 14:47:04 2011 +0200 @@ -324,7 +324,7 @@ selected according to a context (usually at least a request and a result set). - The following attributes should be set on concret appobject classes: + The following attributes should be set on concrete appobject classes: :attr:`__registry__` name of the registry for this object (string like 'views', @@ -415,7 +415,7 @@ appobject is returned without any transformation. """ try: # XXX < 3.6 bw compat - pdefs = cls.property_defs + pdefs = cls.property_defs # pylint: disable=E1101 except AttributeError: pdefs = getattr(cls, 'cw_property_defs', {}) else: diff -r 6cebeb1f386a -r ad0eeb0f7a8d crypto.py --- a/crypto.py Thu Sep 29 14:07:37 2011 +0200 +++ b/crypto.py Thu Sep 29 14:47:04 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,9 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Simple cryptographic routines, based on python-crypto. - -""" +"""Simple cryptographic routines, based on python-crypto.""" __docformat__ = "restructuredtext en" from pickle import dumps, loads diff -r 6cebeb1f386a -r ad0eeb0f7a8d cwconfig.py --- a/cwconfig.py Thu Sep 29 14:07:37 2011 +0200 +++ b/cwconfig.py Thu Sep 29 14:47:04 2011 +0200 @@ -830,6 +830,13 @@ """ return [self.cube_dir(p) for p in self.cubes()] + # these are overridden by set_log_methods below + # only defining here to prevent pylint from complaining + @classmethod + def debug(cls, msg, *a, **kw): + pass + info = warning = error = critical = exception = debug + class CubicWebConfiguration(CubicWebNoAppConfiguration): """base class for cubicweb server and web configurations""" @@ -853,6 +860,9 @@ # wouldn't be possible otherwise repairing = False + # set by upgrade command + verbosity = 0 + options = CubicWebNoAppConfiguration.options + ( ('log-file', {'type' : 'string', @@ -1072,13 +1082,13 @@ @cached def instance_md5_version(self): - import hashlib + from hashlib import md5 # pylint: disable=E0611 infos = [] 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() + return md5(';'.join(infos)).hexdigest() def load_configuration(self): """load instance's configuration files""" @@ -1188,13 +1198,6 @@ SMTP_LOCK.release() return True - # these are overridden by set_log_methods below - # only defining here to prevent pylint from complaining - @classmethod - def debug(cls, msg, *a, **kw): - pass - info = warning = error = critical = exception = debug - set_log_methods(CubicWebNoAppConfiguration, logging.getLogger('cubicweb.configuration')) @@ -1303,7 +1306,7 @@ try: return Binary(fpath) except OSError, ex: - self.critical("can't open %s: %s", fpath, ex) + source.critical("can't open %s: %s", fpath, ex) return None register_function(FSPATH) diff -r 6cebeb1f386a -r ad0eeb0f7a8d dbapi.py --- a/dbapi.py Thu Sep 29 14:07:37 2011 +0200 +++ b/dbapi.py Thu Sep 29 14:47:04 2011 +0200 @@ -311,7 +311,7 @@ self.user = user self.set_entity_cache(user) - def execute(self, *args, **kwargs): + def execute(self, *args, **kwargs): # pylint: disable=E0202 """overriden when session is set. By default raise authentication error so authentication is requested. """ @@ -640,7 +640,8 @@ """ return self._repo.check_session(self.sessionid) - def _txid(self, cursor=None): # XXX could now handle various isolation level! + def _txid(self, cursor=None): # pylint: disable=E0202 + # XXX could now handle various isolation level! # return a dict as bw compat trick return {'txid': currentThread().getName()} diff -r 6cebeb1f386a -r ad0eeb0f7a8d debian/changelog --- a/debian/changelog Thu Sep 29 14:07:37 2011 +0200 +++ b/debian/changelog Thu Sep 29 14:47:04 2011 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.13.7-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Thu, 29 Sep 2011 14:08:07 +0200 + cubicweb (3.13.6-1) unstable; urgency=low * new upstream release diff -r 6cebeb1f386a -r ad0eeb0f7a8d devtools/__init__.py --- a/devtools/__init__.py Thu Sep 29 14:07:37 2011 +0200 +++ b/devtools/__init__.py Thu Sep 29 14:47:04 2011 +0200 @@ -28,7 +28,7 @@ import pickle import glob import warnings -import hashlib +from hashlib import sha1 # pylint: disable=E0611 from datetime import timedelta from os.path import (abspath, join, exists, basename, dirname, normpath, split, isfile, isabs, splitext, isdir, expanduser) @@ -598,7 +598,7 @@ @property def _config_id(self): - return hashlib.sha1(self.config.apphome).hexdigest()[:10] + return sha1(self.config.apphome).hexdigest()[:10] def _backup_name(self, db_id): # merge me with parent backup_name = '_'.join(('cache', self._config_id, self.dbname, db_id)) diff -r 6cebeb1f386a -r ad0eeb0f7a8d devtools/livetest.py --- a/devtools/livetest.py Thu Sep 29 14:07:37 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -# copyright 2003-2010 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 . -"""provide utilies for web (live) unit testing - -""" - -import os -import socket -import logging -from os.path import join, dirname, normpath, abspath -from StringIO import StringIO - -#from twisted.application import service, strports -# from twisted.internet import reactor, task -from twisted.web2 import channel -from twisted.web2 import server -from twisted.web2 import static -from twisted.internet import reactor -from twisted.internet.error import CannotListenError - -from logilab.common.testlib import TestCase - -from cubicweb.dbapi import in_memory_repo_cnx -from cubicweb.etwist.server import CubicWebRootResource -from cubicweb.devtools import BaseApptestConfiguration, init_test_database - - - -def get_starturl(port=7777, login=None, passwd=None): - if login: - return 'http://%s:%s/view?login=%s&password=%s' % (socket.gethostname(), port, login, passwd) - else: - return 'http://%s:%s/' % (socket.gethostname(), port) - - -class LivetestResource(CubicWebRootResource): - """redefines main resource to search for data files in several directories""" - - def locateChild(self, request, segments): - """Indicate which resource to use to process down the URL's path""" - if len(segments) and segments[0] == 'data': - # Anything in data/ is treated as static files - datadir = self.config.locate_resource(segments[1])[0] - if datadir: - return static.File(str(datadir), segments[1:]) - # Otherwise we use this single resource - return self, () - - - -class LivetestConfiguration(BaseApptestConfiguration): - init_repository = False - - def __init__(self, cube=None, sourcefile=None, pyro_name=None, - log_threshold=logging.CRITICAL): - BaseApptestConfiguration.__init__(self, cube, log_threshold=log_threshold) - self.appid = pyro_name or cube - # don't change this, else some symlink problems may arise in some - # environment (e.g. mine (syt) ;o) - # XXX I'm afraid this test will prevent to run test from a production - # environment - self._sources = None - # instance cube test - if cube is not None: - self.apphome = self.cube_dir(cube) - elif 'web' in os.getcwd().split(os.sep): - # web test - self.apphome = join(normpath(join(dirname(__file__), '..')), 'web') - else: - # cube test - self.apphome = abspath('..') - self.sourcefile = sourcefile - self.global_set_option('realm', '') - self.use_pyro = pyro_name is not None - - def pyro_enabled(self): - if self.use_pyro: - return True - else: - return False - - - -def make_site(cube, options=None): - from cubicweb.etwist import twconfig # trigger configuration registration - config = LivetestConfiguration(cube, options.sourcefile, - pyro_name=options.pyro_name, - log_threshold=logging.DEBUG) - init_test_database(config=config) - # if '-n' in sys.argv: # debug mode - cubicweb = LivetestResource(config, debug=True) - toplevel = cubicweb - website = server.Site(toplevel) - cube_dir = config.cube_dir(cube) - source = config.sources()['system'] - for port in xrange(7777, 7798): - try: - reactor.listenTCP(port, channel.HTTPFactory(website)) - saveconf(cube_dir, port, source['db-user'], source['db-password']) - break - except CannotListenError: - print "port %s already in use, I will try another one" % port - else: - raise - cubicweb.base_url = get_starturl(port=port) - print "you can go here : %s" % cubicweb.base_url - -def runserver(): - reactor.run() - -def saveconf(templhome, port, user, passwd): - import pickle - conffile = file(join(templhome, 'test', 'livetest.conf'), 'w') - - pickle.dump((port, user, passwd, get_starturl(port, user, passwd)), - conffile) - conffile.close() - - -def loadconf(filename='livetest.conf'): - import pickle - return pickle.load(file(filename)) - - -def execute_scenario(filename, **kwargs): - """based on twill.parse.execute_file, but inserts cubicweb extensions""" - from twill.parse import _execute_script - stream = StringIO('extend_with cubicweb.devtools.cubicwebtwill\n' + file(filename).read()) - kwargs['source'] = filename - _execute_script(stream, **kwargs) - - -def hijack_twill_output(new_output): - from twill import commands as twc - from twill import browser as twb - twc.OUT = new_output - twb.OUT = new_output - - -class LiveTestCase(TestCase): - - sourcefile = None - cube = '' - def setUp(self): - assert self.cube, "You must specify a cube in your testcase" - # twill can be quite verbose ... - self.twill_output = StringIO() - hijack_twill_output(self.twill_output) - # build a config, and get a connection - self.config = LivetestConfiguration(self.cube, self.sourcefile) - _, user, passwd, _ = loadconf() - self.repo, self.cnx = in_memory_repo_cnx(self.config, user, password=passwd) - self.setup_db(self.cnx) - - def tearDown(self): - self.teardown_db(self.cnx) - - - def setup_db(self, cnx): - """override setup_db() to setup your environment""" - - def teardown_db(self, cnx): - """override teardown_db() to clean up your environment""" - - def get_loggedurl(self): - port, user, passwd, logged_url = loadconf() - return logged_url - - def get_anonurl(self): - port, _, _, _ = loadconf() - return 'http://%s:%s/view?login=anon&password=anon' % ( - socket.gethostname(), port) - - # convenience - execute_scenario = staticmethod(execute_scenario) - - -if __name__ == '__main__': - runserver() diff -r 6cebeb1f386a -r ad0eeb0f7a8d devtools/qunit.py --- a/devtools/qunit.py Thu Sep 29 14:07:37 2011 +0200 +++ b/devtools/qunit.py Thu Sep 29 14:47:04 2011 +0200 @@ -1,3 +1,21 @@ +# copyright 2010-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 os, os.path as osp import signal from tempfile import mkdtemp, NamedTemporaryFile, TemporaryFile diff -r 6cebeb1f386a -r ad0eeb0f7a8d devtools/repotest.py --- a/devtools/repotest.py Thu Sep 29 14:07:37 2011 +0200 +++ b/devtools/repotest.py Thu Sep 29 14:47:04 2011 +0200 @@ -148,8 +148,7 @@ from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions class RQLGeneratorTC(TestCase): - schema = backend = None # set this in concret test - + schema = backend = None # set this in concrete class @classmethod def setUpClass(cls): @@ -197,7 +196,7 @@ class BaseQuerierTC(TestCase): - repo = None # set this in concret test + repo = None # set this in concrete class def setUp(self): self.o = self.repo.querier diff -r 6cebeb1f386a -r ad0eeb0f7a8d devtools/testlib.py --- a/devtools/testlib.py Thu Sep 29 14:07:37 2011 +0200 +++ b/devtools/testlib.py Thu Sep 29 14:47:04 2011 +0200 @@ -1142,34 +1142,34 @@ pass -def vreg_instrumentize(testclass): - # XXX broken - from cubicweb.devtools.apptest import TestEnvironment - env = testclass._env = TestEnvironment('data', configcls=testclass.configcls) - for reg in env.vreg.values(): - reg._selected = {} - try: - orig_select_best = reg.__class__.__orig_select_best - except Exception: - orig_select_best = reg.__class__._select_best - def instr_select_best(self, *args, **kwargs): - selected = orig_select_best(self, *args, **kwargs) - try: - self._selected[selected.__class__] += 1 - except KeyError: - self._selected[selected.__class__] = 1 - except AttributeError: - pass # occurs on reg used to restore database - return selected - reg.__class__._select_best = instr_select_best - reg.__class__.__orig_select_best = orig_select_best +# def vreg_instrumentize(testclass): +# # XXX broken +# from cubicweb.devtools.apptest import TestEnvironment +# env = testclass._env = TestEnvironment('data', configcls=testclass.configcls) +# for reg in env.vreg.values(): +# reg._selected = {} +# try: +# orig_select_best = reg.__class__.__orig_select_best +# except Exception: +# orig_select_best = reg.__class__._select_best +# def instr_select_best(self, *args, **kwargs): +# selected = orig_select_best(self, *args, **kwargs) +# try: +# self._selected[selected.__class__] += 1 +# except KeyError: +# self._selected[selected.__class__] = 1 +# except AttributeError: +# pass # occurs on reg used to restore database +# return selected +# reg.__class__._select_best = instr_select_best +# reg.__class__.__orig_select_best = orig_select_best -def print_untested_objects(testclass, skipregs=('hooks', 'etypes')): - for regname, reg in testclass._env.vreg.iteritems(): - if regname in skipregs: - continue - for appobjects in reg.itervalues(): - for appobject in appobjects: - if not reg._selected.get(appobject): - print 'not tested', regname, appobject +# def print_untested_objects(testclass, skipregs=('hooks', 'etypes')): +# for regname, reg in testclass._env.vreg.iteritems(): +# if regname in skipregs: +# continue +# for appobjects in reg.itervalues(): +# for appobject in appobjects: +# if not reg._selected.get(appobject): +# print 'not tested', regname, appobject diff -r 6cebeb1f386a -r ad0eeb0f7a8d entities/adapters.py --- a/entities/adapters.py Thu Sep 29 14:07:37 2011 +0200 +++ b/entities/adapters.py Thu Sep 29 14:47:04 2011 +0200 @@ -366,8 +366,8 @@ class IProgressAdapter(EntityAdapter): """something that has a cost, a state and a progression. - You should at least override progress_info an in_progress methods on concret - implementations. + You should at least override progress_info an in_progress methods on + concrete implementations. """ __needs_bw_compat__ = True __regid__ = 'IProgress' diff -r 6cebeb1f386a -r ad0eeb0f7a8d entities/wfobjs.py --- a/entities/wfobjs.py Thu Sep 29 14:07:37 2011 +0200 +++ b/entities/wfobjs.py Thu Sep 29 14:47:04 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 6cebeb1f386a -r ad0eeb0f7a8d etwist/request.py --- a/etwist/request.py Thu Sep 29 14:07:37 2011 +0200 +++ b/etwist/request.py Thu Sep 29 14:47:04 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 6cebeb1f386a -r ad0eeb0f7a8d etwist/server.py --- a/etwist/server.py Thu Sep 29 14:07:37 2011 +0200 +++ b/etwist/server.py Thu Sep 29 14:47:04 2011 +0200 @@ -28,7 +28,7 @@ import traceback import threading import re -import hashlib +from hashlib import md5 # pylint: disable=E0611 from os.path import join from time import mktime from datetime import date, timedelta @@ -77,6 +77,11 @@ class NoListingFile(static.File): + def __init__(self, config, path=None): + if path is None: + path = config.static_directory + static.File.__init__(self, path) + self.config = config def set_expires(self, request): if not self.config.debugmode: @@ -93,8 +98,7 @@ class DataLookupDirectory(NoListingFile): def __init__(self, config, path): self.md5_version = config.instance_md5_version() - NoListingFile.__init__(self, path) - self.config = config + NoListingFile.__init__(self, config, path) self.here = path self._defineChildResources() if self.config.debugmode: @@ -134,13 +138,10 @@ return resource else: self.set_expires(request) - return NoListingFile(filepath) + return NoListingFile(self.config, filepath) class FCKEditorResource(NoListingFile): - def __init__(self, config, path): - NoListingFile.__init__(self, path) - self.config = config def getChild(self, path, request): pre_path = request.path.split('/')[1:] @@ -179,7 +180,7 @@ # create a unique / predictable filename. We don't consider cubes # version since uicache is cleared at server startup, and file's dates # are checked in debug mode - fname = 'cache_concat_' + hashlib.md5(';'.join(paths)).hexdigest() + ext + fname = 'cache_concat_' + md5(';'.join(paths)).hexdigest() + ext filepath = osp.join(config.appdatahome, 'uicache', fname) LongTimeExpiringFile.__init__(self, config, filepath) self._concat_cached_filepath(filepath, paths) @@ -239,7 +240,7 @@ self.https_url = config['https-url'] global MAX_POST_LENGTH MAX_POST_LENGTH = config['max-post-length'] - self.putChild('static', NoListingFile(config.static_directory)) + self.putChild('static', NoListingFile(config)) self.putChild('fckeditor', FCKEditorResource(self.config, '')) self.putChild('data', DataLookupDirectory(self.config, '')) @@ -402,6 +403,13 @@ stream=content, code=code, headers=request.headers_out) + # these are overridden by set_log_methods below + # only defining here to prevent pylint from complaining + @classmethod + def debug(cls, msg, *a, **kw): + pass + info = warning = error = critical = exception = debug + JSON_PATHS = set(('json',)) FRAME_POST_PATHS = set(('validateform',)) diff -r 6cebeb1f386a -r ad0eeb0f7a8d ext/rest.py --- a/ext/rest.py Thu Sep 29 14:07:37 2011 +0200 +++ b/ext/rest.py Thu Sep 29 14:47:04 2011 +0200 @@ -187,7 +187,7 @@ try: from pygments import highlight from pygments.lexers import get_lexer_by_name - from pygments.formatters import HtmlFormatter + from pygments.formatters.html import HtmlFormatter except ImportError: pygments_directive = None else: diff -r 6cebeb1f386a -r ad0eeb0f7a8d hooks/metadata.py --- a/hooks/metadata.py Thu Sep 29 14:07:37 2011 +0200 +++ b/hooks/metadata.py Thu Sep 29 14:47:04 2011 +0200 @@ -150,6 +150,8 @@ # entity source handling ####################################################### class ChangeEntityUpdateCaches(hook.Operation): + oldsource = newsource = entity = None # make pylint happy + def postcommit_event(self): self.oldsource.reset_caches() repo = self.session.repo diff -r 6cebeb1f386a -r ad0eeb0f7a8d hooks/notification.py --- a/hooks/notification.py Thu Sep 29 14:07:37 2011 +0200 +++ b/hooks/notification.py Thu Sep 29 14:47:04 2011 +0200 @@ -28,6 +28,8 @@ class RenderAndSendNotificationView(hook.Operation): """delay rendering of notification view until precommit""" + view = None # make pylint happy + def precommit_event(self): view = self.view if view.cw_rset is not None and not view.cw_rset: diff -r 6cebeb1f386a -r ad0eeb0f7a8d hooks/syncschema.py --- a/hooks/syncschema.py Thu Sep 29 14:07:37 2011 +0200 +++ b/hooks/syncschema.py Thu Sep 29 14:47:04 2011 +0200 @@ -246,6 +246,7 @@ CWAttribute entities * add owned_by relation by creating the necessary CWRelation entity """ + entity = None # make pylint happy def precommit_event(self): session = self.session @@ -759,6 +760,8 @@ class MemSchemaCWETypeDel(MemSchemaOperation): """actually remove the entity type from the instance's schema""" + etype = None # make pylint happy + def postcommit_event(self): # del_entity_type also removes entity's relations self.session.vreg.schema.del_entity_type(self.etype) @@ -766,6 +769,8 @@ class MemSchemaCWRTypeAdd(MemSchemaOperation): """actually add the relation type to the instance's schema""" + rtypedef = None # make pylint happy + def precommit_event(self): self.session.vreg.schema.add_relation_type(self.rtypedef) @@ -775,6 +780,8 @@ class MemSchemaCWRTypeDel(MemSchemaOperation): """actually remove the relation type from the instance's schema""" + rtype = None # make pylint happy + def postcommit_event(self): try: self.session.vreg.schema.del_relation_type(self.rtype) @@ -786,6 +793,7 @@ class MemSchemaPermissionAdd(MemSchemaOperation): """synchronize schema when a *_permission relation has been added on a group """ + eid = action = group_eid = expr = None # make pylint happy def precommit_event(self): """the observed connections.cnxset has been commited""" @@ -796,7 +804,7 @@ self.warning('no schema for %s', self.eid) return perms = list(erschema.action_permissions(self.action)) - if hasattr(self, 'group_eid'): + if self.group_eid is not None: perm = self.session.entity_from_eid(self.group_eid).name else: perm = erschema.rql_expression(self.expr) @@ -830,7 +838,7 @@ self.action in ('delete', 'add'): # XXX 3.6.1 migration return perms = list(erschema.action_permissions(self.action)) - if hasattr(self, 'group_eid'): + if self.group_eid is not None: perm = self.session.entity_from_eid(self.group_eid).name else: perm = erschema.rql_expression(self.expr) @@ -845,6 +853,7 @@ class MemSchemaSpecializesAdd(MemSchemaOperation): + etypeeid = parentetypeeid = None # make pylint happy def precommit_event(self): eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid) @@ -856,6 +865,7 @@ class MemSchemaSpecializesDel(MemSchemaOperation): + etypeeid = parentetypeeid = None # make pylint happy def precommit_event(self): try: diff -r 6cebeb1f386a -r ad0eeb0f7a8d hooks/syncsession.py --- a/hooks/syncsession.py Thu Sep 29 14:07:37 2011 +0200 +++ b/hooks/syncsession.py Thu Sep 29 14:47:04 2011 +0200 @@ -40,7 +40,8 @@ class _GroupOperation(hook.Operation): """base class for group operation""" - geid = None + cnxuser = None # make pylint happy + def __init__(self, session, *args, **kwargs): """override to get the group name before actual groups manipulation: @@ -55,6 +56,7 @@ class _DeleteGroupOp(_GroupOperation): """synchronize user when a in_group relation has been deleted""" + def postcommit_event(self): """the observed connections set has been commited""" groups = self.cnxuser.groups @@ -117,9 +119,9 @@ # CWProperty hooks ############################################################# - class _DelCWPropertyOp(hook.Operation): """a user's custom properties has been deleted""" + cwpropdict = key = None # make pylint happy def postcommit_event(self): """the observed connections set has been commited""" @@ -131,6 +133,7 @@ class _ChangeCWPropertyOp(hook.Operation): """a user's custom properties has been added/changed""" + cwpropdict = key = value = None # make pylint happy def postcommit_event(self): """the observed connections set has been commited""" @@ -139,6 +142,7 @@ class _AddCWPropertyOp(hook.Operation): """a user's custom properties has been added/changed""" + cwprop = None # make pylint happy def postcommit_event(self): """the observed connections set has been commited""" diff -r 6cebeb1f386a -r ad0eeb0f7a8d hooks/syncsources.py --- a/hooks/syncsources.py Thu Sep 29 14:07:37 2011 +0200 +++ b/hooks/syncsources.py Thu Sep 29 14:47:04 2011 +0200 @@ -34,6 +34,7 @@ # repo sources synchronization ################################################# class SourceAddedOp(hook.Operation): + entity = None # make pylint happy def postcommit_event(self): self.session.repo.add_source(self.entity) @@ -54,6 +55,7 @@ class SourceRemovedOp(hook.Operation): + uri = None # make pylint happy def postcommit_event(self): self.session.repo.remove_source(self.uri) @@ -82,6 +84,7 @@ class SourceRenamedOp(hook.LateOperation): + oldname = newname = None # make pylint happy def precommit_event(self): source = self.session.repo.sources_by_uri[self.oldname] @@ -141,12 +144,12 @@ # Expect cw_for_source/cw_schema are immutable relations (i.e. can't change from # a source or schema to another). -class SourceMappingDeleteHook(SourceHook): +class SourceMappingImmutableHook(SourceHook): """check cw_for_source and cw_schema are immutable relations XXX empty delete perms would be enough? """ - __regid__ = 'cw.sources.delschemaconfig' + __regid__ = 'cw.sources.mapping.immutable' __select__ = SourceHook.__select__ & hook.match_rtype('cw_for_source', 'cw_schema') events = ('before_add_relation',) def __call__(self): diff -r 6cebeb1f386a -r ad0eeb0f7a8d hooks/workflow.py --- a/hooks/workflow.py Thu Sep 29 14:07:37 2011 +0200 +++ b/hooks/workflow.py Thu Sep 29 14:47:04 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. @@ -45,6 +45,7 @@ class _SetInitialStateOp(hook.Operation): """make initial state be a default state""" + entity = None # make pylint happy def precommit_event(self): session = self.session @@ -61,6 +62,7 @@ class _FireAutotransitionOp(hook.Operation): """try to fire auto transition after state changes""" + entity = None # make pylint happy def precommit_event(self): entity = self.entity @@ -73,6 +75,7 @@ class _WorkflowChangedOp(hook.Operation): """fix entity current state when changing its workflow""" + eid = wfeid = None # make pylint happy def precommit_event(self): # notice that enforcement that new workflow apply to the entity's type is @@ -109,6 +112,7 @@ class _CheckTrExitPoint(hook.Operation): + treid = None # make pylint happy def precommit_event(self): tr = self.session.entity_from_eid(self.treid) @@ -122,6 +126,7 @@ class _SubWorkflowExitOp(hook.Operation): + forentity = trinfo = None # make pylint happy def precommit_event(self): session = self.session diff -r 6cebeb1f386a -r ad0eeb0f7a8d mail.py --- a/mail.py Thu Sep 29 14:07:37 2011 +0200 +++ b/mail.py Thu Sep 29 14:47:04 2011 +0200 @@ -21,10 +21,10 @@ from base64 import b64encode, b64decode from time import time -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from email.MIMEImage import MIMEImage -from email.Header import Header +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.image import MIMEImage +from email.header import Header try: from socket import gethostname except ImportError: @@ -156,6 +156,10 @@ msgid_timestamp = True + # to be defined on concrete sub-classes + content = None # body of the mail + message = None # action verb of the subject + # this is usually the method to call def render_and_send(self, **kwargs): """generate and send an email message for this view""" diff -r 6cebeb1f386a -r ad0eeb0f7a8d md5crypt.py --- a/md5crypt.py Thu Sep 29 14:07:37 2011 +0200 +++ b/md5crypt.py Thu Sep 29 14:47:04 2011 +0200 @@ -41,7 +41,7 @@ MAGIC = '$1$' # Magic string ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" -import hashlib as md5 +from hashlib import md5 # pylint: disable=E0611 def to64 (v, n): ret = '' @@ -63,7 +63,7 @@ salt = salt.split('$', 1)[0] salt = salt[:8] ctx = pw + magic + salt - final = md5.md5(pw + salt + pw).digest() + final = md5(pw + salt + pw).digest() for pl in xrange(len(pw), 0, -16): if pl > 16: ctx = ctx + final[:16] @@ -77,7 +77,7 @@ else: ctx = ctx + pw[0] i = i >> 1 - final = md5.md5(ctx).digest() + final = md5(ctx).digest() # The following is supposed to make # things run slower. # my question: WTF??? @@ -95,7 +95,7 @@ ctx1 = ctx1 + final[:16] else: ctx1 = ctx1 + pw - final = md5.md5(ctx1).digest() + final = md5(ctx1).digest() # Final xform passwd = '' passwd = passwd + to64((int(ord(final[0])) << 16) diff -r 6cebeb1f386a -r ad0eeb0f7a8d migration.py --- a/migration.py Thu Sep 29 14:07:37 2011 +0200 +++ b/migration.py Thu Sep 29 14:47:04 2011 +0200 @@ -201,8 +201,8 @@ if not ask_confirm or self.confirm(msg): return meth(*args, **kwargs) - def confirm(self, question, shell=True, abort=True, retry=False, pdb=False, - default='y'): + def confirm(self, question, # pylint: disable=E0202 + shell=True, abort=True, retry=False, pdb=False, default='y'): """ask for confirmation and return true on positive answer if `retry` is true the r[etry] answer may return 2 diff -r 6cebeb1f386a -r ad0eeb0f7a8d misc/scripts/drop_external_entities.py --- a/misc/scripts/drop_external_entities.py Thu Sep 29 14:07:37 2011 +0200 +++ b/misc/scripts/drop_external_entities.py Thu Sep 29 14:47:04 2011 +0200 @@ -15,7 +15,7 @@ if suri != 'system': try: print 'deleting', e.__regid__, e.eid, suri, e.dc_title().encode('utf8') - repo.delete_info(session, e, suri, meta['extid'], scleanup=True) + repo.delete_info(session, e, suri, scleanup=e.eid) except UnknownEid: print ' cant delete', e.__regid__, e.eid, meta diff -r 6cebeb1f386a -r ad0eeb0f7a8d req.py --- a/req.py Thu Sep 29 14:07:37 2011 +0200 +++ b/req.py Thu Sep 29 14:47:04 2011 +0200 @@ -29,7 +29,7 @@ from logilab.common.deprecation import deprecated from logilab.common.date import ustrftime, strptime, todate, todatetime -from cubicweb import Unauthorized, RegistryException, typed_eid +from cubicweb import Unauthorized, NoSelectableObject, typed_eid from cubicweb.rset import ResultSet ONESECOND = timedelta(0, 1, 0) @@ -336,7 +336,7 @@ initargs.update(kwargs) try: view = self.vreg[__registry].select(__vid, self, rset=rset, **initargs) - except RegistryException: + except NoSelectableObject: if __fallback_oid is None: raise view = self.vreg[__registry].select(__fallback_oid, self, diff -r 6cebeb1f386a -r ad0eeb0f7a8d rqlrewrite.py --- a/rqlrewrite.py Thu Sep 29 14:07:37 2011 +0200 +++ b/rqlrewrite.py Thu Sep 29 14:47:04 2011 +0200 @@ -638,7 +638,7 @@ def visit_mathexpression(self, node): cmp_ = n.MathExpression(node.operator) - for c in cmp.children: + for c in node.children: cmp_.append(c.accept(self)) return cmp_ diff -r 6cebeb1f386a -r ad0eeb0f7a8d schema.py --- a/schema.py Thu Sep 29 14:07:37 2011 +0200 +++ b/schema.py Thu Sep 29 14:47:04 2011 +0200 @@ -665,6 +665,8 @@ # 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 + # to be defined in concrete classes + full_rql = None def __init__(self, expression, mainvars, eid): self.eid = eid # eid of the entity representing this rql expression diff -r 6cebeb1f386a -r ad0eeb0f7a8d selectors.py --- a/selectors.py Thu Sep 29 14:07:37 2011 +0200 +++ b/selectors.py Thu Sep 29 14:47:04 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 6cebeb1f386a -r ad0eeb0f7a8d server/hook.py --- a/server/hook.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/hook.py Thu Sep 29 14:47:04 2011 +0200 @@ -537,7 +537,7 @@ # XXX deprecated enabled = True # stop pylint from complaining about missing attributes in Hooks classes - eidfrom = eidto = entity = rtype = None + eidfrom = eidto = entity = rtype = repo = None @classmethod @cached @@ -580,7 +580,7 @@ warn('[3.6] %s: accepts is deprecated, define proper __select__' % classid(cls), DeprecationWarning) rtypes = [] - for ertype in cls.accepts: + for ertype in cls.accepts: # pylint: disable=E1101 if ertype.islower(): rtypes.append(ertype) else: @@ -601,6 +601,7 @@ if hasattr(self, 'call'): warn('[3.6] %s: call is deprecated, implement __call__' % classid(self.__class__), DeprecationWarning) + # pylint: disable=E1101 if self.event.endswith('_relation'): self.call(self._cw, self.eidfrom, self.rtype, self.eidto) elif 'delete' in self.event: @@ -628,7 +629,7 @@ Notice there are no default behaviour defined when a watched relation is deleted, you'll have to handle this by yourself. - You usually want to use the :class:`match_rtype_sets` selector on concret + You usually want to use the :class:`match_rtype_sets` selector on concrete classes. """ events = ('after_add_relation',) @@ -808,7 +809,7 @@ if event == 'postcommit_event' and hasattr(self, 'commit_event'): warn('[3.10] %s: commit_event method has been replaced by postcommit_event' % classid(self.__class__), DeprecationWarning) - self.commit_event() + self.commit_event() # pylint: disable=E1101 getattr(self, event)() def precommit_event(self): @@ -1092,6 +1093,9 @@ class RQLPrecommitOperation(Operation): + # to be defined in concrete classes + rqls = None + def precommit_event(self): execute = self.session.execute for rql in self.rqls: diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/migractions.py --- a/server/migractions.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/migractions.py Thu Sep 29 14:47:04 2011 +0200 @@ -1571,8 +1571,7 @@ This may be useful on accidental desync between the repository schema and a sql database """ - dbhelper = self.repo.system_source.dbhelper - tablesql = rschema2sql(dbhelper, self.repo.schema.rschema(rtype)) + tablesql = rschema2sql(self.repo.schema.rschema(rtype)) for sql in tablesql.split(';'): if sql.strip(): self.sqlexec(sql) diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/msplanner.py --- a/server/msplanner.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/msplanner.py Thu Sep 29 14:47:04 2011 +0200 @@ -291,6 +291,8 @@ self.sourcesterms = self._sourcesterms = {} # source : {relation: set(child variable and constant)} self._crossrelations = {} + # term : set(sources) + self._discarded_sources = {} # dictionary of variables and constants which are linked to each other # using a non final relation supported by multiple sources (crossed or # not). @@ -539,6 +541,7 @@ if invariant and source is self.system_source: continue self._remove_source_term(source, lhs) + self._discarded_sources.setdefault(lhs, set()).add(source) usesys = self.system_source not in sources else: for source, terms in sourcesterms.items(): @@ -546,6 +549,7 @@ if invariant and source is self.system_source: continue self._remove_source_term(source, lhs) + self._discarded_sources.setdefault(lhs, set()).add(source) usesys = self.system_source in sources if rel is None or (len(var.stinfo['relations']) == 2 and not var.stinfo['selected']): @@ -697,6 +701,12 @@ rel in self._crossrelations[s])) if invalid_sources: self._remove_sources(term, invalid_sources) + discarded = self._discarded_sources.get(term) + if discarded is not None and not any(x[0] for x in (termsources-invalid_sources) + if not x[0] in discarded): + raise BadRQLQuery('relation %s cant be crossed but %s and %s should ' + 'come from difference sources' % + (rel.r_type, term.as_string(), oterm.as_string())) # if term is a rewritten const, we can apply the same changes to # all other consts inserted from the same original variable for const in self._const_vars.get(term, ()): @@ -1438,7 +1448,7 @@ for step in steps for select in step.union.children): if temptable: - step = IntersectFetchStep(plan) # XXX not implemented + raise NotImplementedError('oops') # IntersectFetchStep(plan) else: step = IntersectStep(plan) else: diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/repository.py --- a/server/repository.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/repository.py Thu Sep 29 14:47:04 2011 +0200 @@ -1106,22 +1106,32 @@ hook.CleanupNewEidsCacheOp.get_instance(session).add_data(entity.eid) self.system_source.add_info(session, entity, source, extid, complete) - def delete_info(self, session, entity, sourceuri, extid, scleanup=None): + def delete_info(self, session, entity, sourceuri, scleanup=None): """called by external source when some entity known by the system source has been deleted in the external source """ # mark eid as being deleted in session info and setup cache update # operation hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid) - self._delete_info(session, entity, sourceuri, extid, scleanup) + self._delete_info(session, entity, sourceuri, scleanup) - def _delete_info(self, session, entity, sourceuri, extid, scleanup=None): + def _delete_info(self, session, entity, sourceuri, scleanup=None): """delete system information on deletion of an entity: + * delete all remaining relations from/to this entity + * call delete info on the system source which will transfer record from the entities table to the deleted_entities table + + When scleanup is specified, it's expected to be the source's eid, in + which case we'll specify the target's relation source so that this + source is ignored. E.g. we want to delete relations stored locally, as + the deletion information comes from the external source, it's its + responsability to have cleaned-up its own relations. """ pendingrtypes = session.transaction_data.get('pendingrtypes', ()) + if scleanup is not None: + source = self.sources_by_eid[scleanup] # delete remaining relations: if user can delete the entity, he can # delete all its relations without security checking with security_enabled(session, read=False, write=False): @@ -1137,6 +1147,13 @@ else: rql = 'DELETE Y %s X WHERE X eid %%(x)s' % rtype if scleanup is not None: + # if the relation can't be crossed, nothing to cleanup (we + # would get a BadRQLQuery from the multi-sources planner). + # This may still leave some junk if the mapping has changed + # at some point, but one can still run db-check to catch + # those + if not source in self.can_cross_relation(rtype): + continue # source cleaning: only delete relations stored locally # (here, scleanup rql += ', NOT (Y cw_source S, S eid %(seid)s)' @@ -1144,6 +1161,8 @@ session.execute(rql, {'x': eid, 'seid': scleanup}, build_descr=False) except Exception: + if self.config.mode == 'test': + raise self.exception('error while cascading delete for entity %s ' 'from %s. RQL: %s', entity, sourceuri, rql) self.system_source.delete_info_multi(session, [entity], sourceuri) @@ -1153,6 +1172,8 @@ the same etype and belinging to the same source. """ pendingrtypes = session.transaction_data.get('pendingrtypes', ()) + if scleanup is not None: + source = self.sources_by_eid[scleanup] # delete remaining relations: if user can delete the entity, he can # delete all its relations without security checking with security_enabled(session, read=False, write=False): @@ -1169,11 +1190,20 @@ else: rql = 'DELETE Y %s X WHERE X eid IN (%s)' % (rtype, in_eids) if scleanup is not None: + # if the relation can't be crossed, nothing to cleanup (we + # would get a BadRQLQuery from the multi-sources planner). + # This may still leave some junk if the mapping has changed + # at some point, but one can still run db-check to catch + # those + if not source in self.can_cross_relation(rtype): + continue # source cleaning: only delete relations stored locally rql += ', NOT (Y cw_source S, S eid %(seid)s)' try: session.execute(rql, {'seid': scleanup}, build_descr=False) except Exception: + if self.config.mode == 'test': + raise 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) diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/server.py --- a/server/server.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/server.py Thu Sep 29 14:47:04 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. @@ -129,6 +129,13 @@ signal.signal(signal.SIGINT, lambda x, y, s=self: s.quit()) signal.signal(signal.SIGTERM, lambda x, y, s=self: s.quit()) + + # these are overridden by set_log_methods below + # only defining here to prevent pylint from complaining + @classmethod + def info(cls, msg, *a, **kw): + pass + from logging import getLogger from cubicweb import set_log_methods LOGGER = getLogger('cubicweb.reposerver') diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/sources/__init__.py --- a/server/sources/__init__.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/sources/__init__.py Thu Sep 29 14:47:04 2011 +0200 @@ -118,6 +118,10 @@ # source configuration options options = () + # 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, repo, source_config, eid=None): self.repo = repo self.set_schema(repo.schema) diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/sources/ldapuser.py --- a/server/sources/ldapuser.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/sources/ldapuser.py Thu Sep 29 14:47:04 2011 +0200 @@ -542,7 +542,7 @@ self.warning('deleting ldap user with eid %s and dn %s', eid, base) entity = session.entity_from_eid(eid, 'CWUser') - self.repo.delete_info(session, entity, self.uri, base) + self.repo.delete_info(session, entity, self.uri) self.reset_caches() return [] # except ldap.REFERRAL, e: diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/sources/native.py --- a/server/sources/native.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/sources/native.py Thu Sep 29 14:47:04 2011 +0200 @@ -865,7 +865,7 @@ self.exception('failed to query entities table for eid %s', eid) raise UnknownEid(eid) - def eid_type_source(self, session, eid): + def eid_type_source(self, session, eid): # pylint: disable=E0202 """return a tuple (type, source, extid) for the entity with id """ sql = 'SELECT type, source, extid, asource FROM entities WHERE eid=%s' % eid res = self._eid_type_source(session, eid, sql) @@ -924,13 +924,13 @@ return cursor.fetchone()[0] - def create_eid(self, session): + def create_eid(self, session): # pylint: disable=E0202 # lock needed to prevent 'Connection is busy with results for another # command (0)' errors with SQLServer with self._eid_cnx_lock: - return self._create_eid() + return self._create_eid() # pylint: disable=E1102 - def _create_eid(self): + def _create_eid(self): # pylint: disable=E0202 # internal function doing the eid creation without locking. # needed for the recursive handling of disconnections (otherwise we # deadlock on self._eid_cnx_lock @@ -946,13 +946,13 @@ # FIXME: better detection of deconnection pb self.warning("trying to reconnect create eid connection") self._eid_creation_cnx = None - return self._create_eid() + return self._create_eid() # pylint: disable=E1102 except (self.DbapiError,), exc: # We get this one with pyodbc and SQL Server when connection was reset if exc.args[0] == '08S01': self.warning("trying to reconnect create eid connection") self._eid_creation_cnx = None - return self._create_eid() + return self._create_eid() # pylint: disable=E1102 else: raise except Exception: # WTF? diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/sources/pyrorql.py --- a/server/sources/pyrorql.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/sources/pyrorql.py Thu Sep 29 14:47:04 2011 +0200 @@ -191,7 +191,7 @@ self.support_entities[ertype] = 'write' in options else: # CWRType if ertype in ('is', 'is_instance_of', 'cw_source') or ertype in VIRTUAL_RTYPES: - msg = schemacfg._cw._('%s relation should not be in mapped') % rtype + msg = schemacfg._cw._('%s relation should not be in mapped') % ertype raise ValidationError(schemacfg.eid, {role_name('cw_for_schema', 'subject'): msg}) options = self._check_options(schemacfg, self.rtype_options) if 'dontcross' in options: @@ -286,9 +286,11 @@ # entity has been deleted from external repository but is not known here if eid is not None: entity = session.entity_from_eid(eid, etype) - repo.delete_info(session, entity, self.uri, extid, + repo.delete_info(session, entity, self.uri, scleanup=self.eid) except Exception: + if self.repo.config.mode == 'test': + raise self.exception('while updating %s with external id %s of source %s', etype, extid, self.uri) continue diff -r 6cebeb1f386a -r ad0eeb0f7a8d server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Thu Sep 29 14:07:37 2011 +0200 +++ b/server/test/unittest_msplanner.py Thu Sep 29 14:47:04 2011 +0200 @@ -1806,15 +1806,19 @@ def test_delete_relation3(self): repo._type_source_cache[999999] = ('Note', 'cards', 999999, 'cards') - self._test('DELETE Y multisource_inlined_rel X WHERE X eid %(x)s, NOT (Y cw_source S, S name %(source)s)', - [('DeleteRelationsStep', - [('OneFetchStep', - [('Any Y,999999 WHERE Y multisource_inlined_rel 999999, NOT EXISTS(Y cw_source S, S name "cards"), S is CWSource, Y is IN(Card, Note)', - [{'S': 'CWSource', 'Y': 'Card'}, {'S': 'CWSource', 'Y': 'Note'}])], - None, None, [self.system], {}, - [])] - )], - {'x': 999999, 'source': 'cards'}) + self.assertRaises( + BadRQLQuery, self._test, + 'DELETE Y multisource_inlined_rel X WHERE X eid %(x)s, ' + 'NOT (Y cw_source S, S name %(source)s)', [], + {'x': 999999, 'source': 'cards'}) + + def test_delete_relation4(self): + repo._type_source_cache[999999] = ('Note', 'cards', 999999, 'cards') + self.assertRaises( + BadRQLQuery, self._test, + 'DELETE X multisource_inlined_rel Y WHERE Y is Note, X eid %(x)s, ' + 'NOT (Y cw_source S, S name %(source)s)', [], + {'x': 999999, 'source': 'cards'}) def test_delete_entity1(self): repo._type_source_cache[999999] = ('Note', 'system', 999999, 'system') diff -r 6cebeb1f386a -r ad0eeb0f7a8d setup.py --- a/setup.py Thu Sep 29 14:07:37 2011 +0200 +++ b/setup.py Thu Sep 29 14:47:04 2011 +0200 @@ -1,7 +1,7 @@ #!/usr/bin/env python # pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611 # -# 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. @@ -179,7 +179,7 @@ if USE_SETUPTOOLS: # overwrite MyInstallData to use sys.prefix instead of the egg directory MyInstallMoreData = MyInstallData - class MyInstallData(MyInstallMoreData): + class MyInstallData(MyInstallMoreData): # pylint: disable=E0102 """A class that manages data files installation""" def run(self): _old_install_dir = self.install_dir diff -r 6cebeb1f386a -r ad0eeb0f7a8d sobjects/notification.py --- a/sobjects/notification.py Thu Sep 29 14:07:37 2011 +0200 +++ b/sobjects/notification.py Thu Sep 29 14:47:04 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. @@ -109,6 +109,8 @@ url: %(url)s """ + # to be defined on concrete sub-classes + content_attr = None def context(self, **kwargs): entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0) diff -r 6cebeb1f386a -r ad0eeb0f7a8d sobjects/parsers.py --- a/sobjects/parsers.py Thu Sep 29 14:07:37 2011 +0200 +++ b/sobjects/parsers.py Thu Sep 29 14:47:04 2011 +0200 @@ -32,7 +32,7 @@ """ import os.path as osp -from datetime import datetime, timedelta +from datetime import datetime, timedelta, time from urllib import urlencode from cgi import parse_qs # in urlparse with python >= 2.6 @@ -151,7 +151,7 @@ linker.check_options(options, schemacfg.eid) except KeyError: msg = _('"action" must be specified in options; allowed values are ' - '%s') % ', '.join(self.action_methods) + '%s') % ', '.join(self.list_actions()) raise ValidationError(schemacfg.eid, {rn('options', 'subject'): msg}) except RegistryException: msg = _('allowed values for "action" are %s') % ', '.join(self.list_actions()) diff -r 6cebeb1f386a -r ad0eeb0f7a8d test/unittest_req.py --- a/test/unittest_req.py Thu Sep 29 14:07:37 2011 +0200 +++ b/test/unittest_req.py Thu Sep 29 14:47:04 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,12 +15,14 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . + from logilab.common.testlib import TestCase, unittest_main +from cubicweb import ObjectNotFound from cubicweb.req import RequestSessionBase from cubicweb.devtools.testlib import CubicWebTC from cubicweb import Unauthorized -class RebuildURLTC(TestCase): +class RequestTC(TestCase): def test_rebuild_url(self): rebuild_url = RequestSessionBase(None).rebuild_url self.assertEqual(rebuild_url('http://logilab.fr?__message=pouet', __message='hop'), @@ -49,5 +51,13 @@ self.assertRaises(Unauthorized, req.ensure_ro_rql, 'SET X login "toto" WHERE X is CWUser') self.assertRaises(Unauthorized, req.ensure_ro_rql, ' SET X login "toto" WHERE X is CWUser ') + +class RequestCWTC(CubicWebTC): + def test_view_catch_ex(self): + req = self.request() + rset = self.execute('CWUser X WHERE X login "hop"') + self.assertEqual(req.view('oneline', rset, 'null'), '') + self.assertRaises(ObjectNotFound, req.view, 'onelinee', rset, 'null') + if __name__ == '__main__': unittest_main() diff -r 6cebeb1f386a -r ad0eeb0f7a8d toolsutils.py --- a/toolsutils.py Thu Sep 29 14:07:37 2011 +0200 +++ b/toolsutils.py Thu Sep 29 14:47:04 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. @@ -36,7 +36,7 @@ from logilab.common.compat import any from logilab.common.shellutils import ASK -from cubicweb import warning +from cubicweb import warning # pylint: disable=E0611 from cubicweb import ConfigurationError, ExecutionError def underline_title(title, car='-'): diff -r 6cebeb1f386a -r ad0eeb0f7a8d uilib.py --- a/uilib.py Thu Sep 29 14:07:37 2011 +0200 +++ b/uilib.py Thu Sep 29 14:47:04 2011 +0200 @@ -161,94 +161,84 @@ REM_ROOT_HTML_TAGS = re.compile('', re.U) -try: - from lxml import etree, html - from lxml.html import clean, defs +from lxml import etree, html +from lxml.html import clean, defs - ALLOWED_TAGS = (defs.general_block_tags | defs.list_tags | defs.table_tags | - defs.phrase_tags | defs.font_style_tags | - set(('span', 'a', 'br', 'img', 'map', 'area', 'sub', 'sup')) - ) +ALLOWED_TAGS = (defs.general_block_tags | defs.list_tags | defs.table_tags | + defs.phrase_tags | defs.font_style_tags | + set(('span', 'a', 'br', 'img', 'map', 'area', 'sub', 'sup')) + ) - CLEANER = clean.Cleaner(allow_tags=ALLOWED_TAGS, remove_unknown_tags=False, - style=True, safe_attrs_only=True, - add_nofollow=False, - ) +CLEANER = clean.Cleaner(allow_tags=ALLOWED_TAGS, remove_unknown_tags=False, + style=True, safe_attrs_only=True, + add_nofollow=False, + ) - def soup2xhtml(data, encoding): - """tidy html soup by allowing some element tags and return the result - """ - # remove spurious and tags, then normalize line break - # (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1) - data = REM_ROOT_HTML_TAGS.sub('', u'\n'.join(data.splitlines())) - xmltree = etree.HTML(CLEANER.clean_html('
%s
' % data)) - # NOTE: lxml 2.0 does support encoding='unicode', but last time I (syt) - # tried I got weird results (lxml 2.2.8) - body = etree.tostring(xmltree[0], encoding=encoding) - # remove and and decode to unicode - snippet = body[6:-7].decode(encoding) - # take care to bad xhtml (for instance starting with ) which - # may mess with the
we added below. Only remove it if it's - # still there... - if snippet.startswith('
') and snippet.endswith('
'): - snippet = snippet[5:-6] - return snippet +def soup2xhtml(data, encoding): + """tidy html soup by allowing some element tags and return the result + """ + # remove spurious and tags, then normalize line break + # (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1) + data = REM_ROOT_HTML_TAGS.sub('', u'\n'.join(data.splitlines())) + xmltree = etree.HTML(CLEANER.clean_html('
%s
' % data)) + # NOTE: lxml 2.0 does support encoding='unicode', but last time I (syt) + # tried I got weird results (lxml 2.2.8) + body = etree.tostring(xmltree[0], encoding=encoding) + # remove and and decode to unicode + snippet = body[6:-7].decode(encoding) + # take care to bad xhtml (for instance starting with
) which + # may mess with the
we added below. Only remove it if it's + # still there... + if snippet.startswith('
') and snippet.endswith('
'): + snippet = snippet[5:-6] + return snippet - # lxml.Cleaner envelops text elements by internal logic (not accessible) - # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 - # TODO drop attributes in elements - # TODO add policy configuration (content only, embedded content, ...) - # XXX this is buggy for "

text1

text2

"... - # XXX drop these two snippets action and follow the lxml behaviour - # XXX (tests need to be updated) - # if snippet.startswith('
') and snippet.endswith('
'): - # snippet = snippet[5:-6] - # if snippet.startswith('

') and snippet.endswith('

'): - # snippet = snippet[3:-4] - return snippet.decode(encoding) - -except (ImportError, AttributeError): - # gae environment: lxml not available - # fallback implementation - def soup2xhtml(data, encoding): - # normalize line break - # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 - return u'\n'.join(data.splitlines()) -else: - - if hasattr(etree.HTML('
test
'), 'iter'): # XXX still necessary? + # lxml.Cleaner envelops text elements by internal logic (not accessible) + # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 + # TODO drop attributes in elements + # TODO add policy configuration (content only, embedded content, ...) + # XXX this is buggy for "

text1

text2

"... + # XXX drop these two snippets action and follow the lxml behaviour + # XXX (tests need to be updated) + # if snippet.startswith('
') and snippet.endswith('
'): + # snippet = snippet[5:-6] + # if snippet.startswith('

') and snippet.endswith('

'): + # snippet = snippet[3:-4] + return snippet.decode(encoding) - def safe_cut(text, length): - """returns an html document of length based on , - and cut is necessary. - """ - if text is None: - return u'' - dom = etree.HTML(text) - curlength = 0 - add_ellipsis = False - for element in dom.iter(): - if curlength >= length: - parent = element.getparent() - parent.remove(element) - if curlength == length and (element.text or element.tail): - add_ellipsis = True - else: - if element.text is not None: - element.text = cut(element.text, length - curlength) - curlength += len(element.text) - if element.tail is not None: - if curlength < length: - element.tail = cut(element.tail, length - curlength) - curlength += len(element.tail) - elif curlength == length: - element.tail = '...' - else: - element.tail = '' - text = etree.tounicode(dom[0])[6:-7] # remove wrapping - if add_ellipsis: - return text + u'...' - return text +if hasattr(etree.HTML('
test
'), 'iter'): # XXX still necessary? + # pylint: disable=E0102 + def safe_cut(text, length): + """returns an html document of length based on , + and cut is necessary. + """ + if text is None: + return u'' + dom = etree.HTML(text) + curlength = 0 + add_ellipsis = False + for element in dom.iter(): + if curlength >= length: + parent = element.getparent() + parent.remove(element) + if curlength == length and (element.text or element.tail): + add_ellipsis = True + else: + if element.text is not None: + element.text = cut(element.text, length - curlength) + curlength += len(element.text) + if element.tail is not None: + if curlength < length: + element.tail = cut(element.tail, length - curlength) + curlength += len(element.tail) + elif curlength == length: + element.tail = '...' + else: + element.tail = '' + text = etree.tounicode(dom[0])[6:-7] # remove wrapping + if add_ellipsis: + return text + u'...' + return text def text_cut(text, nbwords=30, gotoperiod=True): """from the given plain text, return a text with at least words, diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/action.py --- a/web/action.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/action.py Thu Sep 29 14:47:04 2011 +0200 @@ -133,10 +133,13 @@ & partial_relation_possible(action='add', strict=True)) submenu = 'addrelated' + # to be defined in concrete classes + target_etype = rtype = None def url(self): try: - ttype = self.etype # deprecated in 3.6, already warned by the selector + # deprecated in 3.6, already warned by the selector + ttype = self.etype # pylint: disable=E1101 except AttributeError: ttype = self.target_etype entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0) diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/box.py --- a/web/box.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/box.py Thu Sep 29 14:47:04 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. @@ -118,7 +118,8 @@ related to the current result set. """ - rql = None + # to be defined in concrete classes + rql = title = None def to_display_rql(self): assert self.rql is not None, self.__regid__ @@ -168,7 +169,7 @@ subclasses should define at least id, rtype and target class attributes. """ - + rtype = None def cell_call(self, row, col, view=None, **kwargs): self._cw.add_js('cubicweb.ajax.js') entity = self.cw_rset.get_entity(row, col) diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/component.py --- a/web/component.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/component.py Thu Sep 29 14:47:04 2011 +0200 @@ -321,7 +321,7 @@ def wview(__vid, rset=None, __fallback_vid=None, **kwargs): self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) self.wview = wview - self.call(**kwargs) + self.call(**kwargs) # pylint: disable=E1101 return getlayout = self._cw.vreg['components'].select layout = getlayout('layout', self._cw, **self.layout_select_args()) @@ -539,6 +539,9 @@ subclasses should define at least id, rtype and target class attributes. """ + # to be defined in concrete classes + rtype = None + def render_title(self, w): w(display_name(self._cw, self.rtype, role(self), context=self.entity.__regid__)) @@ -566,7 +569,9 @@ added_msg = None removed_msg = None - # class attributes below *must* be set in concret classes (additionaly to + # to be defined in concrete classes + rtype = role = target_etype = None + # class attributes below *must* be set in concrete classes (additionaly to # rtype / role [/ target_etype]. They should correspond to js_* methods on # the json controller @@ -706,6 +711,8 @@ __select__ = EntityVComponent.__select__ & partial_has_related_entities() vid = 'list' + # to be defined in concrete classes + rtype = title = None def rql(self): """override this method if you want to use a custom rql query""" diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/controller.py --- a/web/controller.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/controller.py Thu Sep 29 14:47:04 2011 +0200 @@ -114,7 +114,7 @@ [recipient], body, subject) if not self._cw.vreg.config.sendmails([(msg, [recipient])]): msg = self._cw._('could not connect to the SMTP server') - url = self._cw.build_url(__message=msgid) + url = self._cw.build_url(__message=msg) raise Redirect(url) def reset(self): diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/data/excanvas.js --- a/web/data/excanvas.js Thu Sep 29 14:07:37 2011 +0200 +++ b/web/data/excanvas.js Thu Sep 29 14:47:04 2011 +0200 @@ -1,30 +1,1438 @@ -/** - * 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 +// Memory Leaks patch from http://explorercanvas.googlecode.com/svn/trunk/ +// svn : r73 +// ------------------------------------------------------------------ +// 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 only support repeat. +// * 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; + + var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; + + /** + * 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))); + }; + } + + function encodeHtmlAttribute(s) { + return String(s).replace(/&/g, '&').replace(/"/g, '"'); + } + + function addNamespace(doc, prefix, urn) { + if (!doc.namespaces[prefix]) { + doc.namespaces.add(prefix, urn, '#default#VML'); + } + } + + function addNamespacesAndStylesheet(doc) { + addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); + addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); + + // 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}'; + } + } + + // Add namespaces and stylesheet at startup. + addNamespacesAndStylesheet(document); + + var G_vmlCanvasManager_ = { + init: function(opt_doc) { + 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) { + // 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; + + // Add namespaces and stylesheet to document of the element. + addNamespacesAndStylesheet(el.ownerDocument); + + // 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; + }, + + // Memory Leaks patch : see http://code.google.com/p/explorercanvas/issues/detail?id=82 + uninitElement: function(el){ + if (el.getContext) { + var ctx = el.getContext(); + delete ctx.element_; + delete ctx.canvas; + el.innerHTML = ""; + //el.outerHTML = ""; + el.context_ = null; + el.getContext = null; + el.detachEvent("onpropertychange", onPropertyChange); + el.detachEvent("onresize", onResize); + } + } + }; + + function onPropertyChange(e) { + var el = e.srcElement; + + switch (e.propertyName) { + case 'width': + el.getContext().clearRect(); + el.style.width = el.attributes.width.nodeValue + 'px'; + // In IE8 this does not trigger onresize. + el.firstChild.style.width = el.clientWidth + 'px'; + break; + case 'height': + el.getContext().clearRect(); + el.style.height = el.attributes.height.nodeValue + 'px'; + el.firstChild.style.height = el.clientHeight + 'px'; + 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 decToHex = []; + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 16; j++) { + decToHex[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.font = o1.font; + o2.textAlign = o1.textAlign; + o2.textBaseline = o1.textBaseline; + o2.arcScaleX_ = o1.arcScaleX_; + o2.arcScaleY_ = o1.arcScaleY_; + o2.lineScale_ = o1.lineScale_; + } + + var colorData = { + aliceblue: '#F0F8FF', + antiquewhite: '#FAEBD7', + aquamarine: '#7FFFD4', + azure: '#F0FFFF', + beige: '#F5F5DC', + bisque: '#FFE4C4', + black: '#000000', + blanchedalmond: '#FFEBCD', + blueviolet: '#8A2BE2', + brown: '#A52A2A', + burlywood: '#DEB887', + cadetblue: '#5F9EA0', + chartreuse: '#7FFF00', + chocolate: '#D2691E', + coral: '#FF7F50', + cornflowerblue: '#6495ED', + cornsilk: '#FFF8DC', + crimson: '#DC143C', + cyan: '#00FFFF', + darkblue: '#00008B', + darkcyan: '#008B8B', + darkgoldenrod: '#B8860B', + darkgray: '#A9A9A9', + darkgreen: '#006400', + darkgrey: '#A9A9A9', + darkkhaki: '#BDB76B', + darkmagenta: '#8B008B', + darkolivegreen: '#556B2F', + darkorange: '#FF8C00', + darkorchid: '#9932CC', + darkred: '#8B0000', + darksalmon: '#E9967A', + darkseagreen: '#8FBC8F', + darkslateblue: '#483D8B', + darkslategray: '#2F4F4F', + darkslategrey: '#2F4F4F', + darkturquoise: '#00CED1', + darkviolet: '#9400D3', + deeppink: '#FF1493', + deepskyblue: '#00BFFF', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1E90FF', + firebrick: '#B22222', + floralwhite: '#FFFAF0', + forestgreen: '#228B22', + gainsboro: '#DCDCDC', + ghostwhite: '#F8F8FF', + gold: '#FFD700', + goldenrod: '#DAA520', + grey: '#808080', + greenyellow: '#ADFF2F', + honeydew: '#F0FFF0', + hotpink: '#FF69B4', + indianred: '#CD5C5C', + indigo: '#4B0082', + ivory: '#FFFFF0', + khaki: '#F0E68C', + lavender: '#E6E6FA', + lavenderblush: '#FFF0F5', + lawngreen: '#7CFC00', + lemonchiffon: '#FFFACD', + lightblue: '#ADD8E6', + lightcoral: '#F08080', + lightcyan: '#E0FFFF', + lightgoldenrodyellow: '#FAFAD2', + lightgreen: '#90EE90', + lightgrey: '#D3D3D3', + lightpink: '#FFB6C1', + lightsalmon: '#FFA07A', + lightseagreen: '#20B2AA', + lightskyblue: '#87CEFA', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#B0C4DE', + lightyellow: '#FFFFE0', + limegreen: '#32CD32', + linen: '#FAF0E6', + magenta: '#FF00FF', + mediumaquamarine: '#66CDAA', + mediumblue: '#0000CD', + mediumorchid: '#BA55D3', + mediumpurple: '#9370DB', + mediumseagreen: '#3CB371', + mediumslateblue: '#7B68EE', + mediumspringgreen: '#00FA9A', + mediumturquoise: '#48D1CC', + mediumvioletred: '#C71585', + midnightblue: '#191970', + mintcream: '#F5FFFA', + mistyrose: '#FFE4E1', + moccasin: '#FFE4B5', + navajowhite: '#FFDEAD', + oldlace: '#FDF5E6', + olivedrab: '#6B8E23', + orange: '#FFA500', + orangered: '#FF4500', + orchid: '#DA70D6', + palegoldenrod: '#EEE8AA', + palegreen: '#98FB98', + paleturquoise: '#AFEEEE', + palevioletred: '#DB7093', + papayawhip: '#FFEFD5', + peachpuff: '#FFDAB9', + peru: '#CD853F', + pink: '#FFC0CB', + plum: '#DDA0DD', + powderblue: '#B0E0E6', + rosybrown: '#BC8F8F', + royalblue: '#4169E1', + saddlebrown: '#8B4513', + salmon: '#FA8072', + sandybrown: '#F4A460', + seagreen: '#2E8B57', + seashell: '#FFF5EE', + sienna: '#A0522D', + skyblue: '#87CEEB', + slateblue: '#6A5ACD', + slategray: '#708090', + slategrey: '#708090', + snow: '#FFFAFA', + springgreen: '#00FF7F', + steelblue: '#4682B4', + tan: '#D2B48C', + thistle: '#D8BFD8', + tomato: '#FF6347', + turquoise: '#40E0D0', + violet: '#EE82EE', + wheat: '#F5DEB3', + whitesmoke: '#F5F5F5', + yellowgreen: '#9ACD32' + }; + + + function getRgbHslContent(styleString) { + var start = styleString.indexOf('(', 3); + var end = styleString.indexOf(')', start + 1); + var parts = styleString.substring(start + 1, end).split(','); + // add alpha if needed + if (parts.length != 4 || styleString.charAt(3) != 'a') { + parts[3] = 1; + } + return parts; + } + + function percent(s) { + return parseFloat(s) / 100; + } + + function clamp(v, min, max) { + return Math.min(max, Math.max(min, v)); + } + + function hslToRgb(parts){ + var r, g, b, h, s, l; + h = parseFloat(parts[0]) / 360 % 360; + if (h < 0) + h++; + s = clamp(percent(parts[1]), 0, 1); + l = clamp(percent(parts[2]), 0, 1); + if (s == 0) { + r = g = b = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hueToRgb(p, q, h + 1 / 3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1 / 3); + } + + return '#' + decToHex[Math.floor(r * 255)] + + decToHex[Math.floor(g * 255)] + + decToHex[Math.floor(b * 255)]; + } + + function hueToRgb(m1, m2, h) { + if (h < 0) + h++; + if (h > 1) + h--; + + if (6 * h < 1) + return m1 + (m2 - m1) * 6 * h; + else if (2 * h < 1) + return m2; + else if (3 * h < 2) + return m1 + (m2 - m1) * (2 / 3 - h) * 6; + else + return m1; + } + + var processStyleCache = {}; + + function processStyle(styleString) { + if (styleString in processStyleCache) { + return processStyleCache[styleString]; + } + + var str, alpha = 1; + + styleString = String(styleString); + if (styleString.charAt(0) == '#') { + str = styleString; + } else if (/^rgb/.test(styleString)) { + var parts = getRgbHslContent(styleString); + var str = '#', n; + for (var i = 0; i < 3; i++) { + if (parts[i].indexOf('%') != -1) { + n = Math.floor(percent(parts[i]) * 255); + } else { + n = +parts[i]; + } + str += decToHex[clamp(n, 0, 255)]; + } + alpha = +parts[3]; + } else if (/^hsl/.test(styleString)) { + var parts = getRgbHslContent(styleString); + str = hslToRgb(parts); + alpha = parts[3]; + } else { + str = colorData[styleString] || styleString; + } + return processStyleCache[styleString] = {color: str, alpha: alpha}; + } + + var DEFAULT_STYLE = { + style: 'normal', + variant: 'normal', + weight: 'normal', + size: 10, + family: 'sans-serif' + }; + + // Internal text style cache + var fontStyleCache = {}; + + function processFontStyle(styleString) { + if (fontStyleCache[styleString]) { + return fontStyleCache[styleString]; + } + + var el = document.createElement('div'); + var style = el.style; + try { + style.font = styleString; + } catch (ex) { + // Ignore failures to set to invalid font. + } + + return fontStyleCache[styleString] = { + style: style.fontStyle || DEFAULT_STYLE.style, + variant: style.fontVariant || DEFAULT_STYLE.variant, + weight: style.fontWeight || DEFAULT_STYLE.weight, + size: style.fontSize || DEFAULT_STYLE.size, + family: style.fontFamily || DEFAULT_STYLE.family + }; + } + + function getComputedStyle(style, element) { + var computedStyle = {}; + + for (var p in style) { + computedStyle[p] = style[p]; + } + + // Compute the size + var canvasFontSize = parseFloat(element.currentStyle.fontSize), + fontSize = parseFloat(style.size); + + if (typeof style.size == 'number') { + computedStyle.size = style.size; + } else if (style.size.indexOf('px') != -1) { + computedStyle.size = fontSize; + } else if (style.size.indexOf('em') != -1) { + computedStyle.size = canvasFontSize * fontSize; + } else if(style.size.indexOf('%') != -1) { + computedStyle.size = (canvasFontSize / 100) * fontSize; + } else if (style.size.indexOf('pt') != -1) { + computedStyle.size = fontSize / .75; + } else { + computedStyle.size = canvasFontSize; + } + + // Different scaling between normal text and VML text. This was found using + // trial and error to get the same size as non VML text. + computedStyle.size *= 0.981; + + // Fix for VML handling of bare font family names. Add a '' around font family names. + computedStyle.family = "'" + computedStyle.family.replace(/(\'|\")/g,'').replace(/\s*,\s*/g, "', '") + "'"; + + return computedStyle; + } + + function buildStyle(style) { + return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + + style.size + 'px ' + style.family; + } + + var lineCapMap = { + 'butt': 'flat', + 'round': 'round' + }; + + function processLineCap(lineCap) { + return lineCapMap[lineCap] || 'square'; + } + + /** + * This class implements CanvasRenderingContext2D interface as described by + * the WHATWG. + * @param {HTMLElement} canvasElement The element that the 2D context should + * be associated with + */ + function CanvasRenderingContext2D_(canvasElement) { + 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.font = '10px sans-serif'; + this.textAlign = 'left'; + this.textBaseline = 'alphabetic'; + this.canvas = canvasElement; + + var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; + var el = canvasElement.ownerDocument.createElement('div'); + el.style.cssText = cssText; + canvasElement.appendChild(el); + + var overlayEl = el.cloneNode(false); + // Use a non transparent background. + overlayEl.style.backgroundColor = 'red'; + overlayEl.style.filter = 'alpha(opacity=0)'; + canvasElement.appendChild(overlayEl); + + this.element_ = el; + this.arcScaleX_ = 1; + this.arcScaleY_ = 1; + this.lineScale_ = 1; + } + + var contextPrototype = CanvasRenderingContext2D_.prototype; + contextPrototype.clearRect = function() { + if (this.textMeasureEl_) { + this.textMeasureEl_.removeNode(true); + this.textMeasureEl_ = null; + } + 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 = getCoords(this, 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 = getCoords(this, 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 = getCoords(this, aX, aY); + var cp1 = getCoords(this, aCP1x, aCP1y); + var cp2 = getCoords(this, 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 = getCoords(this, aCPx, aCPy); + var p = getCoords(this, 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 = getCoords(this, aX, aY); + var pStart = getCoords(this, xStart, yStart); + var pEnd = getCoords(this, 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 = getCoords(this, 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 W = 10; + var H = 10; + + lineStr.push(''); + + if (!aFill) { + appendStroke(this, lineStr); + } else { + appendFill(this, lineStr, min, max); + } + + lineStr.push(''); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + }; + + function appendStroke(ctx, lineStr) { + var a = processStyle(ctx.strokeStyle); + var color = a.color; + var opacity = a.alpha * ctx.globalAlpha; + var lineWidth = ctx.lineScale_ * ctx.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( + '' + ); + } + + function appendFill(ctx, lineStr, min, max) { + var fillStyle = ctx.fillStyle; + var arcScaleX = ctx.arcScaleX_; + var arcScaleY = ctx.arcScaleY_; + var width = max.x - min.x; + var height = max.y - min.y; + if (fillStyle instanceof CanvasGradient_) { + // TODO: Gradients transformed with the transformation matrix. + 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_ / arcScaleX; + var y0 = fillStyle.y0_ / arcScaleY; + var x1 = fillStyle.x1_ / arcScaleX; + var y1 = fillStyle.y1_ / arcScaleY; + var p0 = getCoords(ctx, x0, y0); + var p1 = getCoords(ctx, 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 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); + focus = { + x: (p0.x - min.x) / width, + y: (p0.y - min.y) / height + }; + + width /= arcScaleX * Z; + height /= 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 * ctx.globalAlpha; + var opacity2 = stops[length - 1].alpha * ctx.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 if (fillStyle instanceof CanvasPattern_) { + if (width && height) { + var deltaLeft = -min.x; + var deltaTop = -min.y; + lineStr.push(''); + } + } else { + var a = processStyle(ctx.fillStyle); + var color = a.color; + var opacity = a.alpha * ctx.globalAlpha; + lineStr.push(''); + } + } + + contextPrototype.fill = function() { + this.stroke(true); + }; + + contextPrototype.closePath = function() { + this.currentPath_.push({type: 'close'}); + }; + + function getCoords(ctx, aX, aY) { + var m = ctx.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() { + if (this.aStack_.length) { + copyState(this.aStack_.pop(), this); + this.m_ = this.mStack_.pop(); + } + }; + + function matrixIsFinite(m) { + return isFinite(m[0][0]) && isFinite(m[0][1]) && + isFinite(m[1][0]) && isFinite(m[1][1]) && + isFinite(m[2][0]) && isFinite(m[2][1]); + } + + 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); + }; + + /** + * The text drawing function. + * The maxWidth argument isn't taken in account, since no browser supports + * it yet. + */ + contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { + var m = this.m_, + delta = 1000, + left = 0, + right = delta, + offset = {x: 0, y: 0}, + lineStr = []; + + var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_); + + var fontStyleString = buildStyle(fontStyle); + + var elementStyle = this.element_.currentStyle; + var textAlign = this.textAlign.toLowerCase(); + switch (textAlign) { + case 'left': + case 'center': + case 'right': + break; + case 'end': + textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; + break; + case 'start': + textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; + break; + default: + textAlign = 'left'; + } + + // 1.75 is an arbitrary number, as there is no info about the text baseline + switch (this.textBaseline) { + case 'hanging': + case 'top': + offset.y = fontStyle.size / 1.75; + break; + case 'middle': + break; + default: + case null: + case 'alphabetic': + case 'ideographic': + case 'bottom': + offset.y = -fontStyle.size / 2.25; + break; + } + + switch(textAlign) { + case 'right': + left = delta; + right = 0.05; + break; + case 'center': + left = right = delta / 2; + break; + } + + var d = getCoords(this, x + offset.x, y + offset.y); + + lineStr.push(''); + + if (stroke) { + appendStroke(this, lineStr); + } else { + // TODO: Fix the min and max params. + appendFill(this, lineStr, {x: -left, y: 0}, + {x: right, y: fontStyle.size}); + } + + var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; + + var skewOffset = mr(d.x / Z + 1 - m[0][0]) + ',' + mr(d.y / Z - 2 * m[1][0]); + + + lineStr.push('', + '', + ''); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + }; + + contextPrototype.fillText = function(text, x, y, maxWidth) { + this.drawText_(text, x, y, maxWidth, false); + }; + + contextPrototype.strokeText = function(text, x, y, maxWidth) { + this.drawText_(text, x, y, maxWidth, true); + }; + + contextPrototype.measureText = function(text) { + if (!this.textMeasureEl_) { + var s = ''; + this.element_.insertAdjacentHTML('beforeEnd', s); + this.textMeasureEl_ = this.element_.lastChild; + } + var doc = this.element_.ownerDocument; + this.textMeasureEl_.innerHTML = ''; + this.textMeasureEl_.style.font = this.font; + // Don't use innerHTML or innerText because they allow markup/whitespace. + this.textMeasureEl_.appendChild(doc.createTextNode(text)); + return {width: this.textMeasureEl_.offsetWidth}; + }; + + /******** STUBS ********/ + contextPrototype.clip = function() { + // TODO: Implement + }; + + contextPrototype.arcTo = function() { + // TODO: Implement + }; + + contextPrototype.createPattern = function(image, repetition) { + return new CanvasPattern_(image, repetition); + }; + + // 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_(image, repetition) { + assertImageIsValid(image); + switch (repetition) { + case 'repeat': + case null: + case '': + this.repetition_ = 'repeat'; + break + case 'repeat-x': + case 'repeat-y': + case 'no-repeat': + this.repetition_ = repetition; + break; + default: + throwException('SYNTAX_ERR'); + } + + this.src_ = image.src; + this.width_ = image.width; + this.height_ = image.height; + } + + function throwException(s) { + throw new DOMException_(s); + } + + function assertImageIsValid(img) { + if (!img || img.nodeType != 1 || img.tagName != 'IMG') { + throwException('TYPE_MISMATCH_ERR'); + } + if (img.readyState != 'complete') { + throwException('INVALID_STATE_ERR'); + } + } + + function DOMException_(s) { + this.code = this[s]; + this.message = s +': DOM Exception ' + this.code; + } + var p = DOMException_.prototype = new Error; + p.INDEX_SIZE_ERR = 1; + p.DOMSTRING_SIZE_ERR = 2; + p.HIERARCHY_REQUEST_ERR = 3; + p.WRONG_DOCUMENT_ERR = 4; + p.INVALID_CHARACTER_ERR = 5; + p.NO_DATA_ALLOWED_ERR = 6; + p.NO_MODIFICATION_ALLOWED_ERR = 7; + p.NOT_FOUND_ERR = 8; + p.NOT_SUPPORTED_ERR = 9; + p.INUSE_ATTRIBUTE_ERR = 10; + p.INVALID_STATE_ERR = 11; + p.SYNTAX_ERR = 12; + p.INVALID_MODIFICATION_ERR = 13; + p.NAMESPACE_ERR = 14; + p.INVALID_ACCESS_ERR = 15; + p.VALIDATION_ERR = 16; + p.TYPE_MISMATCH_ERR = 17; + + // set up externs + G_vmlCanvasManager = G_vmlCanvasManager_; + CanvasRenderingContext2D = CanvasRenderingContext2D_; + CanvasGradient = CanvasGradient_; + CanvasPattern = CanvasPattern_; + DOMException = DOMException_; + G_vmlCanvasManager._version = 888; +})(); + +} // if diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/facet.py --- a/web/facet.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/facet.py Thu Sep 29 14:47:04 2011 +0200 @@ -64,7 +64,7 @@ from cubicweb import Unauthorized, typed_eid from cubicweb.schema import display_name from cubicweb.utils import make_uid -from cubicweb.selectors import match_context_prop, partial_relation_possible +from cubicweb.selectors import match_context_prop, partial_relation_possible, yes from cubicweb.appobject import AppObject from cubicweb.web import RequestError, htmlwidgets @@ -82,9 +82,9 @@ @deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with ' 'slightly modified prototype') -def filter_hiddens(w, **kwargs): +def filter_hiddens(w, baserql, **kwargs): from cubicweb.web.views.facets import filter_hiddens - return filter_hiddens(w, wdgs=kwargs.pop('facets')) + return filter_hiddens(w, baserql, wdgs=kwargs.pop('facets'), **kwargs) ## rqlst manipulation functions used by facets ################################ @@ -502,7 +502,7 @@ class RelationFacet(VocabularyFacet): """Base facet to filter some entities according to other entities to which - they are related. Create concret facet by inheriting from this class an then + they are related. Create concrete facet by inheriting from this class an then configuring it by setting class attribute described below. The relation is defined by the `rtype` and `role` attributes. @@ -751,7 +751,7 @@ restrvar, rtrel = _make_relation(self.select, self.filtered_variable, self.rtype, self.role) if rel is None: - select.add_restriction(rtrel) + self.select.add_restriction(rtrel) else: rel.parent.replace(rel, nodes.And(rel, rtrel)) self._and_restriction(rel, restrvar, value.pop()) @@ -1015,6 +1015,7 @@ (e.g when you want to filter on entities where are not directly linked to the filtered entities). """ + __select__ = yes() # we don't want RelationFacet's selector # must be specified path = None filter_variable = None @@ -1031,8 +1032,11 @@ def __init__(self, *args, **kwargs): super(RQLPathFacet, self).__init__(*args, **kwargs) + assert self.filter_variable != self.label_variable, \ + ('filter_variable and label_variable should be different. ' + 'You may want to let label_variable undefined (ie None).') assert self.path and isinstance(self.path, (list, tuple)), \ - 'path should be a list of 3-uples, not %s' % self.path + 'path should be a list of 3-uples, not %s' % self.path for part in self.path: if isinstance(part, basestring): part = part.split() @@ -1044,8 +1048,7 @@ ','.join(str(p) for p in self.path)) def vocabulary(self): - """return vocabulary for this facet, eg a list of 2-uple (label, value) - """ + """return vocabulary for this facet, eg a list of (label, value)""" select = self.select select.save_state() if self.rql_sort: diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/formfields.py --- a/web/formfields.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/formfields.py Thu Sep 29 14:47:04 2011 +0200 @@ -28,7 +28,7 @@ .. autoclass:: cubicweb.web.formfields.Field -Now, you usually don't use that class but one of the concret field classes +Now, you usually don't use that class but one of the concrete field classes described below, according to what you want to edit. Basic fields @@ -107,7 +107,7 @@ class Field(object): """This class is the abstract base class for all fields. It hold a bunch of attributes which may be used for fine control of the behaviour of a - concret field. + concrete field. **Attributes** @@ -349,6 +349,7 @@ def initial_typed_value(self, form, load_bytes): if self.value is not _MARKER: if callable(self.value): + # pylint: disable=E1102 if support_args(self.value, 'form', 'field'): return self.value(form, self) else: @@ -389,6 +390,7 @@ """ assert self.choices is not None if callable(self.choices): + # pylint: disable=E1102 if getattr(self.choices, 'im_self', None) is self: vocab = self.choices(form=form, **kwargs) elif support_args(self.choices, 'form', 'field'): @@ -396,11 +398,11 @@ else: try: vocab = self.choices(form=form, **kwargs) - warn('[3.6] %s: choices should now take ' + warn('[3.6] %s: choices should now take ' 'the form and field as named arguments' % self, DeprecationWarning) except TypeError: - warn('[3.3] %s: choices should now take ' + warn('[3.3] %s: choices should now take ' 'the form and field as named arguments' % self, DeprecationWarning) vocab = self.choices(req=form._cw, **kwargs) diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/formwidgets.py --- a/web/formwidgets.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/formwidgets.py Thu Sep 29 14:47:04 2011 +0200 @@ -110,8 +110,8 @@ **Attributes** - Here are standard attributes of a widget, that may be set on concret - class to override default behaviours: + Here are standard attributes of a widget, that may be set on concrete class + to override default behaviours: :attr:`needs_js` list of javascript files needed by the widget. @@ -134,7 +134,7 @@ Also, widget instances takes as first argument a `attrs` dictionary which will be stored in the attribute of the same name. It contains HTML - attributes that should be set in the widget's input tag (though concret + attributes that should be set in the widget's input tag (though concrete classes may ignore it). .. currentmodule:: cubicweb.web.formwidgets @@ -190,7 +190,7 @@ return self._render(form, field, renderer) def _render(self, form, field, renderer): - """This is the method you have to implement in concret widget classes. + """This is the method you have to implement in concrete widget classes. """ raise NotImplementedError() @@ -232,7 +232,7 @@ correctly typed value. 3 and 4 are handle by the :meth:`typed_value` method to ease reuse in - concret classes. + concrete classes. """ values = None if not field.ignore_req_params: diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/request.py --- a/web/request.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/request.py Thu Sep 29 14:47:04 2011 +0200 @@ -19,10 +19,10 @@ __docformat__ = "restructuredtext en" -import hashlib import time import random import base64 +from hashlib import sha1 # pylint: disable=E0611 from Cookie import SimpleCookie from calendar import timegm from datetime import date @@ -49,7 +49,7 @@ _MARKER = object() def build_cb_uid(seed): - sha = hashlib.sha1('%s%s%s' % (time.time(), seed, random.random())) + sha = sha1('%s%s%s' % (time.time(), seed, random.random())) return 'cb_%s' % (sha.hexdigest()) @@ -557,7 +557,7 @@ warn('[3.13] remove_cookie now take only a name as argument', DeprecationWarning, stacklevel=2) name = bwcompat - self.set_cookie(key, '', maxage=0, expires=date(1970, 1, 1)) + self.set_cookie(name, '', maxage=0, expires=date(1970, 1, 1)) def set_content_type(self, content_type, filename=None, encoding=None): """set output content type for this request. An optional filename diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/test/unittest_facet.py --- a/web/test/unittest_facet.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/test/unittest_facet.py Thu Sep 29 14:47:04 2011 +0200 @@ -197,13 +197,12 @@ def test_rql_path_eid(self): req, rset, rqlst, filtered_variable = self.prepare_rqlst() - facet.RQLPathFacet.path = [('X created_by U'), ('U owned_by O'), ('O login OL')] - f = facet.RQLPathFacet(req, rset=rset, - select=rqlst.children[0], - filtered_variable=filtered_variable) - f.filter_variable = 'O' - f.label_variable = 'OL' - + class RPF(facet.RQLPathFacet): + path = [('X created_by U'), ('U owned_by O'), ('O login OL')] + filter_variable = 'O' + label_variable = 'OL' + f = RPF(req, rset=rset, select=rqlst.children[0], + filtered_variable=filtered_variable) self.assertEqual(f.vocabulary(), [(u'admin', self.user().eid),]) # ensure rqlst is left unmodified self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser') @@ -219,18 +218,26 @@ self.assertEqual(f.select.as_string(), "DISTINCT Any WHERE X is CWUser, X created_by F, F owned_by G, G eid 1") + def test_rql_path_eid_no_label(self): + req, rset, rqlst, filtered_variable = self.prepare_rqlst() + class RPF(facet.RQLPathFacet): + path = [('X created_by U'), ('U owned_by O'), ('O login OL')] + filter_variable = 'O' + f = RPF(req, rset=rset, select=rqlst.children[0], + filtered_variable=filtered_variable) + self.assertEqual(f.vocabulary(), [(str(self.user().eid), self.user().eid),]) + def test_rql_path_attr(self): req, rset, rqlst, filtered_variable = self.prepare_rqlst() - facet.RQLPathFacet.path = [('X created_by U'), ('U owned_by O'), ('O login OL')] - f = facet.RQLPathFacet(req, rset=rset, - select=rqlst.children[0], - filtered_variable=filtered_variable) - f.filter_variable = 'OL' + class RPF(facet.RQLPathFacet): + path = [('X created_by U'), ('U owned_by O'), ('O login OL')] + filter_variable = 'OL' + f = RPF(req, rset=rset, select=rqlst.children[0], + filtered_variable=filtered_variable) self.assertEqual(f.vocabulary(), [(u'admin', 'admin'),]) # ensure rqlst is left unmodified self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser') - #rqlst = rset.syntax_tree() self.assertEqual(f.possible_values(), ['admin',]) # ensure rqlst is left unmodified self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser') @@ -241,6 +248,16 @@ self.assertEqual(f.select.as_string(), "DISTINCT Any WHERE X is CWUser, X created_by G, G owned_by H, H login 'admin'") + def test_rql_path_check_filter_label_variable(self): + req, rset, rqlst, filtered_variable = self.prepareg_aggregat_rqlst() + class RPF(facet.RQLPathFacet): + path = [('X created_by U'), ('U owned_by O'), ('O login OL')] + filter_variable = 'OL' + label_variable = 'OL' + self.assertRaises(AssertionError, RPF, req, rset=rset, + select=rqlst.children[0], + filtered_variable=filtered_variable) + def prepareg_aggregat_rqlst(self): return self.prepare_rqlst( 'Any 1, COUNT(X) WHERE X is CWUser, X creation_date XD, ' @@ -265,11 +282,11 @@ def test_aggregat_query_rql_path(self): req, rset, rqlst, filtered_variable = self.prepareg_aggregat_rqlst() - facet.RQLPathFacet.path = [('X created_by U'), ('U owned_by O'), ('O login OL')] - f = facet.RQLPathFacet(req, rset=rset, - select=rqlst.children[0], - filtered_variable=filtered_variable) - f.filter_variable = 'OL' + class RPF(facet.RQLPathFacet): + path = [('X created_by U'), ('U owned_by O'), ('O login OL')] + filter_variable = 'OL' + f = RPF(req, rset=rset, select=rqlst.children[0], + filtered_variable=filtered_variable) self.assertEqual(f.vocabulary(), [(u'admin', u'admin')]) self.assertEqual(f.possible_values(), ['admin']) req.form[f.__regid__] = 'admin' diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/test/unittest_formfields.py --- a/web/test/unittest_formfields.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/test/unittest_formfields.py Thu Sep 29 14:47:04 2011 +0200 @@ -144,6 +144,17 @@ self.assertEqual(description_format_field.value(form), 'text/rest') + def test_property_key_field(self): + from cubicweb.web.views.cwproperties import PropertyKeyField + req = self.request() + field = PropertyKeyField() + e = self.vreg['etypes'].etype_class('CWProperty')(req) + renderer = self.vreg['formrenderers'].select('base', req) + form = EntityFieldsForm(req, entity=e) + form.formvalues = {} + field.render(form, renderer) + + class UtilsTC(TestCase): def test_vocab_sort(self): self.assertEqual(vocab_sort([('Z', 1), ('A', 2), diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/ajaxedit.py --- a/web/views/ajaxedit.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/ajaxedit.py Thu Sep 29 14:47:04 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. @@ -70,7 +70,7 @@ if getattr(self, 'etype', None): rset = entity.unrelated(self.rtype, self.etype, role(self), ordermethod='fetch_order') - self.pagination(self._cw, rset, w=self.w) + self.paginate(self._cw, rset=rset, w=self.w) return rset.entities() super(AddRelationView, self).unrelated_entities(self) diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/autoform.py --- a/web/views/autoform.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/autoform.py Thu Sep 29 14:47:04 2011 +0200 @@ -198,6 +198,9 @@ _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype') removejs = "removeInlinedEntity('%s', '%s', '%s')" + # make pylint happy + peid = rtype = role = pform = etype = None + def __init__(self, *args, **kwargs): for attr in self._select_attrs: # don't pop attributes from kwargs, so the end-up in @@ -304,6 +307,9 @@ & specified_etype_implements('Any')) _select_attrs = InlineEntityEditionFormView._select_attrs + ('petype',) + # make pylint happy + petype = None + @property def removejs(self): entity = self._entity() @@ -345,6 +351,7 @@ & specified_etype_implements('Any')) _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',) + card = None # make pylint happy form = None # no actual form wrapped def call(self, i18nctx, **kwargs): @@ -752,6 +759,7 @@ def _generic_relations_field(self): try: + # pylint: disable=E1101 srels_by_cat = self.srelations_by_category('generic', 'add', strict=True) warn('[3.6] %s: srelations_by_category is deprecated, use uicfg or ' 'override editable_relations instead' % classid(self), diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/basetemplates.py --- a/web/views/basetemplates.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/basetemplates.py Thu Sep 29 14:47:04 2011 +0200 @@ -56,6 +56,9 @@ self.wview('htmlheader', rset=self.cw_rset) w(u'%s\n' % xml_escape(page_title)) + def content(self): + raise NotImplementedError() + class LogInTemplate(LogInOutTemplate): __regid__ = 'login' diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/cwproperties.py --- a/web/views/cwproperties.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/cwproperties.py Thu Sep 29 14:47:04 2011 +0200 @@ -314,6 +314,7 @@ def render(self, form, renderer): wdg = self.get_widget(form) + # pylint: disable=E1101 wdg.attrs['tabindex'] = form._cw.next_tabindex() wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( form.edited_entity.eid, form._cw.next_tabindex()) @@ -349,7 +350,7 @@ try: pdef = form._cw.vreg.property_info(entity.pkey) except UnknownProperty, ex: - self.warning('%s (you should probably delete that property ' + form.warning('%s (you should probably delete that property ' 'from the database)', ex) msg = form._cw._('you should probably delete that property') self.widget = NotEditableWidget(entity.printable_value('value'), diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/cwuser.py --- a/web/views/cwuser.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/cwuser.py Thu Sep 29 14:47:04 2011 +0200 @@ -20,7 +20,7 @@ __docformat__ = "restructuredtext en" _ = unicode -import hashlib +from hashlib import sha1 # pylint: disable=E0611 from logilab.mtconverter import xml_escape @@ -86,7 +86,7 @@ emailaddr = entity.cw_adapt_to('IEmailable').get_email() if emailaddr: self.w(u'%s\n' - % hashlib.sha1(emailaddr.encode('utf-8')).hexdigest()) + % sha1(emailaddr.encode('utf-8')).hexdigest()) self.w(u'\n') diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/idownloadable.py --- a/web/views/idownloadable.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/idownloadable.py Thu Sep 29 14:47:04 2011 +0200 @@ -194,7 +194,7 @@ def cell_call(self, row, col, link=False, **kwargs): entity = self.cw_rset.get_entity(row, col) adapter = entity.cw_adapt_to('IDownloadable') - tag = self._embedding_tag(src=adapter.download_url(), + tag = self._embedding_tag(src=adapter.download_url(), # pylint: disable=E1102 alt=(self._cw._('download %s') % adapter.download_file_name()), **kwargs) if link: diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/plots.py --- a/web/views/plots.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/plots.py Thu Sep 29 14:47:04 2011 +0200 @@ -79,6 +79,9 @@ if w is None: return self._stream.getvalue() + def _render(self, *args, **kwargs): + raise NotImplementedError + class FlotPlotWidget(PlotWidget): """PlotRenderer widget using Flot""" onload = u""" diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/primary.py --- a/web/views/primary.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/primary.py Thu Sep 29 14:47:04 2011 +0200 @@ -130,7 +130,8 @@ if hasattr(self, 'render_entity_summary'): warn('[3.10] render_entity_summary method is deprecated (%s)' % self, DeprecationWarning) - self.render_entity_summary(entity) + self.render_entity_summary(entity) # pylint: disable=E1101 + summary = self.summary(entity) if summary: warn('[3.10] summary method is deprecated (%s)' % self, @@ -149,7 +150,7 @@ self.w(u'
') if hasattr(self, 'render_side_related'): warn('[3.2] render_side_related is deprecated') - self.render_side_related(entity, []) + self.render_side_related(entity, []) # pylint: disable=E1101 self.render_side_boxes(boxes) self.w(u'
') self.w(u'') @@ -217,20 +218,21 @@ if display_attributes: self.w(u'') for rschema, role, dispctrl, value in display_attributes: + # pylint: disable=E1101 if not hasattr(self, '_render_attribute'): label = self._rel_label(entity, rschema, role, dispctrl) self.render_attribute(label, value, table=True) elif support_args(self._render_attribute, 'dispctrl'): warn('[3.9] _render_attribute prototype has changed and ' 'renamed to render_attribute, please update %s' - % self.__class___, DeprecationWarning) + % self.__class__, DeprecationWarning) self._render_attribute(dispctrl, rschema, value, role=role, table=True) else: self._render_attribute(rschema, value, role=role, table=True) warn('[3.6] _render_attribute prototype has changed and ' 'renamed to render_attribute, please update %s' - % self.__class___, DeprecationWarning) + % self.__class__, DeprecationWarning) self.w(u'
') def render_attribute(self, label, value, table=False): @@ -255,6 +257,7 @@ if not rset: continue if hasattr(self, '_render_relation'): + # pylint: disable=E1101 if not support_args(self._render_relation, 'showlabel'): self._render_relation(dispctrl, rset, 'autolimited') warn('[3.9] _render_relation prototype has changed and has ' @@ -431,7 +434,7 @@ __regid__ = 'attribute' __select__ = EntityView.__select__ & match_kwargs('rtype') - def entity_call(self, entity, rtype, **kwargs): + def entity_call(self, entity, rtype, role, **kwargs): if self._cw.vreg.schema.rschema(rtype).final: self.w(entity.printable_value(rtype)) else: diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/reledit.py --- a/web/views/reledit.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/reledit.py Thu Sep 29 14:47:04 2011 +0200 @@ -106,13 +106,15 @@ def _handle_attribute(self, rschema, role, divid, reload, action): rvid = self._rules.get('rvid', None) if rvid is not None: - value = self._cw.view(rvid, entity=self.entity, rtype=rschema.type) + value = self._cw.view(rvid, entity=self.entity, + rtype=rschema.type, role=role) else: value = self.entity.printable_value(rschema.type) if not self._should_edit_attribute(rschema): self.w(value) return - form, renderer = self._build_form(self.entity, rschema, role, divid, 'base', reload, action) + 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) diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/tableview.py --- a/web/views/tableview.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/tableview.py Thu Sep 29 14:47:04 2011 +0200 @@ -94,6 +94,8 @@ :param subvid: cell view :param displayfilter: filter that selects rows to display :param headers: columns' titles + :param displaycols: indexes of columns to display (first column is 0) + :param displayactions: if True, display action menu """ req = self._cw req.add_js('jquery.tablesorter.js') diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/tabs.py --- a/web/views/tabs.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/tabs.py Thu Sep 29 14:47:04 2011 +0200 @@ -190,6 +190,8 @@ """ __select__ = EntityView.__select__ & partial_has_related_entities() vid = 'list' + # to be defined in concrete classes + rtype = title = None def cell_call(self, row, col): rset = self.cw_rset.get_entity(row, col).related(self.rtype, role(self)) diff -r 6cebeb1f386a -r ad0eeb0f7a8d web/views/treeview.py --- a/web/views/treeview.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/treeview.py Thu Sep 29 14:47:04 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 6cebeb1f386a -r ad0eeb0f7a8d web/views/workflow.py --- a/web/views/workflow.py Thu Sep 29 14:07:37 2011 +0200 +++ b/web/views/workflow.py Thu Sep 29 14:47:04 2011 +0200 @@ -174,6 +174,7 @@ warn('[3.10] %s should now implement render_body instead of cell_call' % self.__class__, DeprecationWarning) self.w = w + # pylint: disable=E1101 self.cell_call(self.entity.cw_row, self.entity.cw_col) else: self.entity.view('wfhistory', w=w, title=None) diff -r 6cebeb1f386a -r ad0eeb0f7a8d wsgi/request.py --- a/wsgi/request.py Thu Sep 29 14:07:37 2011 +0200 +++ b/wsgi/request.py Thu Sep 29 14:47:04 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.